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.jasper; 20 21 import java.io.File; 22 import java.io.InputStream; 23 import java.lang.reflect.Field; 24 import java.net.URL; 25 import java.util.ArrayList; 26 import java.util.HashSet; 27 import java.util.Set; 28 29 import javax.servlet.Servlet; 30 import javax.servlet.jsp.JspContext; 31 import javax.servlet.jsp.JspFactory; 32 33 import org.apache.jasper.Constants; 34 import org.apache.jasper.compiler.Localizer; 35 import org.eclipse.jetty.deploy.DeploymentManager; 36 import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; 37 import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; 38 import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer; 39 import org.eclipse.jetty.util.log.Log; 40 import org.eclipse.jetty.util.log.Logger; 41 import org.osgi.framework.Bundle; 42 import org.osgi.framework.FrameworkUtil; 43 import org.xml.sax.EntityResolver; 44 import org.xml.sax.InputSource; 45 import org.xml.sax.SAXException; 46 47 /** 48 * 49 * JSTLBundleDiscoverer 50 * 51 * Fix various shortcomings with the way jasper parses the tld files. Plugs the 52 * JSTL tlds assuming that they are packaged with the bundle that contains the 53 * JSTL classes. 54 * <p> 55 * Pluggable tlds at the server level are handled by 56 * {@link ContainerTldBundleDiscoverer}. 57 * </p> 58 */ 59 public class JSTLBundleDiscoverer implements TldBundleDiscoverer 60 { 61 private static final Logger LOG = Log.getLogger(JSTLBundleDiscoverer.class); 62 63 64 /** 65 * Default name of a class that belongs to the jstl bundle. From that class 66 * we locate the corresponding bundle and register it as a bundle that 67 * contains tld files. 68 */ 69 private static String DEFAULT_JSTL_BUNDLE_CLASS = "org.apache.taglibs.standard.tag.el.core.WhenTag"; 70 71 // used to be "org.apache.jasper.runtime.JspFactoryImpl" but now 72 // the standard tag library implementation are stored in a separate bundle. 73 74 // DISABLED please use the tld bundle argument for the OSGiAppProvider 75 // /** 76 // * Default name of a class that belongs to the bundle where the Java 77 // server Faces tld files are defined. 78 // * This is the sun's reference implementation. 79 // */ 80 // private static String DEFAUT_JSF_IMPL_CLASS = 81 // "com.sun.faces.config.ConfigureListener"; 82 83 /** 84 * Default jsp factory implementation. Idally jasper is osgified and we can 85 * use services. In the mean time we statically set the jsp factory 86 * implementation. bug #299733 87 */ 88 private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl"; 89 90 private static final Set<URL> __tldBundleCache = new HashSet<URL>(); 91 92 public JSTLBundleDiscoverer() 93 { 94 //fixupDtdResolution(); 95 96 try 97 { 98 // sanity check: 99 Class cl = getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet"); 100 } 101 catch (Exception e) 102 { 103 LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e); 104 return; 105 } 106 try 107 { 108 Class<javax.servlet.ServletContext> servletContextClass = javax.servlet.ServletContext.class; 109 // bug #299733 110 JspFactory fact = JspFactory.getDefaultFactory(); 111 if (fact == null) 112 { // bug #299733 113 // JspFactory does a simple 114 // Class.getForName("org.apache.jasper.runtime.JspFactoryImpl") 115 // however its bundles does not import the jasper package 116 // so it fails. let's help things out: 117 fact = (JspFactory) JettyBootstrapActivator.class.getClassLoader().loadClass(DEFAULT_JSP_FACTORY_IMPL_CLASS).newInstance(); 118 JspFactory.setDefaultFactory(fact); 119 } 120 121 } 122 catch (Exception e) 123 { 124 LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e); 125 } 126 } 127 128 /** 129 * The jasper TldScanner expects a URLClassloader to parse a jar for the 130 * /META-INF/*.tld it may contain. We place the bundles that we know contain 131 * such tag-libraries. Please note that it will work if and only if the 132 * bundle is a jar (!) Currently we just hardcode the bundle that contains 133 * the jstl implemenation. 134 * 135 * A workaround when the tld cannot be parsed with this method is to copy 136 * and paste it inside the WEB-INF of the webapplication where it is used. 137 * 138 * Support only 2 types of packaging for the bundle: - the bundle is a jar 139 * (recommended for runtime.) - the bundle is a folder and contain jars in 140 * the root and/or in the lib folder (nice for PDE developement situations) 141 * Unsupported: the bundle is a jar that embeds more jars. 142 * 143 * @return array of URLs 144 * @throws Exception 145 */ 146 public URL[] getUrlsForBundlesWithTlds(DeploymentManager deployer, BundleFileLocatorHelper locatorHelper) throws Exception 147 { 148 149 ArrayList<URL> urls = new ArrayList<URL>(); 150 Class<?> jstlClass = null; 151 152 // Look for the jstl bundle 153 // We assume the jstl's tlds are defined there. 154 // We assume that the jstl bundle is imported by this bundle 155 // So we can look for this class using this bundle's classloader: 156 try 157 { 158 jstlClass = JSTLBundleDiscoverer.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS); 159 } 160 catch (ClassNotFoundException e) 161 { 162 LOG.info("jstl not on classpath", e); 163 } 164 165 if (jstlClass != null) 166 { 167 //get the bundle containing jstl 168 Bundle tldBundle = FrameworkUtil.getBundle(jstlClass); 169 File tldBundleLocation = locatorHelper.getBundleInstallLocation(tldBundle); 170 171 System.err.println("jstl bundle: "+tldBundle); 172 System.err.println("jstl bundle location: "+tldBundleLocation); 173 if (tldBundleLocation != null && tldBundleLocation.isDirectory()) 174 { 175 // try to find the jar files inside this folder 176 for (File f : tldBundleLocation.listFiles()) 177 { 178 if (f.getName().endsWith(".jar") && f.isFile()) 179 { 180 System.err.println("Tld jar in dir: "+f.toURI()); 181 urls.add(f.toURI().toURL()); 182 } 183 else if (f.isDirectory() && f.getName().equals("lib")) 184 { 185 for (File f2 : tldBundleLocation.listFiles()) 186 { 187 if (f2.getName().endsWith(".jar") && f2.isFile()) 188 { 189 System.err.println("Tld jar in lib dir: "+f2.toURI()); 190 urls.add(f2.toURI().toURL()); 191 } 192 } 193 } 194 } 195 196 } 197 else if (tldBundleLocation != null) 198 { 199 System.err.println("Tld bundle uri: "+tldBundleLocation.toURI()); 200 urls.add(tldBundleLocation.toURI().toURL()); 201 202 String pattern = (String)deployer.getContextAttribute("org.eclipse.jetty.server.webapp.containerIncludeBundlePattern"); 203 pattern = (pattern==null?"":pattern); 204 if (!pattern.contains(tldBundle.getSymbolicName())) 205 { 206 pattern += "|"+tldBundle.getSymbolicName(); 207 deployer.setContextAttribute("org.eclipse.jetty.server.webapp.containerIncludeBundlePattern", pattern); 208 } 209 System.err.println("PATTERN: "+pattern); 210 } 211 } 212 213 return urls.toArray(new URL[urls.size()]); 214 } 215 216 /** 217 * Jasper resolves the dtd when it parses a taglib descriptor. It uses this 218 * code to do that: 219 * ParserUtils.getClass().getResourceAsStream(resourcePath); where 220 * resourcePath is for example: 221 * /javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd Unfortunately, the 222 * dtd file is not in the exact same classloader as ParserUtils class and 223 * the dtds are packaged in 2 separate bundles. OSGi does not look in the 224 * dependencies' classloader when a resource is searched. 225 * <p> 226 * The workaround consists of setting the entity resolver. That is a patch 227 * added to the version of glassfish-jasper-jetty. IT is also present in the 228 * latest version of glassfish jasper. Could not use introspection to set 229 * new value on a static friendly field :( 230 * </p> 231 */ 232 void fixupDtdResolution() 233 { 234 try 235 { 236 // ParserUtils.setEntityResolver(new MyFixedupEntityResolver()); 237 238 239 } 240 catch (Exception e) 241 { 242 e.printStackTrace(); 243 } 244 245 } 246 247 /** 248 * Instead of using the ParserUtil's classloader, we use a class that is 249 * indeed next to the resource for sure. 250 */ 251 //static class MyFixedupEntityResolver implements EntityResolver 252 //{ 253 /** 254 * Same values than in ParserUtils... 255 */ 256 /* static final String[] CACHED_DTD_PUBLIC_IDS = { Constants.TAGLIB_DTD_PUBLIC_ID_11, Constants.TAGLIB_DTD_PUBLIC_ID_12, 257 Constants.WEBAPP_DTD_PUBLIC_ID_22, Constants.WEBAPP_DTD_PUBLIC_ID_23, }; 258 259 static final String[] CACHED_DTD_RESOURCE_PATHS = { Constants.TAGLIB_DTD_RESOURCE_PATH_11, Constants.TAGLIB_DTD_RESOURCE_PATH_12, 260 Constants.WEBAPP_DTD_RESOURCE_PATH_22, Constants.WEBAPP_DTD_RESOURCE_PATH_23, }; 261 262 static final String[] CACHED_SCHEMA_RESOURCE_PATHS = { Constants.TAGLIB_SCHEMA_RESOURCE_PATH_20, Constants.TAGLIB_SCHEMA_RESOURCE_PATH_21, 263 Constants.WEBAPP_SCHEMA_RESOURCE_PATH_24, Constants.WEBAPP_SCHEMA_RESOURCE_PATH_25, };*/ 264 265 /* public InputSource resolveEntity(String publicId, String systemId) throws SAXException 266 { 267 for (int i = 0; i < CACHED_DTD_PUBLIC_IDS.length; i++) 268 { 269 String cachedDtdPublicId = CACHED_DTD_PUBLIC_IDS[i]; 270 if (cachedDtdPublicId.equals(publicId)) 271 { 272 String resourcePath = CACHED_DTD_RESOURCE_PATHS[i]; 273 InputStream input = null; 274 input = Servlet.class.getResourceAsStream(resourcePath); 275 if (input == null) 276 { 277 input = JspContext.class.getResourceAsStream(resourcePath); 278 if (input == null) 279 {*/ 280 // if that failed try again with the original code: 281 // although it is likely not changed. 282 /* input = this.getClass().getResourceAsStream(resourcePath); 283 } 284 } 285 if (input == null) { throw new SAXException(Localizer.getMessage("jsp.error.internal.filenotfound", resourcePath)); } 286 InputSource isrc = new InputSource(input); 287 return isrc; 288 } 289 } 290 291 return null; 292 } 293 }*/ 294 295 }