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 }