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.jasper;
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.regex.Pattern;
29  
30  import javax.servlet.jsp.JspFactory;
31  
32  import org.eclipse.jetty.deploy.DeploymentManager;
33  import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
34  import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
35  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
36  import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.osgi.framework.Bundle;
40  import org.osgi.framework.FrameworkUtil;
41  
42  
43  
44  /**
45   * ContainerTldBundleDiscoverer
46   * 
47   * Finds bundles that are considered as on the container classpath that
48   * contain tlds.
49   * 
50   * The System property org.eclipse.jetty.osgi.tldbundles is a comma
51   * separated list of exact symbolic names of bundles that have container classpath
52   * tlds.
53   * 
54   * The DeploymentManager context attribute "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern"
55   * can be used to define a pattern of symbolic names of bundles that contain container 
56   * classpath tlds.
57   * 
58   * The matching bundles are converted to URLs that are put onto a special classloader that acts as the
59   * parent classloader for contexts deployed by the jetty Server instance (see ServerInstanceWrapper).
60   * 
61   * It also discovers the bundle that contains the jstl taglib and adds it into the 
62   * "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern" (if it is not already there) so
63   * that the WebInfOSGiConfiguration class will add the jstl taglib bundle into the list of container
64   * resources.
65   * 
66   * Eg:
67   * -Dorg.eclipse.jetty.osgi.tldbundles=org.springframework.web.servlet,com.opensymphony.module.sitemesh
68   * 
69   */
70  public class ContainerTldBundleDiscoverer implements TldBundleDiscoverer
71  {
72  
73      private static final Logger LOG = Log.getLogger(ContainerTldBundleDiscoverer.class);
74      
75  
76      private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl";
77      /**
78       * Default name of a class that belongs to the jstl bundle. From that class
79       * we locate the corresponding bundle and register it as a bundle that
80       * contains tld files.
81       */
82      private static String DEFAULT_JSTL_BUNDLE_CLASS = "org.apache.taglibs.standard.tag.rt.core.WhenTag";
83  
84      private Bundle jstlBundle = null;
85      
86      /**
87       * Check the System property "org.eclipse.jetty.osgi.tldbundles" for names of
88       * bundles that contain tlds and convert to URLs.
89       * 
90       * @return The location of the jars that contain tld files as URLs.
91       */
92      public URL[] getUrlsForBundlesWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception
93      {        
94          if (!isJspAvailable())
95          {
96              return new URL[0];
97          }
98  
99          if (jstlBundle == null)
100             jstlBundle = findJstlBundle();
101 
102         Bundle[] bundles = FrameworkUtil.getBundle(ContainerTldBundleDiscoverer.class).getBundleContext().getBundles();
103         HashSet<URL> urls = new HashSet<URL>();
104         String tmp = System.getProperty(OSGiWebInfConfiguration.SYS_PROP_TLD_BUNDLES); //comma separated exact names
105         List<String> sysNames =   new ArrayList<String>();
106         if (tmp != null)
107         {
108             StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
109             while (tokenizer.hasMoreTokens())
110                 sysNames.add(tokenizer.nextToken());
111         }
112         tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns
113     
114         Pattern pattern = (tmp==null? null : Pattern.compile(tmp));
115         
116         //check that the jstl bundle is not already included in the pattern, and include it if it is not because
117         //subsequent classes such as OSGiWebInfConfiguration use this pattern to determine which jars are
118         //considered to be on the container classpath
119         if (jstlBundle != null) 
120         {
121             if (pattern == null)
122             {
123                 pattern = Pattern.compile(jstlBundle.getSymbolicName());
124                 deploymentManager.setContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN, jstlBundle.getSymbolicName());
125             }
126             else if (!(pattern.matcher(jstlBundle.getSymbolicName()).matches()))
127             {
128                 String s = tmp+"|"+jstlBundle.getSymbolicName();
129                 pattern = Pattern.compile(s);
130                 deploymentManager.setContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN, s);
131             }
132         }
133 
134         
135         for (Bundle bundle : bundles)
136         {
137             if (sysNames.contains(bundle.getSymbolicName()))
138                 convertBundleLocationToURL(locatorHelper, bundle, urls);           
139             else if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches())
140                 convertBundleLocationToURL(locatorHelper, bundle, urls);
141         }
142 
143         return urls.toArray(new URL[urls.size()]);
144 
145     }
146 
147     /**
148      * Check that jsp is on the classpath
149      * @return
150      */
151     public boolean isJspAvailable()
152     {
153         try
154         {
155             getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet");
156         }
157         catch (Exception e)
158         {
159             LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e);
160             return false;
161         }
162         return true;
163     }
164     
165     
166     /**
167      * 
168      * Some versions of JspFactory do Class.forName, which probably won't work in an 
169      * OSGi environment.
170      */
171     public void fixJspFactory ()
172     {   
173         try
174         {
175             Class<javax.servlet.ServletContext> servletContextClass = javax.servlet.ServletContext.class;
176             // bug #299733
177             JspFactory fact = JspFactory.getDefaultFactory();
178             if (fact == null)
179             { // bug #299733
180               // JspFactory does a simple
181               // Class.getForName("org.apache.jasper.runtime.JspFactoryImpl")
182               // however its bundles does not import the jasper package
183               // so it fails. let's help things out:
184                 fact = (JspFactory) JettyBootstrapActivator.class.getClassLoader().loadClass(DEFAULT_JSP_FACTORY_IMPL_CLASS).newInstance();
185                 JspFactory.setDefaultFactory(fact);
186             }
187         }
188         catch (Exception e)
189         {
190             LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e);
191         }
192     }
193     
194     
195     /**
196      * Find the bundle that contains a jstl implementation class, which assumes that
197      * the jstl taglibs will be inside the same bundle.
198      * @return
199      */
200     public Bundle findJstlBundle ()
201     {
202         Class<?> jstlClass = null;
203     
204         try
205         {
206             jstlClass = JSTLBundleDiscoverer.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
207         }
208         catch (ClassNotFoundException e)
209         {
210             LOG.info("jstl not on classpath", e);
211         }
212         
213         if (jstlClass != null)
214             //get the bundle containing jstl
215             return FrameworkUtil.getBundle(jstlClass);
216         
217         return null;
218     }
219     
220     /**
221      * Resolves a bundle that contains tld files as a URL. The URLs are
222      * used by jasper to discover the tld files.
223      * 
224      * Support only 2 types of packaging for the bundle: - the bundle is a jar
225      * (recommended for runtime.) - the bundle is a folder and contain jars in
226      * the root and/or in the lib folder (nice for PDE developement situations)
227      * Unsupported: the bundle is a jar that embeds more jars.
228      * 
229      * @param locatorHelper
230      * @param bundle
231      * @param urls
232      * @throws Exception
233      */
234     private void convertBundleLocationToURL(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
235     {
236         File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
237         if (jasperLocation.isDirectory())
238         {
239             for (File f : jasperLocation.listFiles())
240             {
241                 if (f.getName().endsWith(".jar") && f.isFile())
242                 {
243                     urls.add(f.toURI().toURL());
244                 }
245                 else if (f.isDirectory() && f.getName().equals("lib"))
246                 {
247                     for (File f2 : jasperLocation.listFiles())
248                     {
249                         if (f2.getName().endsWith(".jar") && f2.isFile())
250                         {
251                             urls.add(f2.toURI().toURL());
252                         }
253                     }
254                 }
255             }
256             urls.add(jasperLocation.toURI().toURL());
257         }
258         else
259         {
260             urls.add(jasperLocation.toURI().toURL());
261         }
262     }
263 }