    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    }