View Javadoc

1   /*
2    * $Id$
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts2;
22  
23  import com.opensymphony.xwork2.util.URLUtil;
24  import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
25  import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
26  import com.opensymphony.xwork2.util.finder.UrlSet;
27  import com.opensymphony.xwork2.util.logging.Logger;
28  import com.opensymphony.xwork2.util.logging.LoggerFactory;
29  import org.apache.commons.io.FileUtils;
30  import org.apache.commons.lang.xwork.ObjectUtils;
31  import org.apache.commons.lang.xwork.StringUtils;
32  import org.apache.struts2.compiler.MemoryClassLoader;
33  import org.apache.struts2.compiler.MemoryJavaFileObject;
34  import org.apache.struts2.jasper.JasperException;
35  import org.apache.struts2.jasper.JspC;
36  import org.apache.struts2.jasper.compiler.JspUtil;
37  
38  import javax.servlet.Servlet;
39  import javax.servlet.ServletContext;
40  import javax.servlet.ServletException;
41  import javax.servlet.jsp.JspPage;
42  import javax.servlet.jsp.HttpJspPage;
43  import javax.tools.*;
44  import java.io.File;
45  import java.io.IOException;
46  import java.net.URI;
47  import java.net.URISyntaxException;
48  import java.net.URL;
49  import java.security.CodeSource;
50  import java.security.ProtectionDomain;
51  import java.util.*;
52  import java.util.regex.Pattern;
53  import java.util.regex.Matcher;
54  
55  /***
56   * Uses jasper to extract a JSP from the classpath to a file and compile it. The classpath used for
57   * compilation is built by finding all the jar files using the current class loader (Thread), plus
58   * directories.
59   */
60  public class JSPLoader {
61      private static final Logger LOG = LoggerFactory.getLogger(JSPLoader.class);
62  
63      private static MemoryClassLoader classLoader = new MemoryClassLoader();
64      private static final String DEFAULT_PACKAGE = "org.apache.struts2.jsp";
65  
66      private static final Pattern PACKAGE_PATTERN = Pattern.compile("package (.*?);");
67      private static final Pattern CLASS_PATTERN = Pattern.compile("public final class (.*?) ");
68  
69      public Servlet load(String location) throws Exception {
70          location = StringUtils.substringBeforeLast(location, "?");
71  
72          if (LOG.isDebugEnabled()) {
73              LOG.debug("Compiling JSP [#0]", location);
74          }
75  
76          //use Jasper to compile the JSP into java code
77          JspC jspC = compileJSP(location);
78          String source = jspC.getSourceCode();
79  
80          //System.out.print(source);
81  
82          String className = extractClassName(source);
83  
84          //use Java Compiler API to compile the java code into a class
85          //the tlds that were discovered are added (their jars) to the classpath
86          compileJava(className, source, jspC.getTldAbsolutePaths());
87  
88          //load the class that was just built
89          Class clazz = Class.forName(className, false, classLoader);
90          return createServlet(clazz);
91      }
92  
93      private String extractClassName(String source) {
94          Matcher matcher = PACKAGE_PATTERN.matcher(source);
95          matcher.find();
96          String packageName = matcher.group(1);
97  
98          matcher = CLASS_PATTERN.matcher(source);
99          matcher.find();
100         String className = matcher.group(1);
101 
102         return packageName + "." + className;
103     }
104 
105     /***
106      * Creates and inits a servlet
107      */
108     private Servlet createServlet(Class clazz) throws IllegalAccessException, InstantiationException, ServletException {
109         JSPServletConfig config = new JSPServletConfig(ServletActionContext.getServletContext());
110 
111         Servlet servlet = (Servlet) clazz.newInstance();
112         servlet.init(config);
113 
114         /*
115          there is no need to call JspPage.init explicitly because Jasper's
116          JSP base classe HttpJspBase.init(ServletConfig) calls:
117          jspInit();
118          _jspInit();
119          */
120 
121         return servlet;
122     }
123 
124     /***
125      * Compiles the given source code into java bytecode
126      */
127     private void compileJava(String className, final String source, Set<String> extraClassPath) throws IOException {
128         if (LOG.isTraceEnabled())
129             LOG.trace("Compiling [#0], source: [#1]", className, source);
130 
131         JavaCompiler compiler =
132                 ToolProvider.getSystemJavaCompiler();
133 
134         DiagnosticCollector<JavaFileObject> diagnostics =
135                 new DiagnosticCollector<JavaFileObject>();
136 
137         //the generated bytecode is fed to the class loader
138         JavaFileManager jfm = new
139                 ForwardingJavaFileManager<StandardJavaFileManager>(
140                         compiler.getStandardFileManager(diagnostics, null, null)) {
141 
142                     @Override
143                     public JavaFileObject getJavaFileForOutput(Location location,
144                                                                String name,
145                                                                JavaFileObject.Kind kind,
146                                                                FileObject sibling) throws IOException {
147                         MemoryJavaFileObject fileObject = new MemoryJavaFileObject(name, kind);
148                         classLoader.addMemoryJavaFileObject(name, fileObject);
149                         return fileObject;
150                     }
151                 };
152 
153         //read java source code from memory
154         String fileName = className.replace('.', '/') + ".java";
155         SimpleJavaFileObject sourceCodeObject = new SimpleJavaFileObject(toURI(fileName), JavaFileObject.Kind.SOURCE) {
156             @Override
157             public CharSequence getCharContent(boolean
158                     ignoreEncodingErrors)
159                     throws IOException, IllegalStateException,
160                     UnsupportedOperationException {
161                 return source;
162             }
163 
164         };
165 
166         //build classpath
167         //some entries will be added multiple times, hence the set
168         List<String> optionList = new ArrayList<String>();
169         Set<String> classPath = new HashSet<String>();
170 
171         //find available jars
172         ClassLoaderInterface classLoaderInterface = getClassLoaderInterface();
173         UrlSet urlSet = new UrlSet(classLoaderInterface);
174 
175         //find jars
176         List<URL> urls = urlSet.getUrls();
177 
178         for (URL url : urls) {
179             URL normalizedUrl = URLUtil.normalizeToFileProtocol(url);
180             File file = FileUtils.toFile((URL) ObjectUtils.defaultIfNull(normalizedUrl, url));
181             if (file.exists())
182                 classPath.add(file.getAbsolutePath());
183         }
184 
185         //these should be in the list already, but I am feeling paranoid
186         //this jar
187         classPath.add(getJarUrl(EmbeddedJSPResult.class));
188         //servlet api
189         classPath.add(getJarUrl(Servlet.class));
190         //jsp api
191         classPath.add(getJarUrl(JspPage.class));
192 
193         //add extra classpath entries (jars where tlds were found will be here)
194         for (Iterator<String> iterator = extraClassPath.iterator(); iterator.hasNext();) {
195             String entry = iterator.next();
196             classPath.add(entry);
197         }
198 
199         String classPathString = StringUtils.join(classPath, File.pathSeparator);
200         if (LOG.isDebugEnabled()) {
201             LOG.debug("Compiling [#0] with classpath [#1]", className, classPathString);
202         }
203 
204         optionList.addAll(Arrays.asList("-classpath", classPathString));
205 
206         //compile
207         JavaCompiler.CompilationTask task = compiler.getTask(
208                 null, jfm, diagnostics, optionList, null,
209                 Arrays.asList(sourceCodeObject));
210 
211         if (!task.call()) {
212             throw new StrutsException("Compilation failed:" + diagnostics.getDiagnostics().get(0).toString());
213         }
214     }
215 
216     protected String getJarUrl(Class clazz) {
217         ProtectionDomain protectionDomain = clazz.getProtectionDomain();
218         CodeSource codeSource = protectionDomain.getCodeSource();
219         URL loc = codeSource.getLocation();
220         File file = FileUtils.toFile(loc);
221         return file.getAbsolutePath();
222     }
223 
224     private JspC compileJSP(String location) throws JasperException {
225         JspC jspC = new JspC();
226         jspC.setClassLoaderInterface(getClassLoaderInterface());
227         jspC.setCompile(false);
228         jspC.setJspFiles(location);
229         jspC.setPackage(DEFAULT_PACKAGE);
230         jspC.execute();
231         return jspC;
232     }
233 
234     private ClassLoaderInterface getClassLoaderInterface() {
235         ClassLoaderInterface classLoaderInterface = null;
236         ServletContext ctx = ServletActionContext.getServletContext();
237         if (ctx != null)
238             classLoaderInterface = (ClassLoaderInterface) ctx.getAttribute(ClassLoaderInterface.CLASS_LOADER_INTERFACE);
239 
240         return (ClassLoaderInterface) ObjectUtils.defaultIfNull(classLoaderInterface, new ClassLoaderInterfaceDelegate(JSPLoader.class.getClassLoader()));
241     }
242 
243     private static URI toURI(String name) {
244         try {
245             return new URI(name);
246         } catch (URISyntaxException e) {
247             throw new RuntimeException(e);
248         }
249     }
250 }