View Javadoc

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