From b4056e909a8791a67dc726aa3581bd4f064c3d31 Mon Sep 17 00:00:00 2001 From: slaws Date: Tue, 28 Sep 2010 12:05:54 +0000 Subject: TUSCANY-3674 - Add in a new mojo for generating meta data about groups of bundles, i.e. Tuscany extensions. This is basically a copy of ModuleBundlesBuildMojo.java but massaged to generate ant filesets, manifest etc. for a specified set of dependencies rather than for artefacts with the string "feature" in the name. It also doesn't do the copying of jars into /modules. There is therefore a lot of complexity in here that's not required. Needs rationalising if we decide we like the output. git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1002132 13f79535-47bb-0310-9956-ffa450edef68 --- .../bundle/plugin/BundlesMetaDataBuildMojo.java | 1428 ++++++++++++++++++++ .../tuscany/maven/bundle/plugin/Extension.java | 60 + .../tuscany/maven/bundle/plugin/Feature.java | 59 + 3 files changed, 1547 insertions(+) create mode 100644 maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundlesMetaDataBuildMojo.java create mode 100644 maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Extension.java create mode 100644 maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Feature.java diff --git a/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundlesMetaDataBuildMojo.java b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundlesMetaDataBuildMojo.java new file mode 100644 index 0000000000..4e316a4863 --- /dev/null +++ b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundlesMetaDataBuildMojo.java @@ -0,0 +1,1428 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.maven.bundle.plugin; + +import static org.apache.tuscany.maven.bundle.plugin.BundleUtil.write; +import static org.osgi.framework.Constants.BUNDLE_CLASSPATH; +import static org.osgi.framework.Constants.BUNDLE_VERSION; +import static org.osgi.framework.Constants.RESOLUTION_DIRECTIVE; +import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL; +import static org.osgi.framework.Constants.VISIBILITY_DIRECTIVE; +import static org.osgi.framework.Constants.VISIBILITY_REEXPORT; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.resolver.ArtifactCollector; +import org.apache.maven.artifact.resolver.ArtifactNotFoundException; +import org.apache.maven.artifact.resolver.ArtifactResolutionException; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; +import org.apache.maven.artifact.resolver.filter.ArtifactFilter; +import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.artifact.InvalidDependencyVersionException; +import org.apache.maven.shared.dependency.tree.DependencyNode; +import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; +import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; +import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor; +import org.osgi.framework.Constants; + +/** + * A maven plugin that generates meta-data for groups of Tuscany jars from the distribution's + * modules directory. The meta-data group jars either as mnaifest jars, ant filesets or + * simply as human readable lists of jars + * + * @version $Rev: 929729 $ $Date: 2010-03-31 22:52:37 +0100 (Wed, 31 Mar 2010) $ + * @goal generate-meta-data + * @phase generate-resources + * @requiresDependencyResolution test + * @description Generate a modules directory containing OSGi bundles for all the project's module dependencies. + */ +public class BundlesMetaDataBuildMojo extends AbstractMojo { + + private static final String GATEWAY_BUNDLE = "org.apache.tuscany.sca.gateway"; + + /** + * The project to create a distribution for. + * + * @parameter expression="${project}" + * @required + * @readonly + */ + private MavenProject project; + + /** + * Project builder -- builds a model from a pom.xml + * + * @component role="org.apache.maven.project.MavenProjectBuilder" + * @required + * @readonly + */ + private MavenProjectBuilder mavenProjectBuilder; + /** + * Used to look up Artifacts in the remote repository. + * + * @component + */ + private org.apache.maven.artifact.factory.ArtifactFactory factory; + + /** + * Used to look up Artifacts in the remote repository. + * + * @component + */ + private org.apache.maven.artifact.resolver.ArtifactResolver resolver; + + /** + * @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource" + * hint="maven" + * @required + * @readonly + */ + private ArtifactMetadataSource artifactMetadataSource; + + /** + * The artifact collector to use. + * + * @component + * @required + * @readonly + */ + private ArtifactCollector artifactCollector; + + /** + * The dependency tree builder to use. + * + * @component + * @required + * @readonly + */ + private DependencyTreeBuilder dependencyTreeBuilder; + + + /** + * Location of the local repository. + * + * @parameter expression="${localRepository}" + * @readonly + * @required + */ + private org.apache.maven.artifact.repository.ArtifactRepository local; + + /** + * List of Remote Repositories used by the resolver + * + * @parameter expression="${project.remoteArtifactRepositories}" + * @readonly + * @required + */ + private java.util.List remoteRepos; + + /** + * Target directory. + * + * @parameter expression="${project.build.directory}/modules" + */ + private File targetDirectory; + + /** + * @parameter default-value="features" + */ + private String featuresName = "features"; + + /** + * Directories containing artifacts to exclude. + * + * @parameter + */ + private File[] excludeDirectories; + + /** + * Directories containing groupids to exclude. + * + * @parameter + */ + private String[] excludeGroupIds; + + /** + * Directories containing groupids to include. + * + * @parameter + */ + private String[] includeGroupIds; + + /** + * Set to true to generate configurations under a folder named as the distro + * + * @parameter default-value="true" + */ + private boolean useDistributionName = false; + + /** + * Set to true to generate the contents of the modules directory. + * + * @parameter default-value="true" + */ + private boolean generateModules = false; + + /** + * Set to true to generate a PDE target platform configuration. + * + * @parameter default-value="true" + */ + private boolean generateTargetPlatform = true; + + /** + * Expand non-tuscany bundles as a folder + * @parameter default-value="false" + */ + private boolean expandThirdPartyBundle = false; + + /** + * OSGi execution environment + */ + private String executionEnvironment; + + /** + * A list of Eclipse features to be added to the target definition + * @parameter + */ + private String[] eclipseFeatures; + + /** + * If we use the running eclipse as the default location for the target + * @parameter default-value="true" + */ + private boolean useDefaultLocation = true; + + /** + * Set to true to generate a gateway bundle tuscany-gateway-.jar that handles split packages and META-INF/services. + * + * @parameter default-value="true" + */ + private boolean generateGatewayBundle; + + /** + * @parameter default-value="false" + */ + private boolean gatewayReexport; + + /** + * Set to true to generate a plugin.xml. + * + * @parameter default-value="false" + */ + private boolean generatePlugin; + + /** + * Generate a configuration/config.ini for equinox + * @parameter default-value="true" + */ + private boolean generateConfig = true; + + /** + * Generate an aggregated OSGi bundle for each feature + * @parameter default-value="false" + */ + private boolean generateAggregatedBundle = false; + + /** + * @parameter default-value="true" + */ + private boolean generateBundleStart = true; + + /** + * @parameter default-value="true" + */ + private boolean includeConflictingDepedencies = true; + + /** + * Generete manifest.jar + * @parameter default-value="true" + */ + private boolean generateManifestJar = true; + + /** + * @parameter default-value="tuscany-sca-manifest.jar" + */ + private String manifestJarName = "tuscany-sca-manifest.jar"; + + /** + * @parameter default-value="tuscany-sca-equinox-manifest.jar" + */ + private String equinoxManifestJarName = "tuscany-sca-equinox-manifest.jar"; + + /** + * @parameter default-value="jar,bundle" + */ + private String artifactTypes; + + /** + * @parameter default-value="true" + */ + private boolean generateAntScript = true; + + /** + * @parameter + */ + private ArtifactAggregation[] artifactAggregations; + + /** + * @parameter + */ + private ArtifactManifest[] artifactManifests; + + /** + * Feature artifact IDs to be processed and included in the features directory + * @parameter + */ + private Feature[] features; + + /** + * Extension artifact IDs to be processed and included in the extensions directory + * @parameter + */ + private Extension[] extensions; + + /** + * Inserts a generic Eclipse-BuddyPolicy header into generated artifacts manifests + * @parameter + */ + private String eclipseBuddyPolicy = null; + + private static final String XML_PI = ""; + private static final String ASL_HEADER = + ""; + + /** + * Group the artifacts by distribution poms + */ + private class ProjectSet { + // Distribution projects + private Map projects; + // Key: the pom artifact id + // Value: the names for the artifacts + private Map> nameMap = new HashMap>(); + + private Map artifactToNameMap = new HashMap(); + + public ProjectSet(List projects) { + super(); + this.projects = new HashMap(); + for (MavenProject p : projects) { + this.projects.put(p.getArtifactId(), p); + } + } + + private MavenProject getProject(String artifactId) { + return projects.get(artifactId); + } + + private void add(Artifact artifact, String name) { + String key = ArtifactUtils.versionlessKey(artifact); + for (MavenProject p : projects.values()) { + Artifact a = (Artifact)p.getArtifactMap().get(key); + if (a == null && + artifact.getArtifactId().equals(p.getArtifactId())){ + a = p.getArtifact(); + } + if (a != null) { + Set names = nameMap.get(p.getArtifactId()); + if (names == null) { + names = new HashSet(); + nameMap.put(p.getArtifactId(), names); + } + names.add(name); + } + } + artifactToNameMap.put(key, name); + } + } + + private Manifest findManifest(Artifact artifact) throws IOException { + if (artifactManifests == null) { + return null; + } + for (ArtifactManifest m : artifactManifests) { + if (m.matches(artifact)) { + File mf = m.getManifestFile(); + if (mf != null) { + FileInputStream is = new FileInputStream(mf); + Manifest manifest = new Manifest(is); + is.close(); + getLog().info("MANIFEST.MF found for " + artifact + " (" + mf + ")"); + return manifest; + } else { + getLog().info("Overriding the manifest for " + artifact); + Manifest manifest = BundleUtil.getManifest(artifact.getFile()); + Set jarFiles = new HashSet(); + jarFiles.add(artifact.getFile()); + String symbolicName = BundleUtil.getBundleSymbolicName(manifest); + if (symbolicName == null) { + // Not a bundle + continue; + } + String version = manifest.getMainAttributes().getValue(BUNDLE_VERSION); + manifest = + BundleUtil.libraryManifest(jarFiles, + symbolicName, + symbolicName, + version, + null, + this.eclipseBuddyPolicy, + this.executionEnvironment); + // Remove it as it will be added later on + manifest.getMainAttributes().remove(new Attributes.Name(BUNDLE_CLASSPATH)); + return manifest; + } + } + } + return null; + } + + /** + * Gets the artifact filter to use when resolving the dependency tree. + * + * @return the artifact filter + */ + private ArtifactFilter createResolvingArtifactFilter(String scope) { + ArtifactFilter filter; + + // filter scope + if (scope != null) { + getLog().debug("+ Resolving dependency tree for scope '" + scope + "'"); + + filter = new ScopeArtifactFilter(scope); + } else { + filter = null; + } + + return filter; + } + + public void execute() throws MojoExecutionException { + Log log = getLog(); + + Set artifacts = null; + if (includeConflictingDepedencies) { + try { + artifacts = getDependencyArtifacts(project); + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); + } + } else { + artifacts = project.getArtifacts(); + } + + try { + + // Create the target directory + File root; + if (targetDirectory == null) { + root = new File(project.getBuild().getDirectory(), "plugins/"); + } else { + root = targetDirectory; + } + + if (generateModules){ + root.mkdirs(); + } + + // Build sets of exclude directories and included/excluded/groupids + Set excludedFileNames = new HashSet(); + if (excludeDirectories != null) { + for (File f : excludeDirectories) { + if (f.isDirectory()) { + for (String n : f.list()) { + excludedFileNames.add(n); + } + } + } + } + Set includedGroupIds = new HashSet(); + if (includeGroupIds != null) { + for (String g : includeGroupIds) { + includedGroupIds.add(g); + } + } + Set excludedGroupIds = new HashSet(); + if (excludeGroupIds != null) { + for (String g : excludeGroupIds) { + excludedGroupIds.add(g); + } + } + + // Find all the distribution poms + List poms = new ArrayList(); + + if (useDistributionName) { + for (Object o : project.getArtifacts()) { + Artifact artifact = (Artifact)o; + if ("pom".equals(artifact.getType()) && artifact.getGroupId().equals(project.getGroupId()) + && artifact.getArtifactId().startsWith("tuscany-feature-")) { + log.info("Dependent distribution: " + artifact); + MavenProject pomProject = buildProject(artifact); + poms.add(pomProject); + // log.info(pomProject.getArtifactMap().toString()); + } + } + } + + if (features != null){ + // find all the features that require processing + for (Object o : project.getArtifacts()) { + Artifact artifact = (Artifact)o; + for(Feature feature : features){ + if (artifact.getGroupId().equals(feature.getGroupId()) && + artifact.getArtifactId().equals(feature.getArtifactId())) { + log.info("Feature: " + artifact); + MavenProject pomProject = buildProject(artifact); + poms.add(pomProject); + } + } + } + + // force useDistributionName to true so that subsequent generation works + useDistributionName = true; + } + + if (extensions != null){ + // find all the extensions that require processing + // TODO - putting them in features dir for the time being + for (Object o : project.getArtifacts()) { + Artifact artifact = (Artifact)o; + for(Extension extension : extensions) { + if (artifact.getGroupId().equals(extension.getGroupId()) && + artifact.getArtifactId().equals(extension.getArtifactId())) { + log.info("Extension: " + artifact); + MavenProject pomProject = buildProject(artifact); + poms.add(pomProject); + } + } + } + + // force useDistributionName to true so that subsequent generation works + useDistributionName = true; + } + + // If no features have been specified assume that the current + // project defines the feature + if (poms.size() == 0){ + poms.add(project); + } + + // Process all the dependency artifacts + ProjectSet bundleSymbolicNames = new ProjectSet(poms); + ProjectSet bundleLocations = new ProjectSet(poms); + ProjectSet jarNames = new ProjectSet(poms); + ProjectSet serviceProviders = new ProjectSet(poms); + + for (Artifact artifact: artifacts) { + + log.info("Processing artifact: " + artifact); + + // Only consider Compile and Runtime dependencies + if (!(Artifact.SCOPE_COMPILE.equals(artifact.getScope()) || Artifact.SCOPE_RUNTIME.equals(artifact + .getScope()) + || Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) || (generateTargetPlatform && Artifact.SCOPE_TEST + .equals(artifact.getScope())))) { + log.info("Skipping artifact: " + artifact); + continue; + } + + if (artifactTypes == null) { + artifactTypes = "jar,bundle"; + } + String types[] = artifactTypes.trim().split("( |\t|\n|\r|\f|,)+"); + Set typeSet = new HashSet(Arrays.asList(types)); + + // Only consider JAR and WAR files + if (!typeSet.contains(artifact.getType())) { + log.debug("Artifact with unknown type is skipped: " + artifact); + continue; + } + + // Exclude artifact if its groupId is excluded or if it's not included + if (excludedGroupIds.contains(artifact.getGroupId())) { + log.debug("Artifact groupId is excluded: " + artifact); + continue; + } + if (!includedGroupIds.isEmpty()) { + if (!includedGroupIds.contains(artifact.getGroupId())) { + log.debug("Artifact groupId is not included: " + artifact); + continue; + } + } + + File artifactFile = artifact.getFile(); + if (!artifactFile.exists()) { + log.warn("Artifact doesn't exist: " + artifact); + continue; + } + + if (log.isDebugEnabled()) { + log.debug("Processing artifact: " + artifact); + } + + Manifest customizedMF = findManifest(artifact); + + // Get the bundle name if the artifact is an OSGi bundle + Manifest mf = null; + String bundleName = null; + try { + mf = BundleUtil.getManifest(artifactFile); + bundleName = BundleUtil.getBundleSymbolicName(mf); + } catch (IOException e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + if (bundleName != null && customizedMF == null) { + + // Exclude artifact if its file name is excluded + if (excludedFileNames.contains(artifactFile.getName())) { + log.debug("Artifact file is excluded: " + artifact); + continue; + } + + if (generateModules){ + // Copy an OSGi bundle as is + log.info("Adding OSGi bundle artifact: " + artifact); + } + + if (!expandThirdPartyBundle || artifact.getGroupId().startsWith("org.apache.tuscany.sca") + || artifact.getGroupId().startsWith("org.eclipse")) { + if (generateModules){ + copyFile(artifactFile, root); + } + bundleSymbolicNames.add(artifact, bundleName); + bundleLocations.add(artifact, artifactFile.getName()); + jarNames.add(artifact, artifactFile.getName()); + if (isServiceProvider(mf)) { + serviceProviders.add(artifact, bundleName); + } + } else { + // Expanding the bundle into a folder + + setBundleClassPath(mf, artifactFile); + + int index = artifactFile.getName().lastIndexOf('.'); + String dirName = artifactFile.getName().substring(0, index); + File dir = new File(root, dirName); + if (generateModules){ + File file = new File(dir, "META-INF"); + file.mkdirs(); + file = new File(file, "MANIFEST.MF"); + + FileOutputStream fos = new FileOutputStream(file); + write(mf, fos); + fos.close(); + copyFile(artifactFile, dir); + } + bundleSymbolicNames.add(artifact, bundleName); + bundleLocations.add(artifact, dir.getName()); + jarNames.add(artifact, dirName + "/" + artifactFile.getName()); + if (isServiceProvider(mf)) { + serviceProviders.add(artifact, bundleName); + } + } + + } else if ("war".equals(artifact.getType())) { + + // Exclude artifact if its file name is excluded + if (excludedFileNames.contains(artifactFile.getName())) { + log.debug("Artifact file is excluded: " + artifact); + continue; + } + + if (generateModules){ + // Copy a WAR as is + log.info("Adding WAR artifact: " + artifact); + copyFile(artifactFile, root); + } + } else { + + int index = artifactFile.getName().lastIndexOf('.'); + String dirName = artifactFile.getName().substring(0, index); + File dir = new File(root, dirName); + + // Exclude artifact if its file name is excluded + if (excludedFileNames.contains(dir.getName())) { + log.debug("Artifact file is excluded: " + artifact); + continue; + } + + if (artifactAggregations != null) { + boolean aggregated = false; + for (ArtifactAggregation group : artifactAggregations) { + if (group.matches(artifact)) { + group.getArtifacts().add(artifact); + aggregated = true; + break; + } + } + if (aggregated) { + continue; + } + } + + // create manifest directory + File file = new File(dir, "META-INF"); + if (generateModules){ + // Create a bundle directory for a non-OSGi JAR + log.info("Adding JAR artifact: " + artifact); + + file.mkdirs(); + } + + String symbolicName = null; + if (customizedMF == null) { + String version = BundleUtil.osgiVersion(artifact.getVersion()); + + Set jarFiles = new HashSet(); + jarFiles.add(artifactFile); + symbolicName = (artifact.getGroupId() + "." + artifact.getArtifactId()); + if (generateModules){ + mf = + BundleUtil.libraryManifest(jarFiles, + symbolicName, + symbolicName, + version, + null, + this.eclipseBuddyPolicy, + this.executionEnvironment); + + file = new File(file, "MANIFEST.MF"); + FileOutputStream fos = new FileOutputStream(file); + write(mf, fos); + fos.close(); + log.info("Writing generated manifest for: " + artifact + " to " + file); + } + } else { + mf = customizedMF; + symbolicName = BundleUtil.getBundleSymbolicName(mf); + if (symbolicName == null) { + throw new MojoExecutionException("Invalid customized MANIFEST.MF for " + artifact); + } + setBundleClassPath(mf, artifactFile); + + // re-find the custom MF file and copy it + // I can't get the manifest file from the manifest itself + // the Manifest read/write operation seems to be filtering + // out some entries that I've added manually???? + File artifactManifest = null; + + if (artifactManifests != null) { + for (ArtifactManifest m : artifactManifests) { + if (m.matches(artifact)) { + artifactManifest = m.getManifestFile(); + break; + } + } + } + + if (generateModules){ + file = new File(file, "MANIFEST.MF"); + + if (artifactManifest != null){ + log.info("Copying: " + artifactManifest + " to " + file); + copyManifest(artifactManifest, file); + } else { + FileOutputStream fos = new FileOutputStream(file); + write(mf, fos); + fos.close(); + log.info("Writing generated manifest for: " + artifact + " to " + file); + } + } + } + + if (generateModules){ + copyFile(artifactFile, dir); + } + + bundleSymbolicNames.add(artifact, symbolicName); + bundleLocations.add(artifact, dir.getName()); + jarNames.add(artifact, dirName + "/" + artifactFile.getName()); + if (isServiceProvider(mf)) { + serviceProviders.add(artifact, symbolicName); + } + } + } + + + if (artifactAggregations != null) { + for (ArtifactAggregation group : artifactAggregations) { + if (group.getArtifacts().isEmpty()) { + continue; + } + String symbolicName = group.getSymbolicName(); + String version = group.getVersion(); + File dir = new File(root, symbolicName + "-" + version); + dir.mkdir(); + Set jarFiles = new HashSet(); + Artifact artifact = null; + for (Artifact a : group.getArtifacts()) { + log.info("Aggragating JAR artifact: " + a); + artifact = a; + jarFiles.add(a.getFile()); + copyFile(a.getFile(), dir); + jarNames.add(a, symbolicName + "-" + version + "/" + a.getFile().getName()); + } + Manifest mf = + BundleUtil.libraryManifest(jarFiles, + symbolicName, + symbolicName, + version, + null, + this.eclipseBuddyPolicy, + this.executionEnvironment); + File file = new File(dir, "META-INF"); + file.mkdirs(); + file = new File(file, "MANIFEST.MF"); + + FileOutputStream fos = new FileOutputStream(file); + write(mf, fos); + fos.close(); + log.info("Written aggregate manifest"); + bundleSymbolicNames.add(artifact, symbolicName); + bundleLocations.add(artifact, dir.getName()); + if (isServiceProvider(mf)) { + serviceProviders.add(artifact, symbolicName); + } + } + } + + + if (generateGatewayBundle) { + generateGatewayBundle(serviceProviders); + } + + /* + if (useDistributionName) { + bundleLocations.nameMap.remove(project.getArtifactId()); + jarNames.nameMap.remove(project.getArtifactId()); + bundleSymbolicNames.nameMap.remove(project.getArtifactId()); + } + */ + + // Generate a PDE target + if (generateTargetPlatform) { + generatePDETarget(bundleSymbolicNames, root, log); + } + + // Generate a plugin.xml referencing the PDE target + if (generatePlugin) { + File pluginxml = new File(project.getBasedir(), "plugin.xml"); + FileOutputStream pluginXMLFile = new FileOutputStream(pluginxml); + writePluginXML(new PrintStream(pluginXMLFile)); + pluginXMLFile.close(); + } + + if (generateConfig) { + generateEquinoxConfig(bundleLocations, root, log); + } + + if (generateManifestJar) { + generateManifestJar(jarNames, root, log); + generateEquinoxLauncherManifestJar(jarNames, root, log); + } + + if (generateAntScript) { + generateANTPath(jarNames, root, log); + } + + if (generateAggregatedBundle) { + generateAggregatedBundles(bundleLocations, root, log); + } + + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + } + + private Set getDependencyArtifacts(MavenProject project) throws DependencyTreeBuilderException, + ArtifactResolutionException, ArtifactNotFoundException { + Log log = getLog(); + Set artifacts = new HashSet(); + ArtifactFilter artifactFilter = createResolvingArtifactFilter(Artifact.SCOPE_RUNTIME); + + // TODO: note that filter does not get applied due to MNG-3236 + + DependencyNode rootNode = + dependencyTreeBuilder.buildDependencyTree(project, + local, + factory, + artifactMetadataSource, + artifactFilter, + artifactCollector); + CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor(); + rootNode.accept(visitor); + + // Add included artifacts + for (Object node : visitor.getNodes()) { + DependencyNode depNode = (DependencyNode)node; + int state = depNode.getState(); + if (state == DependencyNode.INCLUDED ) { + Artifact artifact = depNode.getArtifact(); + // Exclude the project artifact to avoid offline resolution failure + if (!artifact.equals(project.getArtifact())) { + resolver.resolve(artifact, remoteRepos, local); + artifacts.add(artifact); + } + } + } + // Scan for newer versions that are omitted + for (Object node : visitor.getNodes()) { + DependencyNode depNode = (DependencyNode)node; + int state = depNode.getState(); + if (state == DependencyNode.OMITTED_FOR_CONFLICT) { + Artifact artifact = depNode.getArtifact(); + resolver.resolve(artifact, remoteRepos, local); + if (state == DependencyNode.OMITTED_FOR_CONFLICT) { + Artifact related = depNode.getRelatedArtifact(); + if (log.isDebugEnabled()) { + log.debug("Dependency node: " + depNode); + } + // Compare the version + ArtifactVersion v1 = new DefaultArtifactVersion(artifact.getVersion()); + ArtifactVersion v2 = new DefaultArtifactVersion(related.getVersion()); + if (v1.compareTo(v2) > 0) { + // Only add newer version if it is omitted for conflict + if (artifacts.add(artifact)) { + log.info("Dependency node added: " + depNode); + } + } + } + } + } + return artifacts; + } + + private static boolean isServiceProvider(Manifest mf) { + if (mf != null) { + String export = (String)mf.getMainAttributes().getValue(Constants.EXPORT_PACKAGE); + if (export != null && export.contains(BundleUtil.META_INF_SERVICES)) { + return true; + } + } + return false; + } + + /** + * Generate a gateway bundle that aggregate other bundles to handle split packages + * @param bundleSymbolicNames + * @throws FileNotFoundException + * @throws IOException + */ + private void generateGatewayBundle(ProjectSet bundleSymbolicNames) throws FileNotFoundException, IOException { + Manifest manifest = new Manifest(); + Attributes attrs = manifest.getMainAttributes(); + StringBuffer requireBundle = new StringBuffer(); + for (String name : new HashSet(bundleSymbolicNames.artifactToNameMap.values())) { + requireBundle.append(name).append(";").append(RESOLUTION_DIRECTIVE).append(":=") + .append(RESOLUTION_OPTIONAL); + if (gatewayReexport) { + requireBundle.append(";").append(VISIBILITY_DIRECTIVE).append(":=").append(VISIBILITY_REEXPORT); + } + requireBundle.append(","); + } + int len = requireBundle.length(); + if (len > 0 && requireBundle.charAt(len - 1) == ',') { + requireBundle.deleteCharAt(len - 1); + attrs.putValue(Constants.REQUIRE_BUNDLE, requireBundle.toString()); + attrs.putValue("Manifest-Version", "1.0"); + attrs.putValue("Implementation-Vendor", "The Apache Software Foundation"); + attrs.putValue("Implementation-Vendor-Id", "org.apache"); + attrs.putValue(Constants.BUNDLE_VERSION, "2.0.0"); + attrs.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); + attrs.putValue(Constants.BUNDLE_SYMBOLICNAME, GATEWAY_BUNDLE); + attrs.putValue(Constants.BUNDLE_NAME, "Apache Tuscany SCA Gateway Bundle"); + attrs.putValue(Constants.BUNDLE_VENDOR, "The Apache Software Foundation"); + attrs.putValue(Constants.EXPORT_PACKAGE, "META-INF.services"); + attrs.putValue(Constants.DYNAMICIMPORT_PACKAGE, "*"); + attrs.putValue(Constants.BUNDLE_ACTIVATIONPOLICY, Constants.ACTIVATION_LAZY); + File file = new File(targetDirectory, "tuscany-gateway-" + project.getVersion() + ".jar"); + getLog().info("Generating gateway bundle: " + file.getAbsolutePath()); + FileOutputStream fos = new FileOutputStream(file); + JarOutputStream jos = new JarOutputStream(fos, manifest); + addFileToJar(jos, "META-INF/LICENSE", getClass().getResource("LICENSE.txt")); + addFileToJar(jos, "META-INF/NOTICE", getClass().getResource("NOTICE.txt")); + jos.close(); + } + } + + private void setBundleClassPath(Manifest mf, File artifactFile) { + // Add the Bundle-ClassPath + String cp = mf.getMainAttributes().getValue(BUNDLE_CLASSPATH); + if (cp == null) { + cp = artifactFile.getName(); + } else { + cp = cp + "," + artifactFile.getName(); + } + mf.getMainAttributes().putValue(BUNDLE_CLASSPATH, cp); + } + + private void generateANTPath(ProjectSet jarNames, File root, Log log) throws FileNotFoundException, IOException { + for (Map.Entry> e : jarNames.nameMap.entrySet()) { + Set jars = e.getValue(); + File feature = new File(root, "../" + featuresName + "/" + (useDistributionName ? trim(e.getKey()) : "")); + feature.mkdirs(); + File antPath = new File(feature, "build-path.xml"); + log.info("Generating ANT build path: " + antPath.getCanonicalPath()); + FileOutputStream fos = new FileOutputStream(antPath); + PrintStream ps = new PrintStream(fos); + // ps.println(XML_PI); + ps.println(ASL_HEADER); + String name = trim(e.getKey()); + ps.println(""); + //ps.println(" "); + //ps.println(" "); + ps.println(" "); + ps.println(" "); + for (String jar : jars) { + ps.println(" "); + } + ps.println(" "); + ps.println(" "); + ps.println(""); + } + } + + private void generateManifestJar(ProjectSet jarNames, File root, Log log) throws FileNotFoundException, IOException { + for (Map.Entry> e : jarNames.nameMap.entrySet()) { + MavenProject pom = jarNames.getProject(e.getKey()); + Set jars = e.getValue(); + File feature = new File(root, "../" + featuresName + "/" + (useDistributionName ? trim(e.getKey()) : "")); + feature.mkdirs(); + File mfJar = new File(feature, manifestJarName); + log.info("Generating manifest jar: " + mfJar.getCanonicalPath()); + FileOutputStream fos = new FileOutputStream(mfJar); + Manifest mf = new Manifest(); + StringBuffer cp = new StringBuffer(); + String path = (useDistributionName ? "../../" : "../") + root.getName(); + for (String jar : jars) { + cp.append(path).append('/').append(jar).append(' '); + } + if (cp.length() > 0) { + cp.deleteCharAt(cp.length() - 1); + } + Attributes attrs = mf.getMainAttributes(); + attrs.putValue("Manifest-Version", "1.0"); + attrs.putValue("Implementation-Title", pom.getName()); + attrs.putValue("Implementation-Vendor", "The Apache Software Foundation"); + attrs.putValue("Implementation-Vendor-Id", "org.apache"); + attrs.putValue("Implementation-Version", pom.getVersion()); + attrs.putValue("Class-Path", cp.toString()); + attrs.putValue("Main-Class", "org.apache.tuscany.sca.node.launcher.NodeMain"); + JarOutputStream jos = new JarOutputStream(fos, mf); + addFileToJar(jos, "META-INF/LICENSE", getClass().getResource("LICENSE.txt")); + addFileToJar(jos, "META-INF/NOTICE", getClass().getResource("NOTICE.txt")); + jos.close(); + } + } + + private void generateEquinoxLauncherManifestJar(ProjectSet jarNames, File root, Log log) throws Exception { + String equinoxLauncher = "org.apache.tuscany.sca:tuscany-node-launcher-equinox"; + Artifact artifact = (Artifact)project.getArtifactMap().get(equinoxLauncher); + if (artifact == null) { + return; + } + Set artifacts = resolveTransitively(artifact).getArtifacts(); + File feature = new File(root, "../" + featuresName + "/"); + feature.mkdirs(); + File mfJar = new File(feature, equinoxManifestJarName); + log.info("Generating equinox manifest jar: " + mfJar.getCanonicalPath()); + FileOutputStream fos = new FileOutputStream(mfJar); + Manifest mf = new Manifest(); + StringBuffer cp = new StringBuffer(); + String path = "../" + root.getName(); + + for (Object o : artifacts) { + Artifact a = (Artifact)o; + if (!Artifact.SCOPE_TEST.equals(a.getScope())) { + String id = ArtifactUtils.versionlessKey(a); + String jar = jarNames.artifactToNameMap.get(id); + if (jar != null) { + cp.append(path).append('/').append(jar).append(' '); + } + } + } + if (cp.length() > 0) { + cp.deleteCharAt(cp.length() - 1); + } + Attributes attrs = mf.getMainAttributes(); + attrs.putValue("Manifest-Version", "1.0"); + attrs.putValue("Implementation-Title", artifact.getId()); + attrs.putValue("Implementation-Vendor", "The Apache Software Foundation"); + attrs.putValue("Implementation-Vendor-Id", "org.apache"); + attrs.putValue("Implementation-Version", artifact.getVersion()); + attrs.putValue("Class-Path", cp.toString()); + attrs.putValue("Main-Class", "org.apache.tuscany.sca.node.equinox.launcher.NodeMain"); + JarOutputStream jos = new JarOutputStream(fos, mf); + addFileToJar(jos, "META-INF/LICENSE", getClass().getResource("LICENSE.txt")); + addFileToJar(jos, "META-INF/NOTICE", getClass().getResource("NOTICE.txt")); + jos.close(); + } + + private void generateEquinoxConfig(ProjectSet bundleLocations, File root, Log log) throws IOException { + for (Map.Entry> e : bundleLocations.nameMap.entrySet()) { + Set locations = new HashSet(e.getValue()); + if (generateGatewayBundle) { + locations.add("tuscany-gateway-" + project.getVersion() + ".jar"); + } + File feature = new File(root, "../" + featuresName + "/" + (useDistributionName ? trim(e.getKey()) : "")); + File config = new File(feature, "configuration"); + config.mkdirs(); + File ini = new File(config, "config.ini"); + log.info("Generating configuation: " + ini.getCanonicalPath()); + FileOutputStream fos = new FileOutputStream(ini); + PrintStream ps = new PrintStream(fos); + int size = locations.size(); + if (size > 0) { + ps.println("osgi.bundles=\\"); + int count = 0; + for (String f : locations) { + if (f.startsWith("osgi")) { + count++; + continue; + } + ps.print(" "); + ps.print(f); + // FIXME: We should not add @start for fragments + if (generateBundleStart) { + ps.print("@:start"); + } + if (count == size - 1) { + // Last one + ps.println(); + } else { + ps.println(",\\"); + } + count++; + } + } + ps.println("eclipse.ignoreApp=true"); + // Do not shutdown + ps.println("osgi.noShutdown=true"); + ps.close(); + } + } + + private void generateAggregatedBundles(ProjectSet bundleLocations, File root, Log log) throws Exception { + for (Map.Entry> e : bundleLocations.nameMap.entrySet()) { + Set locations = new HashSet(e.getValue()); + String featureName = (useDistributionName ? trim(e.getKey()) : ""); + File feature = new File(root, "../" + featuresName + "/" + featureName); +// String bundleFileName = "tuscany-bundle-" + featureName + ".jar"; +// if ("".equals(featureName)) { +// bundleFileName = "tuscany-bundle.jar"; +// } + String bundleFileName = "tuscany-bundle.jar"; + File bundleFile = new File(feature, bundleFileName); + log.info("Generating aggregated OSGi bundle: " + bundleFile); + File[] files = new File[locations.size()]; + int i = 0; + for (String child : locations) { + files[i++] = new File(root, child); + } + String bundleVersion = "2.0.0"; + String bundleName = "org.apache.tuscany.sca.bundle"; + +// String bundleName = "org.apache.tuscany.sca.bundle." + featureName; +// if ("".equals(featureName)) { +// bundleName = "org.apache.tuscany.sca.bundle"; +// } + BundleAggregatorMojo.aggregateBundles(log, root, files, bundleFile, bundleName, bundleVersion); + } + } + + private void generatePDETarget(ProjectSet bundleSymbolicNames, File root, Log log) throws FileNotFoundException, + IOException { + for (Map.Entry> e : bundleSymbolicNames.nameMap.entrySet()) { + Set bundles = new HashSet(e.getValue()); + String name = trim(e.getKey()); + File feature = new File(root, "../" + featuresName + "/" + (useDistributionName ? name : "")); + feature.mkdirs(); + File target = new File(feature, "tuscany.target"); + log.info("Generating target definition: " + target.getCanonicalPath()); + FileOutputStream targetFile = new FileOutputStream(target); + if (!bundles.contains("org.eclipse.osgi")) { + bundles.add("org.eclipse.osgi"); + } + if (generateGatewayBundle) { + bundles.add(GATEWAY_BUNDLE); + } + writeTarget(new PrintStream(targetFile), name, bundles, eclipseFeatures); + targetFile.close(); + + // Generate the PDE target definition file for PDE 3.5 + File target35 = new File(feature, "tuscany-pde35.target"); + log.info("Generating target definition: " + target35.getCanonicalPath()); + FileOutputStream target35File = new FileOutputStream(target35); + writePDE35Target(new PrintStream(target35File), name, bundles, eclipseFeatures); + target35File.close(); + + } + } + + private MavenProject buildProject(Artifact artifact) throws ProjectBuildingException, + InvalidDependencyVersionException, ArtifactResolutionException, ArtifactNotFoundException, DependencyTreeBuilderException { + MavenProject pomProject = mavenProjectBuilder.buildFromRepository(artifact, this.remoteRepos, this.local); + if (pomProject.getDependencyArtifacts() == null) { + pomProject.setDependencyArtifacts(pomProject.createArtifacts(factory, null, // Artifact.SCOPE_TEST, + new ScopeArtifactFilter(Artifact.SCOPE_TEST))); + } + if (includeConflictingDepedencies) { + pomProject.setArtifacts(getDependencyArtifacts(pomProject)); + } else { + ArtifactResolutionResult result = + resolver.resolveTransitively(pomProject.getDependencyArtifacts(), + pomProject.getArtifact(), + remoteRepos, + local, + artifactMetadataSource); + pomProject.setArtifacts(result.getArtifacts()); + } + return pomProject; + } + + private ArtifactResolutionResult resolveTransitively(Artifact artifact) throws ArtifactResolutionException, + ArtifactNotFoundException { + Artifact originatingArtifact = factory.createBuildArtifact("dummy", "dummy", "1.0", "jar"); + + return resolver.resolveTransitively(Collections.singleton(artifact), + originatingArtifact, + local, + remoteRepos, + artifactMetadataSource, + null); + } + + /** + * Convert tuscany-feature-xyz to feature-xyz + * @param artifactId + * @return + */ + private String trim(String artifactId) { + if (artifactId.startsWith("tuscany-feature-")) { + return artifactId.substring("tuscany-feature-".length()); + } else { + return artifactId; + } + } + + private static void copyFile(File jar, File dir) throws FileNotFoundException, IOException { + byte[] buf = new byte[4096]; + File jarFile = new File(dir, jar.getName()); + FileInputStream in = new FileInputStream(jar); + FileOutputStream out = new FileOutputStream(jarFile); + for (;;) { + int len = in.read(buf); + if (len > 0) { + out.write(buf, 0, len); + } else { + break; + } + } + in.close(); + out.close(); + } + + private static void copyManifest(File mfFrom, File mfTo) throws FileNotFoundException, IOException { + byte[] buf = new byte[4096]; + FileInputStream in = new FileInputStream(mfFrom); + FileOutputStream out = new FileOutputStream(mfTo); + for (;;) { + int len = in.read(buf); + if (len > 0) { + out.write(buf, 0, len); + } else { + break; + } + } + in.close(); + out.close(); + } + + private static void addFileToJar(JarOutputStream out, String entryName, URL file) throws FileNotFoundException, + IOException { + byte[] buf = new byte[4096]; + InputStream in = file.openStream(); + out.putNextEntry(new ZipEntry(entryName)); + for (;;) { + int len = in.read(buf); + if (len > 0) { + out.write(buf, 0, len); + } else { + break; + } + } + in.close(); + out.closeEntry(); + } + + private void writeTarget(PrintStream ps, String pom, Set ids, String[] features) { + ps.println(XML_PI); + ps.println(""); + ps.println(ASL_HEADER); + + ps.println(""); + + if (executionEnvironment != null) { + ps.println(" "); + ps.println(" " + executionEnvironment + ""); + ps.println(" "); + } + + if (useDefaultLocation) { + ps.println(" "); + } else { + ps.println(" "); + } + + // ps.println(""); + ps.println(" "); + ps.println(" "); + for (String id : ids) { + ps.println(" "); + } + ps.println(" "); + ps.println(" "); + if (features != null) { + for (String f : features) { + ps.println(" "); + } + } + ps.println(" "); + if (useDefaultLocation) { + ps.println(" "); + // Not sure why the extra path needs to the plugins folder + ps.println(" "); + ps.println(" "); + } + ps.println(" "); + + ps.println(""); + + } + + private void writePDE35Target(PrintStream ps, String pom, Set ids, String[] features) { + ps.println(XML_PI); + ps.println(""); + ps.println(ASL_HEADER); + + ps.println(""); + + if (executionEnvironment != null) { + ps + .println(" "); + } + + ps.println(""); + if (ids.size() > 0) { + ps.println(" "); + ps.println(" "); + for (String id : ids) { + ps.println(" "); + } + ps.println(" "); + ps.println(" "); + } + + /* + if (useDefaultLocation) { + ps.println(" "); + } + */ + + /* + if (features != null) { + for (String f : features) { + ps.println(" "); + } + } + */ + + ps.println(""); + ps.println(""); + + } + + private static void writePluginXML(PrintStream ps) { + ps.println(XML_PI); + ps.println(""); + ps.println(ASL_HEADER); + ps.println(""); + ps.println(""); + ps.println(""); + ps.println(""); + ps.println(""); + } +} diff --git a/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Extension.java b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Extension.java new file mode 100644 index 0000000000..dd4a9b502c --- /dev/null +++ b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Extension.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.maven.bundle.plugin; + + +public class Extension { + private String groupId; + private String artifactId; + private String version; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String toString() { + return groupId + ":" + artifactId + ":" + version; + } + + public boolean matches(Extension extension) { + return groupId.equals(extension.getGroupId()) && (artifactId == null || artifactId.equals("") + || artifactId.equals("*") || artifactId.equals(extension.getArtifactId())) + && (version == null || version.equals("") || version.equals("*") || version.equals(extension.getVersion())); + } +} diff --git a/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Feature.java b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Feature.java new file mode 100644 index 0000000000..41d3b2dc44 --- /dev/null +++ b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/Feature.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.maven.bundle.plugin; + +public class Feature { + private String groupId; + private String artifactId; + private String version; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String toString() { + return groupId + ":" + artifactId + ":" + version; + } + + public boolean matches(Feature feature) { + return groupId.equals(feature.getGroupId()) && (artifactId == null || artifactId.equals("") + || artifactId.equals("*") || artifactId.equals(feature.getArtifactId())) + && (version == null || version.equals("") || version.equals("*") || version.equals(feature.getVersion())); + } +} -- cgit v1.2.3