View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009 Intalio, Inc.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // Contributors:
13  //    Hugues Malphettes - initial API and implementation
14  // ========================================================================
15  package org.eclipse.jetty.osgi.boot.warurl.internal;
16  
17  import java.net.URL;
18  import java.util.ArrayList;
19  import java.util.Enumeration;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map.Entry;
23  import java.util.jar.Attributes;
24  import java.util.jar.JarEntry;
25  import java.util.jar.JarFile;
26  import java.util.jar.Manifest;
27  
28  import org.eclipse.jetty.util.MultiMap;
29  import org.eclipse.jetty.util.UrlEncoded;
30  import org.osgi.framework.Constants;
31  
32  public class WarBundleManifestGenerator
33  {
34      /** missing version in the url and in the manifest
35       * use this one. */
36      private static final String MISSING_VERSION = "0.0.1.unknown";
37      private static final String MISSING_MANIFEST_VERSION = "2";
38      
39      public static Manifest createBundleManifest(Manifest originalManifest, URL url, JarFile jarFile)
40      {
41          Manifest res = new Manifest();
42          res.getMainAttributes().putAll(
43                  createBundleManifest(originalManifest.getMainAttributes(),
44                          url.toString(), jarFile));
45          return res;
46      }
47      
48      
49      private static Attributes createBundleManifest(Attributes originalManifest, String url, JarFile jarFile)
50      {
51          HashMap<String, String> res = new HashMap<String, String>();
52          for (Entry<Object, Object> entries : originalManifest.entrySet())
53          {
54              res.put(entries.getKey().toString(),String.valueOf(entries.getValue()));
55          }
56          MultiMap<String> params = parseQueryString(url);
57          //follow RFC66 documentation:
58          //#1 Bundle-Version
59          String version = params.getString(Constants.BUNDLE_VERSION);
60          if (version != null)
61          {
62              res.put(Constants.BUNDLE_VERSION, version);
63          }
64          else
65          {
66              String versionInManifest = (String) res.get(Constants.BUNDLE_VERSION);
67              if (versionInManifest == null)
68              {
69                  res.put(Constants.BUNDLE_VERSION, MISSING_VERSION);
70              }
71          }
72          
73          //#2 Bundle_ManifestVersion
74          String manversion = params.getString(Constants.BUNDLE_MANIFESTVERSION);
75          if (manversion != null)
76          {
77              res.put(Constants.BUNDLE_MANIFESTVERSION, manversion);
78          }
79          else
80          {
81              int manv = 2;
82              try {
83                  String versionInManifest = (String) res.get(Constants.BUNDLE_MANIFESTVERSION);
84                  if (versionInManifest != null)
85                  {
86                      manv = Integer.parseInt(versionInManifest.trim());
87                  }
88              }
89              catch (NumberFormatException nfe)
90              {
91                  
92              }
93              res.put(Constants.BUNDLE_MANIFESTVERSION, String.valueOf( manv < 2 ? 2 : manv ));
94          }
95          
96          //#3 Bundle-SymbolicName
97          String symbname = params.getString(Constants.BUNDLE_SYMBOLICNAME);
98          if (symbname != null)
99          {
100             res.put(Constants.BUNDLE_SYMBOLICNAME, symbname);
101         }
102         else
103         {
104             symbname = (String) res.get(Constants.BUNDLE_SYMBOLICNAME);
105             if (symbname == null)
106             {
107                 //derive the symbolic name from the url.
108                 int lastSlash = url.lastIndexOf('/');
109                 int beforeQueryString = url.indexOf(lastSlash, '?');
110                 if (beforeQueryString == -1)
111                 {
112                     beforeQueryString = url.indexOf(lastSlash, '#');
113                     if (beforeQueryString == -1)
114                     {
115                         beforeQueryString = url.length();
116                     }
117                 }
118                 symbname = url.substring(lastSlash+1, beforeQueryString);
119                 //todo: something better probably.
120                 res.put(Constants.BUNDLE_SYMBOLICNAME, symbname);
121             }
122         }
123         
124         //#4 Bundle-Classpath
125         String extraBundleClasspath = params.getString(Constants.BUNDLE_CLASSPATH);
126         String alreadyBundleClasspath = res.get(Constants.BUNDLE_CLASSPATH);
127         if (alreadyBundleClasspath == null)
128         {
129             StringBuilder bundleClasspath = new StringBuilder();
130             if (jarFile == null || jarFile.getJarEntry("WEB-INF/classes/") != null)
131             {
132                 bundleClasspath.append("WEB-INF/classes");
133             }
134             if (jarFile != null)
135             {
136                 List<String> libs = getJarsInWebInfLib(jarFile);
137                 if (extraBundleClasspath != null)
138                 {
139                     libs.add(extraBundleClasspath);
140                 }
141                 for (String lib : libs)
142                 {
143                     if (bundleClasspath.length() != 0)
144                     {
145                         bundleClasspath.append(",");
146                     }
147                     bundleClasspath.append(lib);
148                 }
149             }
150             alreadyBundleClasspath = bundleClasspath.toString();
151         }
152         
153         //if there is already a manifest and it specifies the Bundle-Classpath.
154         //for now let's trust that one.
155         //please note that the draft of the spec implies that we should be parsing the existing
156         //header and merge it with the missing stuff so this does not follow the spec yet.
157             
158         res.put(Constants.BUNDLE_CLASSPATH,
159             alreadyBundleClasspath + (extraBundleClasspath == null ? "" : "," + extraBundleClasspath ));
160         
161         //#5 Import-Package
162         String extraImportPackage = params.getString(Constants.IMPORT_PACKAGE);
163         String alreadyImportPackage = res.get(Constants.IMPORT_PACKAGE);
164         if (alreadyImportPackage == null)
165         {//The spec does not specify that the jsp imports are optional
166          //kind of nice to have them optional so we can run simple wars in
167          //simple environments.
168             alreadyImportPackage = "javax.servlet; version=\"2.5\"," +
169                     "javax.servlet.http;version=\"2.5\"," +
170                     "javax.el;version=\"1.0\"" +
171                     "javax.jsp;version=\"2.1\";resolution:=optional," +
172                     "javax.jsp.tagext;version=\"2.1\";resolution:=optional";
173             
174         }
175         if (extraImportPackage != null)
176         {   //if there is already a manifest and it specifies the Bundle-Classpath.
177             //for now let's trust that one.
178             //please note that the draft of the spec implies that we should be parsing the existing
179             //header and merge it with the missing stuff so this does not follow the spec yet.
180             
181             res.put(Constants.IMPORT_PACKAGE,
182                     (alreadyImportPackage == null ? "" : alreadyImportPackage + ",") +
183                     extraImportPackage);
184         }
185         
186         //#6 Export-Package
187         String extraExportPackage = params.getString(Constants.EXPORT_PACKAGE);
188         String alreadyExportPackage = res.get(Constants.EXPORT_PACKAGE);
189         if (extraExportPackage != null)
190         {   //if there is already a manifest and it specifies the Bundle-Classpath.
191             //for now let's trust that one.
192             //please note that the draft of the spec implies that we should be parsing the existing
193             //header and merge it with the missing stuff so this does not follow the spec yet.
194             res.put(Constants.EXPORT_PACKAGE,
195                     (alreadyExportPackage == null ? "" : alreadyExportPackage + ",") +
196                     extraImportPackage);
197         }
198         
199         //#7 Web-ContextPath
200         String webContextPath = params.getString("Web-ContextPath");
201         if (webContextPath != null)
202         {
203             res.put("Web-ContextPath", webContextPath);
204         }
205         else
206         {
207             webContextPath = res.get("Web-ContextPath");
208             if (webContextPath == null)
209             {
210                 //we choose to use the symbolic name as the default context path.
211                 if (symbname.endsWith(".war"))
212                 {
213                     webContextPath = "/" + symbname.substring(0, symbname.length()-".war".length());
214                 }
215                 else
216                 {
217                     webContextPath = "/" + symbname;
218                 }
219                 res.put("Web-ContextPath", webContextPath);
220             }
221         }
222         
223         //#8 Web-JSPExtractLocation
224         String jspExtractLocation = params.getString("Web-JSPExtractLocation");
225         if (jspExtractLocation != null)
226         {
227             res.put("Web-JSPExtractLocation", jspExtractLocation);
228         }
229         else
230         {
231             //nothing to do.
232         }
233         Attributes newAttrs = new Attributes();
234         for (Entry<String,String> e : res.entrySet())
235         {
236              newAttrs.putValue(e.getKey(),e.getValue());
237         }
238         return newAttrs;
239     }
240     
241     
242     /**
243      * @return The key values pairs that are in the query string of this url.
244      */
245     private static MultiMap<String> parseQueryString(String url)
246     {
247         MultiMap<String> res = new MultiMap<String>();
248         int questionMarkIndex = url.indexOf('?');
249         if (questionMarkIndex == -1)
250         {
251             return res;
252         }
253         int poundIndex = url.indexOf('#');
254         if (poundIndex == -1)
255         {
256             poundIndex = url.length();
257         }
258         UrlEncoded.decodeUtf8To(url.getBytes(), questionMarkIndex+1,
259                     poundIndex - questionMarkIndex - 1, res);
260         return res;
261     }
262     
263     private static List<String> getJarsInWebInfLib(JarFile jarFile)
264     {
265         List<String> res = new ArrayList<String>();
266         Enumeration<JarEntry> en = jarFile.entries();
267         while (en.hasMoreElements())
268         {
269             JarEntry e = en.nextElement();
270             if (e.getName().startsWith("WEB-INF/lib/") && e.getName().endsWith(".jar"))
271             {
272                 res.add(e.getName());
273             }
274         }
275         return res;
276     }
277     
278     
279 }