/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 io.milton.servlet; import io.milton.config.HttpManagerBuilder; import io.milton.http.HttpManager; import io.milton.http.Request; import io.milton.http.ResourceFactory; import io.milton.http.Response; import io.milton.http.annotated.AnnotationResourceFactory; import io.milton.http.template.JspViewResolver; import io.milton.http.template.ViewResolver; import io.milton.mail.MailServer; import io.milton.mail.MailServerBuilder; import java.io.File; import java.io.IOException; 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.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; /** * Loads the spring context either from a spring configuration XML file or a * spring @Configuration class. * * <p> * Use {@code contextConfigClass} to define the @Configuration class or * {@code contextConfigLocation} to define the Spring XML configuration file. If * any of them is defined, {@link SpringMiltonFilter} will try to load a file * named applicationContext.xml from the classpath. * <br>If it still fails, only the parent context will be considered. * * <p> * This filter then gets the bean named milton.http.manager and uses that for * Milton processing. * * <p> * The milton.http.manager bean can either be a {@link HttpManager} or it can be * a {@link HttpManagerBuilder}, in which case a {@link HttpManager} is * constructed from it * * <p> * Requests with a path which begins with one of the exclude paths will not be * processed by Milton. Instead, for these requests, the filter chain will be * invoked so the request can be serviced by JSP or a servlet, etc * * <p> * This uses an init parameter called {@code milton.exclude.paths}, which should * be a comma separated list of paths to ignore. For example: * * <pre>/static,/images,/login.jsp</pre> * * <p> * This allows non-Milton resources to be accessed, while still mapping all URLs * to Milton * * @author bradm */ public class SpringMiltonFilter implements javax.servlet.Filter { private static final Logger log = LoggerFactory.getLogger(SpringMiltonFilter.class); private ConfigurableApplicationContext context; private HttpManager httpManager; private MailServer mailServer; private ServletContext servletContext; /** * Resources with this as the first part of their path will not be served * from Milton. Instead, this filter will allow filter processing to * continue so they will be served by JSP or a servlet */ private String[] excludeMiltonPaths; @Override public void init(FilterConfig fc) throws ServletException { log.info("init"); initSpringApplicationContext(fc); servletContext = fc.getServletContext(); String sExcludePaths = fc.getInitParameter(EXCLUDE_PATHS_SYSPROP); if (sExcludePaths != null) { log.info("init: exclude paths: " + sExcludePaths); excludeMiltonPaths = sExcludePaths.split(","); } else { log.info("init: exclude paths property has not been set in filter init param " + EXCLUDE_PATHS_SYSPROP); } Object milton = context.getBean("milton.http.manager"); if (milton instanceof HttpManager) { this.httpManager = (HttpManager) milton; } else if (milton instanceof HttpManagerBuilder) { HttpManagerBuilder builder = (HttpManagerBuilder) milton; ResourceFactory rf = builder.getMainResourceFactory(); if (rf instanceof AnnotationResourceFactory) { AnnotationResourceFactory arf = (AnnotationResourceFactory) rf; if (arf.getViewResolver() == null) { ViewResolver viewResolver = new JspViewResolver(servletContext); arf.setViewResolver(viewResolver); } } this.httpManager = builder.buildHttpManager(); } // init mail server if (context.containsBean("milton.mail.server")) { log.info("init mailserver..."); Object oMailServer = context.getBean("milton.mail.server"); if (oMailServer instanceof MailServer) { mailServer = (MailServer) oMailServer; } else if (oMailServer instanceof MailServerBuilder) { MailServerBuilder builder = (MailServerBuilder) oMailServer; mailServer = builder.build(); } else { throw new RuntimeException("Unsupported type: " + oMailServer.getClass() + " expected " + MailServer.class + " or " + MailServerBuilder.class); } log.info("starting mailserver"); mailServer.start(); } log.info("Finished init"); } private static final String EXCLUDE_PATHS_SYSPROP = "milton.exclude.paths"; @SuppressWarnings("resource") protected void initSpringApplicationContext(FilterConfig fc) { final WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(fc.getServletContext()); StaticApplicationContext parent; if (rootContext != null) { log.info("Found a root spring context, and using it"); parent = new StaticApplicationContext(rootContext); } else { log.info("No root spring context"); parent = new StaticApplicationContext(); } final FilterConfigWrapper configWrapper = new FilterConfigWrapper(fc); parent.getBeanFactory().registerSingleton("config", configWrapper); parent.getBeanFactory().registerSingleton("servletContext", fc.getServletContext()); File webRoot = new File(fc.getServletContext().getRealPath("/")); parent.getBeanFactory().registerSingleton("webRoot", webRoot); log.info("Registered root webapp path in: webroot=" + webRoot.getAbsolutePath()); parent.refresh(); final String configClass = fc.getInitParameter("contextConfigClass"); final String sFiles = fc.getInitParameter("contextConfigLocation"); ConfigurableApplicationContext ctx = null; if (StringUtils.isNotBlank(configClass)) { try { Class<?> clazz = Class.forName(configClass); final AnnotationConfigApplicationContext annotationCtx = new AnnotationConfigApplicationContext(); annotationCtx.setParent(parent); annotationCtx.register(clazz); annotationCtx.refresh(); ctx = annotationCtx; } catch (ClassNotFoundException e) { ctx = null; log.error("Unable to create a child context for Milton", e); } } else { String[] contextFiles; if (sFiles != null && sFiles.trim().length() > 0) { contextFiles = sFiles.split(" "); } else { contextFiles = new String[]{"applicationContext.xml"}; } try { ctx = new ClassPathXmlApplicationContext(contextFiles, parent); } catch (BeansException e) { log.error("Unable to create a child context for Milton", e); } } if (ctx == null) { log.warn("No child context available, only using parent context"); context = parent; } else { context = ctx; } } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc) throws IOException, ServletException { if (req instanceof HttpServletRequest) { HttpServletRequest hsr = (HttpServletRequest) req; String url = hsr.getRequestURI(); // Allow certain paths to be excluded from Milton, these might be other servlets, for example if (excludeMiltonPaths != null) { for (String s : excludeMiltonPaths) { if (url.startsWith(s)) { log.trace("doFilter: is excluded path"); fc.doFilter(req, resp); return; } } } log.trace("doFilter: begin milton processing"); doMiltonProcessing((HttpServletRequest) req, (HttpServletResponse) resp); } else { log.trace("doFilter: request is not a supported type, continue with filter chain"); fc.doFilter(req, resp); return; } } @Override public void destroy() { context.close(); if (httpManager != null) { httpManager.shutdown(); } if (mailServer != null) { mailServer.stop(); } } private void doMiltonProcessing(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { MiltonServlet.setThreadlocals(req, resp); Request request = new io.milton.servlet.ServletRequest(req, servletContext); Response response = new io.milton.servlet.ServletResponse(resp); httpManager.process(request, response); } finally { MiltonServlet.clearThreadlocals(); //resp.getOutputStream().flush(); resp.flushBuffer(); } } }