View Javadoc

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