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.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 }