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 }