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    }