From 845740951bf395da58f984eaf3fea6f8187b922b Mon Sep 17 00:00:00 2001 From: rfeng Date: Tue, 5 Jan 2010 18:00:36 +0000 Subject: Add new goal so that we can generate a single OSGi bundle from the modules git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@896154 13f79535-47bb-0310-9956-ffa450edef68 --- .../bundle/plugin/AggregatedBundleActivator.java | 56 ++++ .../maven/bundle/plugin/BundleAggregatorMojo.java | 263 ++++++++++++++++ .../tuscany/maven/bundle/plugin/HeaderParser.java | 331 +++++++++++++++++++++ 3 files changed, 650 insertions(+) create mode 100644 maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java create mode 100644 maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java create mode 100644 maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java (limited to 'maven-plugins') diff --git a/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java new file mode 100644 index 0000000000..64999cb309 --- /dev/null +++ b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/AggregatedBundleActivator.java @@ -0,0 +1,56 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * A bundle activator that delegates to others + */ +public class AggregatedBundleActivator implements BundleActivator { + public static final String BUNDLE_ACTIVATOR_LIST = "Tuscany-Bundle-Activator-List"; + private List activators = new ArrayList(); + + public void start(BundleContext context) throws Exception { + String list = (String)context.getBundle().getHeaders().get(BUNDLE_ACTIVATOR_LIST); + if (list == null) { + return; + } + for (String cls : list.split(",")) { + Object i = context.getBundle().loadClass(cls).newInstance(); + if (i instanceof BundleActivator) { + ((BundleActivator)i).start(context); + activators.add((BundleActivator)i); + } + } + } + + public void stop(BundleContext context) throws Exception { + for (BundleActivator a : activators) { + a.stop(context); + } + + } + +} diff --git a/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java new file mode 100644 index 0000000000..c0952ad222 --- /dev/null +++ b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/BundleAggregatorMojo.java @@ -0,0 +1,263 @@ +/* + * 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.AggregatedBundleActivator.BUNDLE_ACTIVATOR_LIST; +import static org.apache.tuscany.maven.bundle.plugin.HeaderParser.merge; +import static org.osgi.framework.Constants.BUNDLE_ACTIVATOR; +import static org.osgi.framework.Constants.BUNDLE_CLASSPATH; + +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.util.ArrayList; +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.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.apache.tuscany.maven.bundle.plugin.HeaderParser.HeaderClause; + +/** + * @version $Rev$ $Date$ + * @goal aggregate-modules + * @phase process-resources + * @requiresDependencyResolution test + * @description Generate an aggregated bundle that contains all the modules and 3rd party jars + */ +public class BundleAggregatorMojo extends AbstractMojo { + /** + * The project to create a distribution for. + * + * @parameter expression="${project}" + * @required + * @readonly + */ + private MavenProject project; + + /** + * Root directory. + * + * @parameter expression="${project.build.directory}/modules" + */ + private File rootDirectory; + + /** + * Aggregated bundle + * + * @parameter expression="${project.build.directory}/singlebundle/tuscany-bundle.jar" + */ + private File targetBundleFile; + + /** + * @parameter default-value== "org.apache.tuscany.sca.bundle"; + */ + private String bundleName = "org.apache.tuscany.sca.bundle"; + + /** + * @parameter default-value== "2.0.0"; + */ + private String bundleVersion = "2.0.0"; + + // private static final Logger logger = Logger.getLogger(BundleAggregatorMojo.class.getName()); + + public void aggregateBundles(File root, File targetBundleFile) throws Exception { + Log log = getLog(); + if (!root.isDirectory()) { + log.warn(root + " is not a directory"); + return; + } + targetBundleFile.getParentFile().mkdirs(); + Set jarFiles = new HashSet(); + List manifests = new ArrayList(); + for (File child : root.listFiles()) { + try { + Manifest manifest = null; + if (child.isDirectory()) { + File mf = new File(child, "META-INF/MANIFEST.MF"); + if (mf.isFile()) { + FileInputStream is = new FileInputStream(mf); + manifest = new Manifest(is); + is.close(); + if (manifest != null) { + String classpath = manifest.getMainAttributes().getValue("Bundle-ClassPath"); + if (classpath != null) { + for (HeaderClause clause : HeaderParser.parse(classpath)) { + if (clause.getValue().equals(".")) { + continue; + } else { + jarFiles.add(new File(child, clause.getValue())); + } + } + } else { + // + } + } + } + } else if (child.getName().endsWith(".jar")) { + JarFile jar = new JarFile(child); + manifest = jar.getManifest(); + jar.close(); + if (manifest != null) { + String id = manifest.getMainAttributes().getValue("Bundle-SymbolicName"); + if (id != null && (id.startsWith("org.eclipse.") || id + .startsWith("org.apache.tuscany.sca.gateway"))) { + manifest = null; + } else { + jarFiles.add(child); + } + } + } + if (manifest == null) { + continue; + } + + log.debug("Bundle file: " + child); + manifests.add(manifest); + } catch (Exception e) { + throw e; + } + } + Manifest merged = new Manifest(); + Attributes attributes = merged.getMainAttributes(); + attributes.putValue("Manifest-Version", "1.0"); + attributes.putValue("Bundle-ManifestVersion", "2"); + attributes.putValue("Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt"); + attributes.putValue("Bundle-DocURL", "http://www.apache.org/"); + attributes.putValue("Bundle-RequiredExecutionEnvironment", "J2SE-1.5,JavaSE-1.6"); + attributes.putValue("Bundle-Vendor", "The Apache Software Foundation"); + attributes.putValue("Bundle-Version", bundleVersion); + attributes.putValue("Bundle-SymbolicName", bundleName); + attributes.putValue("SCA-Version", "1.1"); + attributes.putValue("Bundle-Name", bundleName); + attributes.putValue("Bundle-ActivationPolicy", "lazy"); + for (Manifest mf : manifests) { + for (Map.Entry e : mf.getMainAttributes().entrySet()) { + Attributes.Name key = (Attributes.Name)e.getKey(); + String name = key.toString(); + String oldValue = attributes.getValue(name); + String value = (String)e.getValue(); + if (name.equals("Export-Package") || name.equals("Import-Package") + || name.equals("Require-Bundle") + || name.equals("DynamicImport-Package") + || name.equals("Bundle-ClassPath") + || name.equals("Private-Package") + || name.equals("Bundle-Description")) { + attributes.putValue(name, merge(oldValue, value)); + } else if (name.equals(BUNDLE_ACTIVATOR)) { + oldValue = attributes.getValue(BUNDLE_ACTIVATOR_LIST); + attributes.putValue(BUNDLE_ACTIVATOR_LIST, merge(oldValue, value)); + } else if (name.equals("Main-Class") || name.startsWith("Eclipse-") || name.startsWith("Bundle-")) { + // Ignore + } else { + // Ignore + // attributes.putValue(name, value); + } + } + } + log.info("Generating " + targetBundleFile); + attributes.putValue(BUNDLE_ACTIVATOR, AggregatedBundleActivator.class.getName()); + String bundleClassPath = attributes.getValue(BUNDLE_CLASSPATH); + bundleClassPath = merge(bundleClassPath, "."); + for (File f : jarFiles) { + bundleClassPath = merge(bundleClassPath, f.getName()); + } + attributes.putValue(BUNDLE_CLASSPATH, bundleClassPath); + + FileOutputStream fos = new FileOutputStream(targetBundleFile); + JarOutputStream bundle = new JarOutputStream(fos, merged); + + for (File file : jarFiles) { + log.info("Adding " + file); + addEntry(bundle, file.getName(), file); + } + + String classFile = AggregatedBundleActivator.class.getName().replace(".", "/") + ".class"; + InputStream classStream = BundleAggregatorMojo.class.getClassLoader().getResourceAsStream(classFile); + addEntry(bundle, classFile, classStream); + bundle.close(); + } + + private static void addDir(JarOutputStream jos, File root, File dir) throws IOException, FileNotFoundException { + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + addDir(jos, root, file); + } else if (file.isFile()) { + // getLog().info(file.toString()); + String uri = root.toURI().relativize(file.toURI()).toString(); + if ("META-INF/MANIFEST.MF".equals(uri)) { + continue; + } + addEntry(jos, uri, file); + } + } + } + + private static void addEntry(JarOutputStream jos, String name, File file) throws IOException { + FileInputStream in = new FileInputStream(file); + addEntry(jos, name, in); + } + + private static final byte[] buf = new byte[4096]; + + private static void addEntry(JarOutputStream jos, String name, InputStream in) throws IOException { + ZipEntry entry = new ZipEntry(name); + jos.putNextEntry(entry); + for (;;) { + int len = in.read(buf); + if (len > 0) { + jos.write(buf, 0, len); + } else { + break; + } + } + in.close(); + jos.closeEntry(); + } + + private static void generateJar(File root, File jar, Manifest mf) throws IOException { + FileOutputStream fos = new FileOutputStream(jar); + JarOutputStream jos = mf != null ? new JarOutputStream(fos, mf) : new JarOutputStream(fos); + addDir(jos, root, root); + jos.close(); + } + + public void execute() throws MojoExecutionException, MojoFailureException { + try { + aggregateBundles(rootDirectory, targetBundleFile); + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + } +} diff --git a/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java new file mode 100644 index 0000000000..c49261e3ff --- /dev/null +++ b/maven-plugins/trunk/maven-bundle-plugin/src/main/java/org/apache/tuscany/maven/bundle/plugin/HeaderParser.java @@ -0,0 +1,331 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.namespace.QName; + +/** + * Parser for the service descriptors. The syntax of the service declaration is similar with the OSGi + * headers with the following exceptions: + *
    + *
  • Tuscany uses , and ; as the separator for attibutes + *
  • Tuscany + */ +public class HeaderParser { + + private static final String PATH_SEPARATOR = ","; // OSGi style + // private static final String PATH_SEPARATOR = "|"; + + private static final String SEGMENT_SEPARATOR = ";"; // OSGi style + // private static final String SEGMENT_SEPARATOR = ";,"; + + private static final String ATTRIBUTE_SEPARATOR = "="; + private static final String DIRECTIVE_SEPARATOR = ":="; + + private static final char QUOTE_CHAR = '"'; + private static final String QUOTE = "\""; + + // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2, + // path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 + public static List parse(String header) { + + if (header != null) { + if (header.length() == 0) { + throw new IllegalArgumentException("A header cannot be an empty string."); + } + + String[] clauseStrings = parseDelimitedString(header, PATH_SEPARATOR); + + List completeList = new ArrayList(); + for (int i = 0; (clauseStrings != null) && (i < clauseStrings.length); i++) { + completeList.add(parseClause(clauseStrings[i])); + } + + return completeList; + } + + return null; + + } + + // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 + private static HeaderClause parseClause(String clauseString) throws IllegalArgumentException { + // Break string into semi-colon delimited pieces. + String[] pieces = parseDelimitedString(clauseString, SEGMENT_SEPARATOR); + + // Count the number of different paths; paths + // will not have an '=' in their string. This assumes + // that paths come first, before directives and + // attributes. + int pathCount = 0; + for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++) { + if (pieces[pieceIdx].indexOf('=') >= 0) { + break; + } + pathCount++; + } + + // Create an array of paths. + String[] paths = new String[pathCount]; + System.arraycopy(pieces, 0, paths, 0, pathCount); + + // Parse the directives/attributes. + Map dirsMap = new HashMap(); + Map attrsMap = new HashMap(); + int idx = -1; + String sep = null; + for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++) { + // Check if it is a directive. + if ((idx = pieces[pieceIdx].indexOf(DIRECTIVE_SEPARATOR)) >= 0) { + sep = DIRECTIVE_SEPARATOR; + } + // Check if it is an attribute. + else if ((idx = pieces[pieceIdx].indexOf(ATTRIBUTE_SEPARATOR)) >= 0) { + sep = ATTRIBUTE_SEPARATOR; + } + // It is an error. + else { + throw new IllegalArgumentException("Not a directive/attribute: " + clauseString); + } + + String key = pieces[pieceIdx].substring(0, idx).trim(); + String value = pieces[pieceIdx].substring(idx + sep.length()).trim(); + + // Remove quotes, if value is quoted. + if (value.startsWith(QUOTE) && value.endsWith(QUOTE)) { + value = value.substring(1, value.length() - 1); + } + + // Save the directive/attribute in the appropriate array. + if (sep.equals(DIRECTIVE_SEPARATOR)) { + // Check for duplicates. + if (dirsMap.get(key) != null) { + throw new IllegalArgumentException("Duplicate directive: " + key); + } + dirsMap.put(key, value); + } else { + // Check for duplicates. + if (attrsMap.get(key) != null) { + throw new IllegalArgumentException("Duplicate attribute: " + key); + } + attrsMap.put(key, value); + } + } + + StringBuffer path = new StringBuffer(); + for (int i = 0; i < paths.length; i++) { + path.append(paths[i]); + if (i != paths.length - 1) { + path.append(';'); + } + } + + HeaderClause descriptor = new HeaderClause(); + descriptor.text = clauseString; + descriptor.value = path.toString(); + descriptor.valueComponents = paths; + descriptor.attributes = attrsMap; + descriptor.directives = dirsMap; + + return descriptor; + } + + /** + * Parses delimited string and returns an array containing the tokens. This + * parser obeys quotes, so the delimiter character will be ignored if it is + * inside of a quote. This method assumes that the quote character is not + * included in the set of delimiter characters. + * @param value the delimited string to parse. + * @param delim the characters delimiting the tokens. + * @return an array of string tokens or null if there were no tokens. + **/ + private static String[] parseDelimitedString(String value, String delim) { + if (value == null) { + value = ""; + } + + List list = new ArrayList(); + + int CHAR = 1; + int DELIMITER = 2; + int STARTQUOTE = 4; + int ENDQUOTE = 8; + + StringBuffer sb = new StringBuffer(); + + int expecting = (CHAR | DELIMITER | STARTQUOTE); + + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + + boolean isDelimiter = (delim.indexOf(c) >= 0); + boolean isQuote = (c == QUOTE_CHAR); + + if (isDelimiter && ((expecting & DELIMITER) > 0)) { + list.add(sb.toString().trim()); + sb.delete(0, sb.length()); + expecting = (CHAR | DELIMITER | STARTQUOTE); + } else if (isQuote && ((expecting & STARTQUOTE) > 0)) { + sb.append(c); + expecting = CHAR | ENDQUOTE; + } else if (isQuote && ((expecting & ENDQUOTE) > 0)) { + sb.append(c); + expecting = (CHAR | STARTQUOTE | DELIMITER); + } else if ((expecting & CHAR) > 0) { + sb.append(c); + } else { + throw new IllegalArgumentException("Invalid delimited string: " + value); + } + } + + if (sb.length() > 0) { + list.add(sb.toString().trim()); + } + + return (String[])list.toArray(new String[list.size()]); + } + + public static class HeaderClause { + private String text; + private String value; + private String[] valueComponents; + private Map attributes; + private Map directives; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String[] getValueComponents() { + return valueComponents; + } + + public void setValueComponents(String[] valueComponents) { + this.valueComponents = valueComponents; + } + + public Map getAttributes() { + return attributes; + } + + public Map getDirectives() { + return directives; + } + + public String toString() { + String text = null; + if (text == null) { + StringBuffer buf = new StringBuffer(); + if (value == null) { + int start = buf.length(); + for (int i = 0; i < valueComponents.length; i++) { + if (i != valueComponents.length - 1) { + buf.append(valueComponents[i]).append(';'); + } else { + buf.append(valueComponents[i]); + } + } + int end = buf.length(); + if (end > start) { + value = buf.substring(start, end); + } + } + buf.append(value); + for (Map.Entry e : attributes.entrySet()) { + buf.append(';').append(e.getKey()).append("=\"").append(e.getValue()).append("\""); + } + for (Map.Entry e : directives.entrySet()) { + buf.append(';').append(e.getKey()).append(":=\"").append(e.getValue()).append("\""); + } + text = buf.toString(); + } + return text; + } + + } + + /** + * Returns a QName object from a QName expressed as {ns}name + * or ns#name. + * + * @param qname + * @return + */ + public static QName getQName(String qname) { + if (qname == null) { + return null; + } + qname = qname.trim(); + if (qname.startsWith("{")) { + int h = qname.indexOf('}'); + if (h != -1) { + return new QName(qname.substring(1, h), qname.substring(h + 1)); + } + } else { + int h = qname.indexOf('#'); + if (h != -1) { + return new QName(qname.substring(0, h), qname.substring(h + 1)); + } + } + return new QName(qname); + } + + public static String toHeader(List descriptors) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < descriptors.size(); i++) { + HeaderClause descriptor = descriptors.get(i); + buf.append(descriptor); + if (i != descriptors.size() - 1) { + buf.append(','); + } + } + return buf.toString(); + } + + public static String merge(String... headers) { + List merged = new ArrayList(); + for (String header : headers) { + if (header == null || header.length() == 0) { + continue; + } + List descriptors = parse(header); + merged.addAll(descriptors); + } + Set values = new HashSet(); + for (Iterator i = merged.iterator(); i.hasNext();) { + if (!values.add(i.next().getValue())) { + i.remove(); + } + } + return toHeader(merged); + } +} -- cgit v1.2.3