/* * This file is part of the Wayback archival access software * (http://archive-access.sourceforge.net/projects/wayback/). * * Licensed to the Internet Archive (IA) by one or more individual * contributors. * * The IA licenses this file to You 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 org.archive.wayback.util.webapp; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.LogManager; import java.util.logging.Logger; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.archive.wayback.util.MonitoredFileSet; /** * Top-Level integration point between a series of RequestHandler mappings and a * generic ServletContext. This filter is assumed to be responsible for matching * ALL requests received by the webapp ("*") and uses a RequestMapper to * delegate incoming HttpServletRequests to the appropriate RequestHandler, via * the doFilter() method. * * @author brad */ public class RequestFilter implements Filter { private static final Logger LOGGER = Logger.getLogger(RequestFilter.class.getName()); private final static String CONFIG_PATH = "config-path"; private final static String LOGGING_CONFIG_PATH = "logging-config-path"; private final static String MONITOR_MS_CONFIG = "monitor-ms"; private final static String MONITOR_FILES_CONFIG = "monitor-files"; private UpdateThread thread = null; private RequestMapper mapper = null; private ServletContext context; private String springConfigPath; public void init(FilterConfig config) throws ServletException { context = config.getServletContext(); String logConfigPath = context.getInitParameter(LOGGING_CONFIG_PATH); if (logConfigPath != null) { String resolvedLogPath = context.getRealPath(logConfigPath); File logConfigFile = new File(resolvedLogPath); if (logConfigFile.exists()) { FileInputStream finp = null; try { finp = new FileInputStream(logConfigFile); LogManager.getLogManager().readConfiguration(finp); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (finp != null) { finp.close(); } } catch (IOException e) { throw new ServletException(e); } } } } String configPath = context.getInitParameter(CONFIG_PATH); if (configPath == null) { throw new ServletException("Missing " + CONFIG_PATH + " parameter"); } springConfigPath = context.getRealPath(configPath); String monitorFiles = context.getInitParameter(MONITOR_FILES_CONFIG); if(monitorFiles == null) { // just load once: mapper = loadRequestMapper(); } else { // we're in fancy mode: start the background thread to watch // our Spring config - it will swap out our mapper when things // change String monitorMSString = context.getInitParameter(MONITOR_MS_CONFIG); long monitorMS = 10000; if(monitorMSString != null) { try { monitorMS = Long.parseLong(monitorMSString); } catch(NumberFormatException e) { throw new ServletException("Non int for " + MONITOR_MS_CONFIG); } } String[] monitored = monitorFiles.split(","); ArrayList<String> monitoredL = new ArrayList<String>(); for(String monitoredPath : monitored) { monitoredL.add(monitoredPath); } thread = new UpdateThread(this, monitorMS, monitoredL); // TODO: should we force initial load of a mapper? // it means incoming requests will block until we're ready.. // if we don't the thread will immediately being loading // the Spring config, and will swap it in when it's ready thread.reloadMapper(); thread.start(); } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean handled = false; String origThreadName = Thread.currentThread().getName(); try { if (request instanceof HttpServletRequest) { if (response instanceof HttpServletResponse) { if(mapper != null) { handled = mapper.handleRequest( (HttpServletRequest) request, (HttpServletResponse) response); } } } } finally { Thread.currentThread().setName(origThreadName); } if (!handled) { chain.doFilter(request, response); } } public void destroy() { LOGGER.info("Shutdown starting."); if(thread != null) { thread.interrupt(); } if(mapper != null) { mapper.shutdown(); } LOGGER.info("Shutdown complete."); } private RequestMapper loadRequestMapper() { LOGGER.info("Initializing Spring config at: " + springConfigPath); RequestMapper newMapper = SpringReader.readSpringConfig(springConfigPath, context); LOGGER.info("Initialized Spring config at: " + springConfigPath); return newMapper; } /** * @return the mapper */ public RequestMapper getMapper() { return mapper; } /** * @param mapper the mapper to set */ public void setMapper(RequestMapper mapper) { this.mapper = mapper; } /** * Thread that repeatedly checks a set of Spring config files. If any * change, then a new RequestMapper is created from them, which is then * swapped in on the containing RequestFilter. The old one if present is * shut down. * * @author Brad Tofel */ private class UpdateThread extends Thread { /** * object which merges CDX files with the BDBResourceIndex */ private RequestFilter filter = null; private long runInterval; private MonitoredFileSet fileSet; private MonitoredFileSet.FileState activeState; /** * @param filter the RequestFilter we will update * @param runInterval number of MS bewtween checks * @param monitored List of files to check Mod Time to trigger reload */ public UpdateThread(RequestFilter filter, long runInterval, List<String> monitored) { super("RequestFilter.UpdateThread"); super.setDaemon(true); this.filter = filter; this.runInterval = runInterval; fileSet = new MonitoredFileSet(monitored); activeState = null; } public void reloadMapper() { MonitoredFileSet.FileState startState = fileSet.getFileState(); RequestMapper mapper = filter.loadRequestMapper(); if(fileSet.isChanged(startState)) { // erk.. files changed during the operation.. update nothing.. LOGGER.warning("Files changed during Spring reload... discarding.."); mapper.shutdown(); } else { LOGGER.warning("Loaded RequestMapper."); RequestMapper oldMapper = filter.getMapper(); filter.setMapper(mapper); if(oldMapper != null) { // shut it down (cross fingers first) LOGGER.warning("Shutting Down old RequestMapper."); oldMapper.shutdown(); } activeState = startState; } } public void run() { LOGGER.info("RequestFilter.UpdateThread is alive."); while (true) { try { if((activeState == null) || fileSet.isChanged(activeState)) { reloadMapper(); } sleep(runInterval); } catch (InterruptedException e) { LOGGER.info("Shutting Down."); return; } } } } }