package fr.imag.adele.apam.declarations.repository.maven; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.felix.ipojo.metadata.Element; import org.apache.felix.ipojo.parser.ManifestMetadataParser; import org.apache.felix.ipojo.parser.ParseException; import org.apache.maven.artifact.Artifact; import fr.imag.adele.apam.CST; import fr.imag.adele.apam.declarations.ComponentDeclaration; import fr.imag.adele.apam.declarations.PropertyDefinition; import fr.imag.adele.apam.declarations.encoding.Decoder; import fr.imag.adele.apam.declarations.encoding.ipojo.MetadataParser; import fr.imag.adele.apam.declarations.references.components.ComponentReference; import fr.imag.adele.apam.declarations.references.components.VersionedReference; import fr.imag.adele.apam.declarations.repository.ComponentIndex; import fr.imag.adele.apam.declarations.repository.Repository; import fr.imag.adele.apam.declarations.tools.Reporter; import fr.imag.adele.apam.declarations.tools.Reporter.Severity; /** * This class represents a component repository backed-up by the iPOJO metadata available in * an APAM component built by maven * * NOTE This repository is intended to be used at build time and we are supposing there are a * few components by bundle. Then, we keep permanently a list of components in memory, and * it is never automatically reloaded, only manually using {@link #refresh()} * * @author vega * */ public class MavenArtifactRepository implements Repository, Classpath.Entry { /** * The reporter to signal errors and debug information */ private final Reporter reporter; /** * The maven artifact that backs-up this repository */ private final Artifact artifact; /** * The version of APAM that is being used */ private final String apamVersion; /** * The list of components loaded from this artifact. */ private final List<ComponentDeclaration> components; /** * The index of components by version */ private final ComponentIndex index; /** * The list of classes in this bundle */ private final Set<String> classes; public MavenArtifactRepository(Artifact artifact, String apamVersion, Reporter reporter) throws IOException, ParseException { this.artifact = artifact; this.apamVersion = apamVersion; this.reporter = reporter; this.components = new ArrayList<ComponentDeclaration>(); this.index = new ComponentIndex(); this.classes = new HashSet<String>(); load(); } /** * The list of declared components of this artifact */ public List<ComponentDeclaration> getComponents() { return components; } /** * The source artifact of this repository */ public Artifact getArtifact() { return artifact; } /** * Reloads the component information from the underlying artifact */ public void refresh() { try { load(); } catch (Exception e) { error("Error reloading components from maven artifact "+artifact.getId(),e); } } @Override public <C extends ComponentDeclaration> C getComponent(ComponentReference<C> reference) { return index.getComponent(reference); } @Override public <C extends ComponentDeclaration> C getComponent(VersionedReference<C> reference) { return index.getComponent(reference); } @Override public boolean contains(String fullyQualifiedClassName) { return classes.contains(fullyQualifiedClassName); } /** * The additional properties added to components to handle maven versionning */ public static final String PROPERTY_VERSION_APAM = "apam.version"; public static final String PROPERTY_VERSION_MAVEN_GROUP = "maven.groupId"; public static final String PROPERTY_VERSION_MAVEN_ARTIFACT = "maven.artifactId"; public static final String PROPERTY_VERSION_MAVEN_VERSION = "maven.version"; /** * Adds version information to a loaded component */ private ComponentDeclaration versioned(ComponentDeclaration component) { addProperty(component,PROPERTY_VERSION_APAM,"version",apamVersion.replace('-', '.')); addProperty(component,PROPERTY_VERSION_MAVEN_GROUP,"string",artifact.getGroupId()); addProperty(component,PROPERTY_VERSION_MAVEN_ARTIFACT,"string",artifact.getArtifactId()); addProperty(component,PROPERTY_VERSION_MAVEN_VERSION,"string",artifact.getVersion()); addProperty(component,CST.VERSION,"version",artifact.getVersion().replace('-', '.')); return component; } /** * Loads APAM components and class information from the target file associated to this maven artifact */ private void load() throws IOException, ParseException { JarFile bundle = null; Manifest manifest = null; try { if (artifact.getFile() == null || !artifact.getFile().exists() || !artifact.getFile().isFile()) { throw new IOException("Error loading jar file for maven artifact "+artifact.getId()); } bundle = new JarFile(artifact.getFile()); manifest = bundle.getManifest(); loadComponents(manifest); loadClasses(bundle); } finally { if (bundle != null) bundle.close(); } } /** * Loads APAM component metadata stored in the manifest of the bundle */ protected void loadComponents(Manifest manifest) throws ParseException { components.clear(); index.clear(); if (manifest == null) return; String componentHeader = manifest.getMainAttributes().getValue("iPOJO-Components"); if (componentHeader == null) { return; } Element metadata = ManifestMetadataParser.parseHeaderMetadata(componentHeader); /* * parse all the declared components */ info("Parsing Apam metadata for " + artifact.getId()+ " from " + artifact.getFile()); StringBuilder contents = new StringBuilder(" contains components: "); Decoder<Element> parser = new MetadataParser(); for (Element element : metadata.getElements()) { ComponentDeclaration declaration = parser.decode(element,reporter); if (declaration == null) continue; components.add(declaration); index.put(versioned(declaration)); contents.append(declaration.getName()).append(" "); } info(contents.toString()); } /** * Load the list of classes available in this bundle * * TODO We should consider classes visibility as specified by the Export-Packages in the bundle manifest */ protected void loadClasses(JarFile bundle) { classes.clear(); Enumeration<JarEntry> entries = bundle.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.isDirectory()) continue; if (!entry.getName().endsWith(".class")) continue; String className = entry.getName().substring(0,entry.getName().lastIndexOf(".class")).replace('/','.').replace('\\','.').replace('$','.'); classes.add(className); } } /** * Add a property to an existing component * * NOTE We may be modifying a component that has already version information attached (either because the * component has already been built, and we are loading it as a dependency, or because the user has added * the information manually) so we need to be careful not to override it * */ private static final void addProperty(ComponentDeclaration component, String property, String type, String value) { /* */ PropertyDefinition defintition = component.getPropertyDefinition(property); if (defintition == null) { defintition = new PropertyDefinition(component.getReference(), property, type, null); component.getPropertyDefinitions().add(defintition); } String currentValue = component.getProperty(property); if (currentValue == null) { component.getProperties().put(property, value); } } /** * Utility functions to report errors and debug infor * */ public void error(String message, Throwable cause) { error(message); for (StackTraceElement frame : cause.getStackTrace()) { error(frame.toString()); } } public final void error(String message) { reporter.report(Severity.ERROR, message); } public final void warning(String message) { reporter.report(Severity.WARNING, message); } public final void info(String message) { reporter.report(Severity.INFO, message); } }