View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   * 
19   * Author: Siamak Haschemi
20   * Contact: haschemi@informatik.hu-berlin.de
21   */
22  package net.sourceforge.osgi.deployment.maven.manifest;
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.util.Collection;
27  import java.util.List;
28  import java.util.jar.JarInputStream;
29  import java.util.jar.Manifest;
30  import java.util.regex.Pattern;
31  
32  import net.sourceforge.osgi.deployment.maven.IDeploymentPluginContext;
33  import net.sourceforge.osgi.deployment.maven.container.BundleResource;
34  import net.sourceforge.osgi.deployment.maven.container.DeploymentPackageInfo;
35  import net.sourceforge.osgi.deployment.maven.container.ProcessedResource;
36  
37  import org.apache.maven.model.License;
38  import org.apache.maven.project.MavenProject;
39  
40  
41  /**
42   * This class represents the manifest of a Deployment-Package. It creates the various headers using the {@link IDeploymentPluginContext}. The
43   * Deployment-Package manifest is an ordered manifest, because the order and empty lines are part of the semantics.
44   * 
45   * @author Siamak Haschemi, haschemi@informatik.hu-berlin.de
46   */
47  public class DeploymentPackageManifest extends OrderedManifest {
48    /** Extra header in the manifest file. */
49    public static final String CREATED_BY = "Created-By";
50    /** Extra header in the manifest file. */
51    public static final String TOOL = "Tool";
52    /** Extra header in the manifest file. */
53    public static final String CREATED_AT = "Created-At";
54  
55    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
56    public static final String DP_MANIFESTVERSION = "DeploymentPackage-ManifestVersion";
57  
58    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
59    public static final String DP_CONTENT_TYPE = "application/vnd.osgi.dp";
60  
61    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
62    public static final String DP_SYMBOLICNAME = "DeploymentPackage-SymbolicName";
63  
64    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
65    public static final String DP_VERSION = "DeploymentPackage-Version";
66  
67    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
68    public static final String DP_COPYRIGHT = "DeploymentPackage-Copyright";
69  
70    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
71    public static final String DP_CONTACTADDRESS = "DeploymentPackage-ContactAddress";
72  
73    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
74    public static final String DP_DESCRIPTION = "DeploymentPackage-Description";
75  
76    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
77    public static final String DP_DOCURL = "DeploymentPackage-DocURL";
78  
79    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
80    public static final String DP_VENDOR = "DeploymentPackage-Vendor";
81  
82    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
83    public static final String DP_LICENSE = "DeploymentPackage-License";
84  
85    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
86    public static final String DP_FIXPACK = "DeploymentPackage-FixPack";
87  
88    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
89    public static final String DP_CUSTOMIZER = "DeploymentPackage-Customizer";
90  
91    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
92    public static final String DP_NAME = "Name";
93  
94    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
95    public static final String DP_SHA1_DIGEST = "SHA1-Digest";
96  
97    /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
98    public static final String DP_RESOURCE_PROCESSOR = "Resource-Processor";
99  
100   /** Constant from the Deployment Admin Specification in the OSGi Platform Release 4, Version 4.1. */
101   public static final Pattern PATH_NAME_PATTERN = Pattern.compile("[A-Za-z0-9_\\.-]+");
102 
103   /** Constant from the OSGi Platform Release 4, Version 4.1. */
104   public static final Pattern UNIQUE_NAME_PATTERN = Pattern.compile(".+(\\..+)*");
105 
106   /**
107    * Manifest header (named "Bundle-Version") identifying the bundle's version.
108    */
109   public static final String BUNDLE_VERSION = "Bundle-Version";
110 
111   /**
112    * Manifest header (named "Bundle-SymbolicName") identifying the bundle's symbolic name.
113    */
114   public static final String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName";
115 
116   /**
117    * Manifest header (named "Bundle-Localization") identifying the base name of the bundle's localization entries.
118    */
119   public static final String BUNDLE_LOCALIZATION = "Bundle-Localization";
120 
121   /**
122    * @param p_context
123    *          the context
124    */
125   public DeploymentPackageManifest(final IDeploymentPluginContext p_context) {
126 
127     try {
128       writeGlobalSection(p_context);
129       writeNameSection(p_context);
130     } catch (final ManifestException e) {
131       throw new ManifestException("The creation of the Deploymant-Package Manifest file failed", e);
132     }
133   }
134 
135   private void writeNameSection(final IDeploymentPluginContext p_context) {
136     writeBundleResources(p_context);
137     writeLocalizationResources(p_context);
138     writeProcessedResources(p_context);
139   }
140 
141   private void writeGlobalSection(final IDeploymentPluginContext p_context) {
142     // The manifest version of the overall jar manifest
143     add(MANIFEST_VERSION, "1.0");
144 
145     writeExtraData(p_context);
146 
147     // The deployment version in the 4.1 Spec.
148     add(DP_MANIFESTVERSION, "1");
149     add(CONTENT_TYPE, DP_CONTENT_TYPE);
150 
151     final DeploymentPackageInfo deploymentPackageInfo = p_context.getDeploymentPackageInfo();
152     final MavenProject project = p_context.getProject();
153 
154     writesymlobicName(deploymentPackageInfo, project);
155     writeVersion(deploymentPackageInfo, project);
156     writeFixPack(deploymentPackageInfo);
157     writeDescription(deploymentPackageInfo, project);
158     writeLicense(project);
159     writeVendor(deploymentPackageInfo, project);
160     writeCopyright(deploymentPackageInfo);
161     writeContactAdress(deploymentPackageInfo);
162     writeDocURL(deploymentPackageInfo);
163     writeBundleLocalization(deploymentPackageInfo);
164   }
165 
166   private void writeProcessedResources(final IDeploymentPluginContext p_context) {
167     final DeploymentPackageInfo deploymentPackageInfo = p_context.getDeploymentPackageInfo();
168 
169     for (final ProcessedResource processedResource : deploymentPackageInfo.getProcessedResources()) {
170       final String resourceId = processedResource.getResourceId();
171 
172       if (!isResourceId(resourceId)) {
173         throw new ManifestException("\"" + resourceId + "\" is not a valid path name as specified in OSGi 114.3.2 Service Compendium");
174       }
175 
176       add(OrderedManifest.EMPTY_LINE);
177       add(DP_NAME, resourceId);
178       add(DP_RESOURCE_PROCESSOR, processedResource.getProcessor());
179     }
180   }
181 
182   private void writeLocalizationResources(final IDeploymentPluginContext p_context) {
183     final DeploymentPackageInfo deploymentPackageInfo = p_context.getDeploymentPackageInfo();
184 
185     for (final ProcessedResource processedResource : deploymentPackageInfo.getLocalizationResources()) {
186       final String resourceId = processedResource.getResourceId();
187 
188       if (!isResourceId(resourceId)) {
189         throw new ManifestException("\"" + resourceId + "\" is not a valid path name as specified in OSGi 114.3.2 Service Compendium");
190       }
191 
192       add(OrderedManifest.EMPTY_LINE);
193       add(DP_NAME, resourceId);
194       add(DP_RESOURCE_PROCESSOR, processedResource.getProcessor());
195     }
196   }
197 
198   private void writeBundleResources(final IDeploymentPluginContext p_context) {
199     final DeploymentPackageInfo deploymentPackageInfo = p_context.getDeploymentPackageInfo();
200 
201     for (final BundleResource bundleResource : deploymentPackageInfo.getBundleResources()) {
202       try {
203 
204         // Get some info from the bundle jar and add them to the manifest
205         final File resolvedFile = bundleResource.getResolvedFile();
206         final JarInputStream jis = new JarInputStream(new FileInputStream(resolvedFile));
207         final Manifest bundleManifest = jis.getManifest();
208         final String bundleVersion = bundleManifest.getMainAttributes().getValue(BUNDLE_VERSION);
209         final String bundleSymbolicName = bundleManifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME);
210         final String resourceId = bundleResource.getResourceId();
211 
212         if (!isUniqueName(bundleSymbolicName)) {
213           throw new ManifestException("\"" + bundleSymbolicName + "\" is not a valid unique-name as specified in OSGi 1.3.2 Core");
214         }
215 
216         if (!isVersion(bundleVersion)) {
217           throw new ManifestException("\"" + bundleVersion + "\" is not a valid version as specified in OSGi 3.2.4 Core");
218         }
219 
220         if (!isResourceId(resourceId)) {
221           throw new ManifestException("\"" + resourceId + "\" is not a valid path name as specified in OSGi 114.3.2 Service Compendium");
222         }
223 
224         add(OrderedManifest.EMPTY_LINE);
225         add(DP_NAME, resourceId);
226         if (bundleResource.isCustomizer()) {
227           add(DP_CUSTOMIZER, "true");
228         }
229         add(BUNDLE_SYMBOLICNAME, bundleSymbolicName);
230         add(BUNDLE_VERSION, bundleVersion);
231       } catch (final Exception e) {
232         throw new ManifestException("Error while processing bundle resource " + bundleResource, e);
233       }
234     }
235   }
236 
237   private void writeBundleLocalization(final DeploymentPackageInfo p_deploymentPackageInfo) {
238     // Write the Bundle-Localization
239     if (isNotEmpty(p_deploymentPackageInfo.getBundleLocalization())) {
240       add(BUNDLE_LOCALIZATION, p_deploymentPackageInfo.getBundleLocalization());
241     }
242   }
243 
244   private void writeDocURL(final DeploymentPackageInfo p_deploymentPackageInfo) {
245     // Write the DeploymentPackage-DocURL
246     if (isNotEmpty(p_deploymentPackageInfo.getDocURL())) {
247       add(DP_DOCURL, p_deploymentPackageInfo.getDocURL());
248     }
249   }
250 
251   private void writeContactAdress(final DeploymentPackageInfo p_deploymentPackageInfo) {
252     // Write the DeploymentPackage-ContactAdress
253     if (isNotEmpty(p_deploymentPackageInfo.getContactAddress())) {
254       add(DP_CONTACTADDRESS, p_deploymentPackageInfo.getContactAddress());
255     }
256   }
257 
258   private void writeCopyright(final DeploymentPackageInfo p_deploymentPackageInfo) {
259     // Write the DeploymentPackage-Copyright
260     if (isNotEmpty(p_deploymentPackageInfo.getCopyright())) {
261       add(DP_COPYRIGHT, p_deploymentPackageInfo.getCopyright());
262     }
263   }
264 
265   private void writeVendor(final DeploymentPackageInfo p_deploymentPackageInfo, final MavenProject p_project) {
266     // Write the DeploymentPackage-Vendor
267     String vendor = null;
268     if (isNotEmpty(p_deploymentPackageInfo.getVendor())) {
269       vendor = p_deploymentPackageInfo.getVendor();
270     } else if (p_project.getOrganization() != null) {
271       vendor = p_project.getOrganization().getName();
272     }
273     if (isNotEmpty(vendor)) {
274       header(DP_VENDOR, vendor);
275     }
276   }
277 
278   private void writeLicense(final MavenProject p_project) {
279     // Write the DeploymentPackage-License
280     final String licenseText = printLicenses(p_project.getLicenses());
281     if (isNotEmpty(licenseText)) {
282       header(DP_LICENSE, licenseText);
283     }
284   }
285 
286   private void writeDescription(final DeploymentPackageInfo p_deploymentPackageInfo, final MavenProject p_project) {
287     // Write the DeploymentPackage-Description
288     String description = null;
289     if (isNotEmpty(p_deploymentPackageInfo.getDescription())) {
290       description = p_deploymentPackageInfo.getDescription();
291     } else {
292       description = p_project.getDescription();
293     }
294     if (description != null) {
295       header(DP_DESCRIPTION, description);
296     }
297   }
298 
299   private void writeFixPack(final DeploymentPackageInfo p_deploymentPackageInfo) {
300     final String fixPack = p_deploymentPackageInfo.getFixPack();
301     if (isNotEmpty(fixPack)) {
302       if (!isVersionRange(fixPack)) {
303         throw new ManifestException("\"" + fixPack + "\" is not a valid version range as specified in OSGi 3.2.5 Core");
304       }
305       add(DP_FIXPACK, fixPack);
306     }
307   }
308 
309   private void writeVersion(final DeploymentPackageInfo p_deploymentPackageInfo, final MavenProject p_project) {
310     // Write the DeploymentPackage-Version
311     String version = null;
312     if (isNotEmpty(p_deploymentPackageInfo.getVersion())) {
313       version = p_deploymentPackageInfo.getVersion();
314     } else {
315       String pomVersion = p_project.getVersion();
316       // Make the POM version OSGi conform
317       if (!isVersion(pomVersion)) {
318         pomVersion = pomVersion.replaceAll("-", "\\.");
319       }
320       version = pomVersion;
321     }
322     if (!isVersion(version)) {
323       throw new ManifestException("\"" + version + "\" is not a valid version as specified in OSGi 3.2.4 Core");
324     }
325     add(DP_VERSION, version);
326   }
327 
328   private void writesymlobicName(final DeploymentPackageInfo p_deploymentPackageInfo, final MavenProject p_project) {
329     // Write the DeploymentPackage-SymbolicName
330     String symbolicName = null;
331     if (p_deploymentPackageInfo.getSymbolicName() != null && p_deploymentPackageInfo.getSymbolicName().trim().length() > 0) {
332       symbolicName = p_deploymentPackageInfo.getSymbolicName();
333     } else {
334       symbolicName = p_project.getGroupId() + "." + p_project.getArtifactId();
335     }
336     if (!isUniqueName(symbolicName)) {
337       throw new ManifestException("\"" + symbolicName + "\" is not a valid unique-name as specified in OSGi 1.3.2 Core");
338     }
339     add(DP_SYMBOLICNAME, symbolicName);
340   }
341 
342   private void writeExtraData(final IDeploymentPluginContext p_context) {
343     // Write some extra data
344     if (p_context.isWriteExtraData()) {
345       add(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")");
346       add(TOOL, p_context.getPluginName() + " " + p_context.getPluginVersion());
347       add(CREATED_AT, "" + System.currentTimeMillis());
348     }
349   }
350 
351   private void header(final String p_key, final Object p_value) {
352     if (p_value == null) {
353       return;
354     }
355 
356     if (p_value instanceof Collection && ((Collection< ? >) p_value).isEmpty()) {
357       return;
358     }
359 
360     add(p_key, p_value.toString().replaceAll("[\r\n]", ""));
361   }
362 
363   private String printLicenses(final List< License > p_licenses) {
364     if (p_licenses == null || p_licenses.size() == 0) {
365       return null;
366     }
367     final StringBuffer sb = new StringBuffer();
368     String del = "";
369     for (final License license : p_licenses) {
370       final String url = license.getUrl();
371       if (url == null) {
372         continue;
373       }
374       sb.append(del);
375       sb.append(url);
376       del = ", ";
377     }
378     if (sb.length() == 0) {
379       return null;
380     }
381     return sb.toString();
382   }
383 
384   private static boolean isNotEmpty(final String p_string) {
385     if (p_string != null && p_string.trim().length() > 0) {
386       return true;
387     }
388     return false;
389   }
390 
391   /**
392    * This method checks whether the given version conforms to the OSGi Specification.
393    * 
394    * @param p_versionToCheck
395    *          the version to check
396    * @return <CODE>TRUE></CODE> if the version is valid, else <CODE>FALSE</CODE>
397    */
398   public static boolean isVersion(final String p_versionToCheck) {
399     // TODO implement version check
400     return true;
401   }
402 
403   /**
404    * This method checks whether the given version range conforms to the OSGi Specification.
405    * 
406    * @param p_versionRange
407    *          the version range to check
408    * @return <CODE>TRUE></CODE> if the version range is valid, else <CODE>FALSE</CODE>
409    */
410   public static final boolean isVersionRange(final String p_versionRange) {
411     // TODO implement versionRange check
412     return true;
413   }
414 
415   /**
416    * This method checks whether the given symbolic name conforms to the OSGi Specification of a unique name.
417    * 
418    * @param p_symbolicName
419    *          the symbolic name to check
420    * @return <CODE>TRUE></CODE> if the symbolic name is valid, else <CODE>FALSE</CODE>
421    */
422   public static boolean isUniqueName(final String p_symbolicName) {
423     boolean isUniqueName = true;
424     // DeploymentPackage-SymbolicName ::= unique-name
425     // unique-name ::= identifier ( '.' identifier )*
426     // identifier ::= jletter jletterordigit * jletter ::= <see [5] Lexical Structure Java Language for JavaLetter>
427     // jletterordigit::= <See [5] Lexical Structure Java Language for JavaLetterOrDigit >
428 
429     for (int i = 0; i < p_symbolicName.length(); i++) {
430       final char c = p_symbolicName.charAt(i);
431       if (!(Character.isJavaIdentifierStart(c) || Character.isJavaIdentifierPart(c) || c == '.')) {
432         isUniqueName = false;
433       }
434     }
435 
436     if (isUniqueName && !UNIQUE_NAME_PATTERN.matcher(p_symbolicName).matches()) {
437       isUniqueName = false;
438     }
439 
440     return isUniqueName;
441   }
442 
443   /**
444    * This method checks whether the given resourceId conforms to the OSGi Specification.
445    * 
446    * @param p_resourceId
447    *          the resourceId to check
448    * @return <CODE>TRUE></CODE> if the resourceId is valid, else <CODE>FALSE</CODE>
449    */
450   public static boolean isResourceId(final String p_resourceId) {
451     if (p_resourceId == null) {
452       return false;
453     }
454     if (p_resourceId.contains("/")) {
455 
456       for (final String pathToken : p_resourceId.split("/")) {
457         if (!pathToken.equals("") && !PATH_NAME_PATTERN.matcher(pathToken).matches()) {
458           return false;
459         }
460       }
461     } else {
462       if (!PATH_NAME_PATTERN.matcher(p_resourceId).matches()) {
463         return false;
464       }
465     }
466     return true;
467   }
468 
469 }