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                 LOG.debug("Checking bundle {}:{}", bundle.getBundleId(), bundle.getSymbolicName());
109                 if (pattern != null)
110                 {
111                     // if bundle symbolic name matches the pattern
112                     if (pattern.matcher(bundle.getSymbolicName()).matches())
113                     {
114                         //get the file location of the jar and put it into the list of container jars that will be scanned for stuff (including tlds)
115                         matchingResources.addAll(getBundleAsResource(bundle));
116                     }
117                 }               
118                 if (names != null)
119                 {
120                     //if there is an explicit bundle name, then check if it matches
121                     if (names.contains(bundle.getSymbolicName()))
122                         matchingResources.addAll(getBundleAsResource(bundle));
123                 }
124             }
125         }        
126         for (Resource r:matchingResources)
127         {
128             context.getMetaData().addContainerResource(r);
129         }
130     }
131     
132     @Override
133     public void postConfigure(WebAppContext context) throws Exception
134     {
135         context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, null); 
136         context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, null);
137         super.postConfigure(context);
138     }
139     
140     /* ------------------------------------------------------------ */
141     /** 
142      * Consider the fragment bundles associated with the bundle of the webapp being deployed.
143      * 
144      * 
145      * @see org.eclipse.jetty.webapp.WebInfConfiguration#findJars(org.eclipse.jetty.webapp.WebAppContext)
146      */
147     @Override
148     protected List<Resource> findJars (WebAppContext context) 
149     throws Exception
150     {
151         List<Resource> mergedResources = new ArrayList<Resource>();
152         //get jars from WEB-INF/lib if there are any
153         List<Resource> webInfJars = super.findJars(context);
154         if (webInfJars != null)
155             mergedResources.addAll(webInfJars);
156         
157         //add fragment jars and any Required-Bundles as if in WEB-INF/lib of the associated webapp
158         Bundle[] bundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
159         if (bundles != null && bundles.length > 0)
160         {
161             Set<Bundle> fragsAndReqsBundles = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
162             if (fragsAndReqsBundles == null)
163             {
164                 fragsAndReqsBundles = new HashSet<Bundle>();
165                 context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, fragsAndReqsBundles);
166             }
167             
168             Set<Resource> fragsAndReqsResources = (Set<Resource>)context.getAttribute(FRAGMENT_AND_REQUIRED_RESOURCES);
169             if (fragsAndReqsResources == null)
170             {
171                 fragsAndReqsResources = new HashSet<Resource>();
172                 context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, fragsAndReqsResources);
173             }
174             
175             for (Bundle b : bundles)
176             {
177                 //add to context attribute storing associated fragments and required bundles
178                 fragsAndReqsBundles.add(b);
179                 File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(b);
180                 Resource r = Resource.newResource(f.toURI());
181                 //add to convenience context attribute storing fragments and required bundles as Resources
182                 fragsAndReqsResources.add(r);
183                 mergedResources.add(r);
184             }
185         }
186         
187         return mergedResources;
188     }
189     
190     /* ------------------------------------------------------------ */
191     /** 
192      * Allow fragments to supply some resources that are added to the baseResource of the webapp.
193      * 
194      * The resources can be either prepended or appended to the baseResource.
195      * 
196      * @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
197      */
198     @Override
199     public void configure(WebAppContext context) throws Exception
200     {
201         TreeMap<String, Resource> patchResourcesPath = new TreeMap<String, Resource>();
202         TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>();
203              
204         Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
205         if (bundle != null)
206         {
207             Set<Bundle> fragments = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
208             if (fragments != null && !fragments.isEmpty())
209             {
210                 // sorted extra resource base found in the fragments.
211                 // the resources are either overriding the resourcebase found in the
212                 // web-bundle
213                 // or appended.
214                 // amongst each resource we sort them according to the alphabetical
215                 // order
216                 // of the name of the internal folder and the symbolic name of the
217                 // fragment.
218                 // this is useful to make sure that the lookup path of those
219                 // resource base defined by fragments is always the same.
220                 // This natural order could be abused to define the order in which
221                 // the base resources are
222                 // looked up.
223                 for (Bundle frag : fragments)
224                 {
225                     String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH);
226                     String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH);
227                     if (fragFolder != null)
228                     {
229                         URL fragUrl = frag.getEntry(fragFolder);
230                         if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder
231                                                                                   + " inside "
232                                                                                   + " the fragment '"
233                                                                                   + frag.getSymbolicName()
234                                                                                   + "'"); }
235                         fragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(fragUrl);
236                         String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder;
237                         appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl));
238                     }
239                     if (patchFragFolder != null)
240                     {
241                         URL patchFragUrl = frag.getEntry(patchFragFolder);
242                         if (patchFragUrl == null)
243                         { 
244                             throw new IllegalArgumentException("Unable to locate " + patchFragUrl
245                                                                + " inside fragment '"+frag.getSymbolicName()+ "'"); 
246                         }
247                         patchFragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(patchFragUrl);
248                         String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder;
249                         patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl));
250                     }
251                 }
252                 if (!appendedResourcesPath.isEmpty())
253                     context.setAttribute(WebInfConfiguration.RESOURCE_DIRS, new HashSet<Resource>(appendedResourcesPath.values()));
254             }
255         }
256         
257         super.configure(context);
258 
259         // place the patch resources at the beginning of the contexts's resource base
260         if (!patchResourcesPath.isEmpty())
261         {
262             Resource[] resources = new Resource[1+patchResourcesPath.size()];
263             ResourceCollection mergedResources = new ResourceCollection (patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]));
264             System.arraycopy(patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]), 0, resources, 0, patchResourcesPath.size());
265             resources[resources.length-1] = context.getBaseResource();
266             context.setBaseResource(new ResourceCollection(resources));
267         }
268     }
269 
270     
271     /* ------------------------------------------------------------ */
272     /**
273     * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars
274     * embedded in the bundle.
275     */
276     private  List<Resource> getBundleAsResource(Bundle bundle)
277     throws Exception
278     {
279         List<Resource> resources = new ArrayList<Resource>();
280 
281         File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
282         if (file.isDirectory())
283         {
284             for (File f : file.listFiles())
285             {
286                 if (f.getName().endsWith(".jar") && f.isFile())
287                 {
288                     resources.add(Resource.newResource(f));
289                 }
290                 else if (f.isDirectory() && f.getName().equals("lib"))
291                 {
292                     for (File f2 : file.listFiles())
293                     {
294                         if (f2.getName().endsWith(".jar") && f2.isFile())
295                         {
296                             resources.add(Resource.newResource(f));
297                         }
298                     }
299                 }
300             }
301             resources.add(Resource.newResource(file)); //TODO really???
302         }
303         else
304         {
305             resources.add(Resource.newResource(file));
306         }
307         
308         return resources;
309     }
310 }