View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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;
20  
21  import java.io.File;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.StringTokenizer;
28  import java.util.TreeMap;
29  import java.util.regex.Pattern;
30  
31  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
32  import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
33  import org.eclipse.jetty.util.log.Log;
34  import org.eclipse.jetty.util.log.Logger;
35  import org.eclipse.jetty.util.resource.Resource;
36  import org.eclipse.jetty.util.resource.ResourceCollection;
37  import org.eclipse.jetty.webapp.WebAppContext;
38  import org.eclipse.jetty.webapp.WebInfConfiguration;
39  import org.osgi.framework.Bundle;
40  import org.osgi.framework.FrameworkUtil;
41  
42  
43  
44  /**
45   * OSGiWebInfConfiguration
46   *
47   * Handle adding resources found in bundle fragments, and add them into the 
48   */
49  public class OSGiWebInfConfiguration extends WebInfConfiguration
50  {
51      private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
52      
53      /**
54       * Comma separated list of symbolic names of bundles that contain tlds that should be considered
55       * as on the container classpath
56       */
57      public static final String SYS_PROP_TLD_BUNDLES = "org.eclipse.jetty.osgi.tldbundles";
58      /**
59       * Regex of symbolic names of bundles that should be considered to be on the container classpath
60       */
61      public static final String CONTAINER_BUNDLE_PATTERN = "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern";
62      public static final String FRAGMENT_AND_REQUIRED_BUNDLES = "org.eclipse.jetty.osgi.fragmentAndRequiredBundles";
63      public static final String FRAGMENT_AND_REQUIRED_RESOURCES = "org.eclipse.jetty.osgi.fragmentAndRequiredResources";
64      
65      
66      /* ------------------------------------------------------------ */
67      /** 
68       * Check to see if there have been any bundle symbolic names added of bundles that should be
69       * regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
70       * This can be defined in:
71       * <ol>
72       *  <li>SystemProperty SYS_PROP_TLD_BUNDLES</li>
73       *  <li>DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN</li>
74       *  </ol>
75       *  
76       *  We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds
77       *  MANIFEST.MF header. 
78       *  
79       * @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
80       */
81      @Override
82      public void preConfigure(final WebAppContext context) throws Exception
83      {
84          super.preConfigure(context);
85          
86          //Check to see if there have been any bundle symbolic names added of bundles that should be
87          //regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
88          //This can be defined in:
89          // 1. SystemProperty SYS_PROP_TLD_BUNDLES
90          // 2. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN
91          String tmp = (String)context.getAttribute(CONTAINER_BUNDLE_PATTERN);
92          Pattern pattern = (tmp==null?null:Pattern.compile(tmp));
93          List<String> names = new ArrayList<String>();
94          tmp = System.getProperty(SYS_PROP_TLD_BUNDLES);
95          if (tmp != null)
96          {
97              StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
98              while (tokenizer.hasMoreTokens())
99                  names.add(tokenizer.nextToken());
100         }
101         HashSet<Resource> matchingResources = new HashSet<Resource>();
102         if ( !names.isEmpty() || pattern != null)
103         {
104             Bundle[] bundles = FrameworkUtil.getBundle(OSGiWebInfConfiguration.class).getBundleContext().getBundles();
105            
106             for (Bundle bundle : bundles)
107             {
108                 if (pattern != null)
109                 {
110                     // if bundle symbolic name matches the pattern
111                     if (pattern.matcher(bundle.getSymbolicName()).matches())
112                     {
113                         //get the file location of the jar and put it into the list of container jars that will be scanned for stuff (including tlds)
114                         matchingResources.addAll(getBundleAsResource(bundle));
115                     }
116                 }               
117                 if (names != null)
118                 {
119                     //if there is an explicit bundle name, then check if it matches
120                     if (names.contains(bundle.getSymbolicName()))
121                         matchingResources.addAll(getBundleAsResource(bundle));
122                 }
123             }
124         }        
125         for (Resource r:matchingResources)
126         {
127             context.getMetaData().addContainerResource(r);
128         }
129     }
130     
131     @Override
132     public void postConfigure(WebAppContext context) throws Exception
133     {
134         context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, null); 
135         context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, null);
136         super.postConfigure(context);
137     }
138     
139     /* ------------------------------------------------------------ */
140     /** 
141      * Consider the fragment bundles associated with the bundle of the webapp being deployed.
142      * 
143      * 
144      * @see org.eclipse.jetty.webapp.WebInfConfiguration#findJars(org.eclipse.jetty.webapp.WebAppContext)
145      */
146     @Override
147     protected List<Resource> findJars (WebAppContext context) 
148     throws Exception
149     {
150         List<Resource> mergedResources = new ArrayList<Resource>();
151         //get jars from WEB-INF/lib if there are any
152         List<Resource> webInfJars = super.findJars(context);
153         if (webInfJars != null)
154             mergedResources.addAll(webInfJars);
155         
156         //add fragment jars and any Required-Bundles as if in WEB-INF/lib of the associated webapp
157         Bundle[] bundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
158         if (bundles != null && bundles.length > 0)
159         {
160             Set<Bundle> fragsAndReqsBundles = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
161             if (fragsAndReqsBundles == null)
162             {
163                 fragsAndReqsBundles = new HashSet<Bundle>();
164                 context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, fragsAndReqsBundles);
165             }
166             
167             Set<Resource> fragsAndReqsResources = (Set<Resource>)context.getAttribute(FRAGMENT_AND_REQUIRED_RESOURCES);
168             if (fragsAndReqsResources == null)
169             {
170                 fragsAndReqsResources = new HashSet<Resource>();
171                 context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, fragsAndReqsResources);
172             }
173             
174             for (Bundle b : bundles)
175             {
176                 //add to context attribute storing associated fragments and required bundles
177                 fragsAndReqsBundles.add(b);
178                 File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(b);
179                 Resource r = Resource.newResource(f.toURI());
180                 //add to convenience context attribute storing fragments and required bundles as Resources
181                 fragsAndReqsResources.add(r);
182                 mergedResources.add(r);
183             }
184         }
185         
186         return mergedResources;
187     }
188     
189     /* ------------------------------------------------------------ */
190     /** 
191      * Allow fragments to supply some resources that are added to the baseResource of the webapp.
192      * 
193      * The resources can be either prepended or appended to the baseResource.
194      * 
195      * @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
196      */
197     @Override
198     public void configure(WebAppContext context) throws Exception
199     {
200         TreeMap<String, Resource> patchResourcesPath = new TreeMap<String, Resource>();
201         TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>();
202              
203         Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
204         if (bundle != null)
205         {
206             Set<Bundle> fragments = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
207             if (fragments != null && !fragments.isEmpty())
208             {
209                 // sorted extra resource base found in the fragments.
210                 // the resources are either overriding the resourcebase found in the
211                 // web-bundle
212                 // or appended.
213                 // amongst each resource we sort them according to the alphabetical
214                 // order
215                 // of the name of the internal folder and the symbolic name of the
216                 // fragment.
217                 // this is useful to make sure that the lookup path of those
218                 // resource base defined by fragments is always the same.
219                 // This natural order could be abused to define the order in which
220                 // the base resources are
221                 // looked up.
222                 for (Bundle frag : fragments)
223                 {
224                     String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH);
225                     String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH);
226                     if (fragFolder != null)
227                     {
228                         URL fragUrl = frag.getEntry(fragFolder);
229                         if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder
230                                                                                   + " inside "
231                                                                                   + " the fragment '"
232                                                                                   + frag.getSymbolicName()
233                                                                                   + "'"); }
234                         fragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(fragUrl);
235                         String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder;
236                         appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl));
237                     }
238                     if (patchFragFolder != null)
239                     {
240                         URL patchFragUrl = frag.getEntry(patchFragFolder);
241                         if (patchFragUrl == null)
242                         { 
243                             throw new IllegalArgumentException("Unable to locate " + patchFragUrl
244                                                                + " inside fragment '"+frag.getSymbolicName()+ "'"); 
245                         }
246                         patchFragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(patchFragUrl);
247                         String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder;
248                         patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl));
249                     }
250                 }
251                 if (!appendedResourcesPath.isEmpty())
252                     context.setAttribute(WebInfConfiguration.RESOURCE_DIRS, new HashSet<Resource>(appendedResourcesPath.values()));
253             }
254         }
255         
256         super.configure(context);
257 
258         // place the patch resources at the beginning of the contexts's resource base
259         if (!patchResourcesPath.isEmpty())
260         {
261             Resource[] resources = new Resource[1+patchResourcesPath.size()];
262             ResourceCollection mergedResources = new ResourceCollection (patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]));
263             System.arraycopy(patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]), 0, resources, 0, patchResourcesPath.size());
264             resources[resources.length-1] = context.getBaseResource();
265             context.setBaseResource(new ResourceCollection(resources));
266         }
267     }
268 
269     
270     /* ------------------------------------------------------------ */
271     /**
272     * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars
273     * embedded in the bundle.
274     */
275     private  List<Resource> getBundleAsResource(Bundle bundle)
276     throws Exception
277     {
278         List<Resource> resources = new ArrayList<Resource>();
279 
280         File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
281         if (file.isDirectory())
282         {
283             for (File f : file.listFiles())
284             {
285                 if (f.getName().endsWith(".jar") && f.isFile())
286                 {
287                     resources.add(Resource.newResource(f));
288                 }
289                 else if (f.isDirectory() && f.getName().equals("lib"))
290                 {
291                     for (File f2 : file.listFiles())
292                     {
293                         if (f2.getName().endsWith(".jar") && f2.isFile())
294                         {
295                             resources.add(Resource.newResource(f));
296                         }
297                     }
298                 }
299             }
300             resources.add(Resource.newResource(file)); //TODO really???
301         }
302         else
303         {
304             resources.add(Resource.newResource(file));
305         }
306         
307         return resources;
308     }
309 }