001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.osgi;
018    
019    import org.osgi.framework.Bundle;
020    import org.osgi.framework.BundleContext;
021    import org.osgi.framework.Constants;
022    
023    import java.io.ByteArrayInputStream;
024    import java.io.ByteArrayOutputStream;
025    import java.io.File;
026    import java.io.FileInputStream;
027    import java.io.IOException;
028    import java.util.Collections;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import java.util.Set;
032    import java.util.StringTokenizer;
033    import java.util.jar.Attributes;
034    import java.util.jar.JarEntry;
035    import java.util.jar.JarInputStream;
036    import java.util.jar.JarOutputStream;
037    import java.util.jar.Manifest;
038    
039    /**
040     * @author Dain Sundstrom
041     * @version $Id$
042     * @since 2.0
043     */
044    public class MavenBundleManager {
045        private final BundleContext bundleContext;
046        private final File localRepository;
047    
048        public MavenBundleManager(BundleContext bundleContext, File localRepository) {
049            this.bundleContext = bundleContext;
050            this.localRepository = localRepository;
051        }
052    
053        public Project loadProject(Artifact artifact) {
054            if (artifact instanceof Project) {
055                return (Project) artifact;
056            } else {
057                return new Project(artifact.getGroupId(),
058                        artifact.getArtifactId(),
059                        artifact.getVersion(),
060                        artifact.getType(),
061                        Collections.EMPTY_SET);
062            }
063        }
064    
065        public Project loadProject(String groupId, String artifactId, String version) {
066            return new Project(groupId, artifactId, version, "jar", Collections.EMPTY_SET);
067        }
068    
069        public Bundle installBundle(String groupId, String artifactId, String version) throws Exception {
070            return installBundle(loadProject(groupId, artifactId, version));
071        }
072    
073        public Bundle installBundle(Artifact artifact) throws Exception {
074            String symbolicName = artifact.getGroupId() + "." + artifact.getArtifactId();
075            String bundleVersion = coerceToOsgiVersion(artifact.getVersion());
076    
077            // check if we already loaded this bundle
078            Bundle[] bundles = bundleContext.getBundles();
079            for (int i = 0; i < bundles.length; i++) {
080                Bundle bundle = bundles[i];
081                if (symbolicName.equals(bundle.getSymbolicName()) &&
082                        bundleVersion.equals(bundle.getHeaders().get(Constants.BUNDLE_VERSION))) {
083                    return bundle;
084                }
085            }
086    
087            // load the project object model for this artiface
088            Project project = loadProject(artifact);
089    
090            // build an OSGi manifest for the project
091            Manifest manifest = createOsgiManifest(project);
092    
093            // create a jar in memory for the manifest
094            ByteArrayOutputStream out = new ByteArrayOutputStream();
095            JarOutputStream jarOut = new JarOutputStream(out, manifest);
096            jarOut.close();
097            out.close();
098            ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
099    
100            // install the in memory jar
101            Bundle bundle = bundleContext.installBundle(symbolicName, in);
102    
103            // install bundles for all of the dependencies
104            for (Iterator iterator = project.getDependencies().iterator(); iterator.hasNext();) {
105                Artifact dependency = (Artifact) iterator.next();
106                installBundle(dependency);
107            }
108    
109            return bundle;
110        }
111    
112        public Manifest createOsgiManifest(Project project) throws IOException {
113            String groupId = project.getGroupId();
114            String artifactId = project.getArtifactId();
115            String version = project.getVersion();
116            String jar = groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + project.getJar();
117    
118            StringBuffer requireBundle = new StringBuffer();
119            for (Iterator iterator = project.getDependencies().iterator(); iterator.hasNext();) {
120                Artifact dependency = (Artifact) iterator.next();
121                if (requireBundle.length() > 0) requireBundle.append(',');
122    
123                requireBundle.append(dependency.getGroupId()).append('.').append(dependency.getArtifactId());
124                requireBundle.append(";visibility:=reexport;bundle-version:=").append(coerceToOsgiVersion(dependency.getVersion()));
125    
126            }
127    
128            String jarPath = new File(localRepository, jar).getAbsolutePath();
129            StringBuffer exports = createExportList(jarPath);
130    
131            Manifest manifest = new Manifest();
132            Attributes attributes = manifest.getMainAttributes();
133            attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
134            attributes.putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
135            attributes.putValue(Constants.BUNDLE_VENDOR, groupId);
136            attributes.putValue(Constants.BUNDLE_NAME, artifactId);
137            attributes.putValue(Constants.BUNDLE_VERSION, coerceToOsgiVersion(version));
138            attributes.putValue(Constants.BUNDLE_SYMBOLICNAME, groupId + "." + artifactId);
139            attributes.putValue("Eclipse-AutoStart", "true");
140    
141    
142            attributes.putValue(Constants.BUNDLE_CLASSPATH, ".,external:" + jarPath);
143    
144            attributes.putValue(Constants.EXPORT_PACKAGE, exports.toString());
145            if (requireBundle != null && requireBundle.length() > 0) {
146                attributes.putValue(Constants.REQUIRE_BUNDLE, requireBundle.toString());
147            }
148    
149            return manifest;
150        }
151    
152        private static String coerceToOsgiVersion(String version) {
153            int partsFound = 0;
154            String[] versionParts = new String[] { "0", "0", "0"};
155            StringBuffer qualifier = new StringBuffer();
156            for (StringTokenizer stringTokenizer = new StringTokenizer(version, ".-"); stringTokenizer.hasMoreTokens();) {
157                String part = stringTokenizer.nextToken();
158                if (partsFound < 4) {
159                    try {
160                        Integer.parseInt(part);
161                        versionParts[partsFound++] = part;
162                    } catch (NumberFormatException e) {
163                        partsFound = 4;
164                        qualifier.append(coerceToOsgiQualifier(part));
165                    }
166                } else {
167                    if (qualifier.length() > 0) qualifier.append("_");
168                    qualifier.append(coerceToOsgiQualifier(part));
169                }
170            }
171    
172            StringBuffer osgiVersion = new StringBuffer();
173            osgiVersion.append(versionParts[0]).append(".").append(versionParts[1]).append(".").append(versionParts[2]);
174            if (qualifier.length() > 0) {
175                osgiVersion.append(".").append(qualifier);
176            }
177            return osgiVersion.toString();
178        }
179    
180        private static String coerceToOsgiQualifier(String qualifier) {
181            char[] chars = qualifier.toCharArray();
182            for (int i = 0; i < chars.length; i++) {
183                char c = chars[i];
184                if (!Character.isLetterOrDigit(c) && c != '_' && c != '-') {
185                    chars[i] = '_';
186                }
187            }
188            return new String(chars);
189        }
190    
191    
192        private static StringBuffer createExportList(String jarPath) throws IOException {
193            Set packages = new HashSet();
194            FileInputStream in = null;
195            try {
196                in = new FileInputStream(jarPath);
197                JarInputStream jarIn = new JarInputStream(in);
198                for (JarEntry jarEntry = jarIn.getNextJarEntry(); jarEntry != null; jarEntry = jarIn.getNextJarEntry()) {
199                    String packageName = jarEntry.getName();
200                    if (!jarEntry.isDirectory()) {
201                        int index = packageName.lastIndexOf("/");
202                        // we can't export the default package
203                        if (index > 0) {
204                            packageName = packageName.substring(0, index);
205                            if (!packageName.equals("META-INF")) {
206                                packageName = packageName.replace('/', '.');
207                                packages.add(packageName);
208                            }
209                        }
210                    }
211                }
212            } finally {
213                if (in != null) {
214                    try {
215                        in.close();
216                    } catch (IOException e) {
217                    }
218                }
219            }
220    
221            StringBuffer exports = new StringBuffer();
222            for (Iterator iterator = packages.iterator(); iterator.hasNext();) {
223                String packageName = (String) iterator.next();
224                if (exports.length() > 0) exports.append(";");
225                exports.append(packageName);
226            }
227            return exports;
228        }
229    }