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;
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                 //skip bundles that are not installed
178                 if (b.getState() == Bundle.UNINSTALLED)
179                     continue;
180                 
181                 //add to context attribute storing associated fragments and required bundles
182                 fragsAndReqsBundles.add(b);
183                 File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(b);
184                 Resource r = Resource.newResource(f.toURI());
185                 //add to convenience context attribute storing fragments and required bundles as Resources
186                 fragsAndReqsResources.add(r);
187                 mergedResources.add(r);
188             }
189         }
190         
191         return mergedResources;
192     }
193     
194     /* ------------------------------------------------------------ */
195     /** 
196      * Allow fragments to supply some resources that are added to the baseResource of the webapp.
197      * 
198      * The resources can be either prepended or appended to the baseResource.
199      * 
200      * @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
201      */
202     @Override
203     public void configure(WebAppContext context) throws Exception
204     {
205         TreeMap<String, Resource> patchResourcesPath = new TreeMap<String, Resource>();
206         TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>();
207              
208         Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
209         if (bundle != null)
210         {
211             Set<Bundle> fragments = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
212             if (fragments != null && !fragments.isEmpty())
213             {
214                 // sorted extra resource base found in the fragments.
215                 // the resources are either overriding the resourcebase found in the
216                 // web-bundle
217                 // or appended.
218                 // amongst each resource we sort them according to the alphabetical
219                 // order
220                 // of the name of the internal folder and the symbolic name of the
221                 // fragment.
222                 // this is useful to make sure that the lookup path of those
223                 // resource base defined by fragments is always the same.
224                 // This natural order could be abused to define the order in which
225                 // the base resources are
226                 // looked up.
227                 for (Bundle frag : fragments)
228                 {
229                     String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH);
230                     String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH);
231                     if (fragFolder != null)
232                     {
233                         URL fragUrl = frag.getEntry(fragFolder);
234                         if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder
235                                                                                   + " inside "
236                                                                                   + " the fragment '"
237                                                                                   + frag.getSymbolicName()
238                                                                                   + "'"); }
239                         fragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(fragUrl);
240                         String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder;
241                         appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl));
242                     }
243                     if (patchFragFolder != null)
244                     {
245                         URL patchFragUrl = frag.getEntry(patchFragFolder);
246                         if (patchFragUrl == null)
247                         { 
248                             throw new IllegalArgumentException("Unable to locate " + patchFragUrl
249                                                                + " inside fragment '"+frag.getSymbolicName()+ "'"); 
250                         }
251                         patchFragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(patchFragUrl);
252                         String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder;
253                         patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl));
254                     }
255                 }
256                 if (!appendedResourcesPath.isEmpty())
257                     context.setAttribute(WebInfConfiguration.RESOURCE_DIRS, new HashSet<Resource>(appendedResourcesPath.values()));
258             }
259         }
260         
261         super.configure(context);
262 
263         // place the patch resources at the beginning of the contexts's resource base
264         if (!patchResourcesPath.isEmpty())
265         {
266             Resource[] resources = new Resource[1+patchResourcesPath.size()];
267             ResourceCollection mergedResources = new ResourceCollection (patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]));
268             System.arraycopy(patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]), 0, resources, 0, patchResourcesPath.size());
269             resources[resources.length-1] = context.getBaseResource();
270             context.setBaseResource(new ResourceCollection(resources));
271         }
272     }
273 
274     
275     /* ------------------------------------------------------------ */
276     /**
277     * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars
278     * embedded in the bundle.
279     */
280     private  List<Resource> getBundleAsResource(Bundle bundle)
281     throws Exception
282     {
283         List<Resource> resources = new ArrayList<Resource>();
284 
285         File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
286         if (file.isDirectory())
287         {
288             for (File f : file.listFiles())
289             {
290                 if (f.getName().endsWith(".jar") && f.isFile())
291                 {
292                     resources.add(Resource.newResource(f));
293                 }
294                 else if (f.isDirectory() && f.getName().equals("lib"))
295                 {
296                     for (File f2 : file.listFiles())
297                     {
298                         if (f2.getName().endsWith(".jar") && f2.isFile())
299                         {
300                             resources.add(Resource.newResource(f));
301                         }
302                     }
303                 }
304             }
305             resources.add(Resource.newResource(file)); //TODO really???
306         }
307         else
308         {
309             resources.add(Resource.newResource(file));
310         }
311         
312         return resources;
313     }
314 }