001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.commons.jci.examples.serverpages; 019 020 import java.io.File; 021 import java.io.IOException; 022 import java.io.PrintWriter; 023 import java.lang.String; 024 import java.util.HashMap; 025 import java.util.HashSet; 026 import java.util.Map; 027 import java.util.Set; 028 029 import javax.servlet.ServletException; 030 import javax.servlet.http.HttpServlet; 031 import javax.servlet.http.HttpServletRequest; 032 import javax.servlet.http.HttpServletResponse; 033 034 import org.apache.commons.jci.ReloadingClassLoader; 035 import org.apache.commons.jci.compilers.CompilationResult; 036 import org.apache.commons.jci.compilers.JavaCompilerFactory; 037 import org.apache.commons.jci.listeners.CompilingListener; 038 import org.apache.commons.jci.monitor.FilesystemAlterationMonitor; 039 import org.apache.commons.jci.monitor.FilesystemAlterationObserver; 040 import org.apache.commons.jci.problems.CompilationProblem; 041 import org.apache.commons.jci.readers.ResourceReader; 042 import org.apache.commons.jci.stores.MemoryResourceStore; 043 import org.apache.commons.jci.stores.TransactionalResourceStore; 044 import org.apache.commons.jci.utils.ConversionUtils; 045 046 047 /** 048 * A mini JSP servlet that monitors a certain directory and 049 * recompiles and then instantiates the JSP pages as soon as 050 * they have changed. 051 * 052 * @author tcurdt 053 */ 054 public final class ServerPageServlet extends HttpServlet { 055 056 private static final long serialVersionUID = 1L; 057 058 private final ReloadingClassLoader classloader = new ReloadingClassLoader(ServerPageServlet.class.getClassLoader()); 059 private FilesystemAlterationMonitor fam; 060 private CompilingListener jspListener; 061 062 private Map<String, HttpServlet> servletsByClassname = new HashMap<String, HttpServlet>(); 063 064 public void init() throws ServletException { 065 super.init(); 066 067 final File serverpagesDir = new File(getServletContext().getRealPath("/") + getInitParameter("serverpagesDir")); 068 069 log("Monitoring serverpages in " + serverpagesDir); 070 071 final TransactionalResourceStore store = new TransactionalResourceStore(new MemoryResourceStore()) { 072 073 private Set<String> newClasses; 074 private Map<String, HttpServlet> newServletsByClassname; 075 076 public void onStart() { 077 super.onStart(); 078 079 newClasses = new HashSet<String>(); 080 newServletsByClassname = new HashMap<String, HttpServlet>(servletsByClassname); 081 } 082 083 public void onStop() { 084 super.onStop(); 085 086 boolean reload = false; 087 for (String clazzName : newClasses) { 088 try { 089 final Class clazz = classloader.loadClass(clazzName); 090 091 if (!HttpServlet.class.isAssignableFrom(clazz)) { 092 log(clazzName + " is not a servlet"); 093 continue; 094 } 095 096 // create new instance of jsp page 097 final HttpServlet servlet = (HttpServlet) clazz.newInstance(); 098 newServletsByClassname.put(clazzName, servlet); 099 100 reload = true; 101 } catch(Exception e) { 102 log("", e); 103 } 104 } 105 106 if (reload) { 107 log("Activating new map of servlets "+ newServletsByClassname); 108 servletsByClassname = newServletsByClassname; 109 } 110 } 111 112 public void write(String pResourceName, byte[] pResourceData) { 113 super.write(pResourceName, pResourceData); 114 115 if (pResourceName.endsWith(".class")) { 116 117 // compiler writes a new class, remember the classes to reload 118 newClasses.add(pResourceName.replace('/', '.').substring(0, pResourceName.length() - ".class".length())); 119 } 120 } 121 122 }; 123 124 // listener that generates the java code from the jsp page and provides that to the compiler 125 jspListener = new CompilingListener(new JavaCompilerFactory().createCompiler("eclipse"), store) { 126 127 private final JspGenerator transformer = new JspGenerator(); 128 private final Map<String, byte[]> sources = new HashMap<String, byte[]>(); 129 private final Set<String> resourceToCompile = new HashSet<String>(); 130 131 public void onStart(FilesystemAlterationObserver pObserver) { 132 super.onStart(pObserver); 133 134 resourceToCompile.clear(); 135 } 136 137 138 public void onFileChange(File pFile) { 139 if (pFile.getName().endsWith(".jsp")) { 140 final String resourceName = ConversionUtils.stripExtension(getSourceNameFromFile(observer, pFile)) + ".java"; 141 142 log("Updating " + resourceName); 143 144 sources.put(resourceName, transformer.generateJavaSource(resourceName, pFile)); 145 146 resourceToCompile.add(resourceName); 147 } 148 super.onFileChange(pFile); 149 } 150 151 152 public void onFileCreate(File pFile) { 153 if (pFile.getName().endsWith(".jsp")) { 154 final String resourceName = ConversionUtils.stripExtension(getSourceNameFromFile(observer, pFile)) + ".java"; 155 156 log("Creating " + resourceName); 157 158 sources.put(resourceName, transformer.generateJavaSource(resourceName, pFile)); 159 160 resourceToCompile.add(resourceName); 161 } 162 super.onFileCreate(pFile); 163 } 164 165 166 public String[] getResourcesToCompile(FilesystemAlterationObserver pObserver) { 167 // we only want to compile the jsp pages 168 final String[] resourceNames = new String[resourceToCompile.size()]; 169 resourceToCompile.toArray(resourceNames); 170 return resourceNames; 171 } 172 173 174 public ResourceReader getReader( final FilesystemAlterationObserver pObserver ) { 175 return new JspReader(sources, super.getReader(pObserver)); 176 } 177 }; 178 jspListener.addReloadNotificationListener(classloader); 179 180 fam = new FilesystemAlterationMonitor(); 181 fam.addListener(serverpagesDir, jspListener); 182 fam.start(); 183 } 184 185 private String convertRequestToServletClassname( final HttpServletRequest request ) { 186 187 String path = request.getPathInfo().substring(1); 188 189 return ConversionUtils.stripExtension(path).replace('/', '.'); 190 } 191 192 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 193 194 log("Request " + request.getRequestURI()); 195 196 final CompilationResult result = jspListener.getCompilationResult(); 197 final CompilationProblem[] errors = result.getErrors(); 198 199 if (errors.length > 0) { 200 201 // if there are errors we provide the compilation errors instead of the jsp page 202 203 final PrintWriter out = response.getWriter(); 204 205 out.append("<html><body>"); 206 207 for (CompilationProblem problem : errors) { 208 out.append(problem.toString()).append("<br/>").append('\n'); 209 } 210 211 out.append("</body></html>"); 212 213 out.flush(); 214 out.close(); 215 return; 216 } 217 218 final String servletClassname = convertRequestToServletClassname(request); 219 220 log("Checking for serverpage " + servletClassname); 221 222 final HttpServlet servlet = servletsByClassname.get(servletClassname); 223 224 if (servlet == null) { 225 log("No servlet for " + request.getRequestURI()); 226 response.sendError(404); 227 return; 228 } 229 230 log("Delegating request to " + servletClassname); 231 232 servlet.service(request, response); 233 } 234 235 public void destroy() { 236 237 fam.stop(); 238 239 super.destroy(); 240 } 241 }