/** * Copyright 2010 Marko Lavikainen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package net.contextfw.web.application.internal.service; import java.io.IOException; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.contextfw.web.application.ResourceCleaner; import net.contextfw.web.application.WebApplication; import net.contextfw.web.application.WebApplicationException; import net.contextfw.web.application.PageHandle; import net.contextfw.web.application.configuration.Configuration; import net.contextfw.web.application.internal.page.PageScope; import net.contextfw.web.application.internal.page.WebApplicationPage; import net.contextfw.web.application.lifecycle.LifecycleListener; import net.contextfw.web.application.remote.ResourceResponse; import net.contextfw.web.application.scope.Execution; import net.contextfw.web.application.scope.PageScopedExecutor; import net.contextfw.web.application.scope.ScopedWebApplicationExecution; import net.contextfw.web.application.scope.WebApplicationStorage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @Singleton public class UpdateHandler { private static final String CONTEXTFW_REFRESH = "contextfw-refresh"; private static final String CONTEXTFW_UPDATE = "contextfw-update"; private static final String CONTEXTFW_REMOVE = "contextfw-remove"; private ThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10); private Logger logger = LoggerFactory.getLogger(UpdateHandler.class); private final LifecycleListener listeners; private final Gson gson; private final DirectoryWatcher watcher; private final ResourceCleaner cleaner; private PageScope pageScope; private final long maxInactivity; private final WebApplicationStorage storage; @Inject public UpdateHandler(LifecycleListener listeners, DirectoryWatcher watcher, ResourceCleaner cleaner, WebApplicationStorage storage, Configuration configuration, PageScope pageScope, Gson gson) { this.gson = gson; this.listeners = listeners; this.maxInactivity = configuration.get(Configuration.MAX_INACTIVITY); this.storage = storage; this.pageScope = pageScope; if (configuration.get(Configuration.DEVELOPMENT_MODE)) { this.cleaner = cleaner; this.watcher = watcher; } else { this.cleaner = null; this.watcher = null; } } private int getCommandStart(String[] splits) { int remaining = splits.length; for (int i = 0; i < splits.length; i++) { remaining--; String s = splits[i]; if ((CONTEXTFW_REMOVE.equals(s) || CONTEXTFW_REFRESH.equals(s)) && remaining >= 1) { return i; } else if (CONTEXTFW_UPDATE.equals(s) && remaining >= 3) { return i; } } return -1; } public final void handleRequest(final HttpServlet servlet, final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { if (watcher != null && watcher.hasChanged()) { logger.info("Reloading resources"); cleaner.clean(); } final String[] uriSplits = request.getRequestURI().split("/"); final int commandStart = getCommandStart(uriSplits); if (commandStart != -1) { String command = uriSplits[commandStart]; PageHandle handle = new PageHandle(uriSplits[commandStart + 1]); if (CONTEXTFW_REMOVE.equals(command)) { storage.remove(handle, request); response.setStatus(HttpServletResponse.SC_NO_CONTENT); } else if (CONTEXTFW_REFRESH.equals(command)) { storage.refresh(handle, request, System.currentTimeMillis() + maxInactivity); response.setStatus(HttpServletResponse.SC_NO_CONTENT); } else if (CONTEXTFW_UPDATE.equals(command)) { final UpdateInvocation[] invocation = new UpdateInvocation[1]; invocation[0] = UpdateInvocation.NOT_DELAYED; storage.update( handle, request, System.currentTimeMillis() + maxInactivity, new ScopedWebApplicationExecution() { @Override public void execute(WebApplication application) { if (application == null) { try { response.sendError(HttpServletResponse.SC_NOT_FOUND); } catch (IOException e) { throw new WebApplicationException(e); } } else { WebApplicationPage page = (WebApplicationPage) application; pageScope.activatePage(page, servlet, request, response); try { invocation[0] = page.getWebApplication().updateState( uriSplits[commandStart + 2], uriSplits[commandStart + 3]); if (invocation[0].isDelayed()) { pageScope.deactivateCurrentPage(); return; } if (!invocation[0].isResource() && !invocation[0].isCancelled()) { listeners.beforeRender(); setHeaders(response); response.setContentType("text/xml; charset=UTF-8"); page.getWebApplication().sendResponse(); listeners.afterRender(); } } catch (Exception e) { listeners.onException(e); } finally { pageScope.deactivateCurrentPage(); } } } }); Set<Execution> afterRun = new HashSet<Execution>(); Object retVal = null; if (invocation[0].isResource()) { retVal = handleResource(request, response, invocation[0]); } else { retVal = invocation[0].getRetVal(); } if (retVal instanceof Execution) { afterRun.add((Execution) retVal); } else if (retVal instanceof Iterable) { for (Object i : ((Iterable<?>) retVal)) { afterRun.add((Execution) i); } } else if (retVal instanceof Execution[]) { for (Execution i : ((Execution[]) retVal)) { afterRun.add(i); } } if (!afterRun.isEmpty()) { runAfterRun(handle, afterRun); } } } else { response.sendError(HttpServletResponse.SC_BAD_REQUEST); } } private void runAfterRun(final PageHandle handle, final Set<Execution> afterRun) throws IOException { final PageScopedExecutor pageScopedExecutor = new PageScopedExecutor() { @Override public void execute(final Runnable execution) { storage.execute(handle, new ScopedWebApplicationExecution() { @Override public void execute(WebApplication application) { if (application != null) { WebApplicationPage page = (WebApplicationPage) application; pageScope.activatePage(page, null, null, null); try { execution.run(); } finally { pageScope.deactivateCurrentPage(); } } } }); } }; for (final Execution exec : afterRun) { executor.execute(new Runnable() { public void run() { try { exec.execute(pageScopedExecutor); } catch (RuntimeException e) { logger.error("Error during running Execution", e); } } }); } } private Execution handleResource(final HttpServletRequest request, final HttpServletResponse response, UpdateInvocation invocation) throws IOException { if (invocation.getRetVal() == null) { response.getWriter().close(); return null; } if (invocation.getRetVal() instanceof ResourceResponse) { return ((ResourceResponse) invocation.getRetVal()).serve(request, response); } else { setHeaders(response); response.setContentType("application/json; charset=UTF-8"); gson.toJson(invocation.getRetVal(), response.getWriter()); response.getWriter().close(); return null; } } public void setHeaders(HttpServletResponse response) { response.addHeader("Expires", "Sun, 19 Nov 1978 05:00:00 GMT"); response.addHeader("Last-Modified", new Date().toString()); response.addHeader("Cache-Control", "no-store, no-cache, must-revalidate"); // response.addHeader("Cache-Control","post-check=0, pre-check=0"); response.addHeader("Pragma", "no-cache"); response.setHeader("Connection", "Keep-Alive"); } @Inject public void setPageScope(PageScope pageScope) { this.pageScope = pageScope; } }