/** * This file Copyright (c) 2011-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.cms.filters; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.HierarchyManager; import info.magnolia.cms.servlets.ClasspathSpool; import info.magnolia.cms.util.ObservationUtil; import info.magnolia.context.MgnlContext; import info.magnolia.context.SystemContext; import info.magnolia.jcr.node2bean.Node2BeanException; import info.magnolia.jcr.node2bean.Node2BeanProcessor; import info.magnolia.module.ModuleManager; import info.magnolia.repository.RepositoryConstants; import java.util.Collections; import javax.inject.Inject; import javax.inject.Singleton; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import org.apache.commons.lang.StringUtils; /** * Default {@link FilterManager} implementation; uses node2bean and observation * to maintain the filter chain configured at {@value #SERVER_FILTERS}. */ @Singleton public class FilterManagerImpl implements FilterManager { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FilterManagerImpl.class); private final EventListener filtersEventListener = new EventListener() { @Override public void onEvent(EventIterator arg0) { MgnlContext.doInSystemContext(new MgnlContext.VoidOp() { @Override public void doExec() { resetRootFilter(); } }, true); } }; private final ModuleManager moduleManager; private final SystemContext systemContext; private final MgnlFilterDispatcher filterDispatcher = new MgnlFilterDispatcher(); private final Object resetLock = new Object(); private FilterConfig filterConfig; private final Node2BeanProcessor nodeToBean; @Inject public FilterManagerImpl(ModuleManager moduleManager, SystemContext systemContext, Node2BeanProcessor nodeToBean) { this.moduleManager = moduleManager; this.systemContext = systemContext; this.nodeToBean = nodeToBean; } @Override public void init(FilterConfig filterConfig) throws ServletException { // remember this config this.filterConfig = filterConfig; MgnlContext.doInSystemContext(new MgnlContext.VoidOp() { @Override public void doExec() { try { MgnlFilter filter = createRootFilter(); initRootFilter(filter, FilterManagerImpl.this.filterConfig); filterDispatcher.replaceTargetFilter(filter); } catch (ServletException e) { log.error("Error initializing filters", e); return; } if (!isSystemUIMode()) { startObservation(); } } }, true); } @Override public void destroy() { MgnlFilter filter = filterDispatcher.replaceTargetFilter(null); destroyRootFilter(filter); } @Override public MgnlFilterDispatcher getFilterDispatcher() { return filterDispatcher; } @Override public void startUsingConfiguredFilters() { resetRootFilter(); startObservation(); } protected void resetRootFilter() { synchronized (resetLock) { MgnlFilter newFilter; try { newFilter = createRootFilter(); initRootFilter(newFilter, filterConfig); } catch (ServletException e) { log.error("Error initializing filters", e); return; } final MgnlFilter oldFilter = filterDispatcher.replaceTargetFilter(newFilter); // This is executed asynchronously in another thread so we don't risk causing dead lock, see SafeDestroyMgnlFilterWrapper doInSystemContextAsync("FilterChainDisposerThread", new MgnlContext.VoidOp() { @Override public void doExec() { destroyRootFilter(oldFilter); } }, true); } } protected MgnlFilter createRootFilter() throws ServletException { if (isSystemUIMode()) { return createSystemUIFilter(); } return createConfiguredFilters(); } protected void initRootFilter(MgnlFilter rootFilter, FilterConfig filterConfig) throws ServletException { log.info("Initializing filters"); rootFilter.init(filterConfig); if (log.isDebugEnabled()) { printFilters(rootFilter); } } protected void destroyRootFilter(MgnlFilter rootFilter) { if (rootFilter != null) { rootFilter.destroy(); } } private MgnlFilter createConfiguredFilters() throws ServletException { try { final HierarchyManager hm = systemContext.getHierarchyManager(RepositoryConstants.CONFIG); final Content node = hm.getContent(SERVER_FILTERS); MgnlFilter filter = (MgnlFilter) nodeToBean.toBean(node.getJCRNode(), MgnlFilter.class); if (filter == null) { throw new ServletException("Unable to create filter objects"); } return filter; } catch (PathNotFoundException e) { throw new ServletException("No filters configured at " + SERVER_FILTERS); } catch (RepositoryException e) { throw new ServletException("Can't read filter definitions", e); } catch (Node2BeanException e) { throw new ServletException("Can't create filter objects", e); } } /** * Initializes the required filter(s) if we need to go through * SystemUI initialization screens. */ protected MgnlFilter createSystemUIFilter() { final CompositeFilter systemUIFilter = new CompositeFilter(); final ServletDispatchingFilter classpathSpoolFilter = new ServletDispatchingFilter(); classpathSpoolFilter.setName("resources"); classpathSpoolFilter.setServletName("ClasspathSpool Servlet"); classpathSpoolFilter.setServletClass(ClasspathSpool.class.getName()); classpathSpoolFilter.addMapping("/.resources/*"); classpathSpoolFilter.addMapping("/favicon.ico"); classpathSpoolFilter.setParameters(Collections.emptyMap()); classpathSpoolFilter.setEnabled(true); systemUIFilter.addFilter(classpathSpoolFilter); final InstallFilter installFilter = new InstallFilter(moduleManager, this); installFilter.setName("install"); systemUIFilter.addFilter(installFilter); return systemUIFilter; } /** * Checks if Magnolia is ready to operate or if we need to go through * SystemUI initialization screens. */ protected boolean isSystemUIMode() { return moduleManager.getStatus().needsUpdateOrInstall(); } protected void startObservation() { ObservationUtil.registerDeferredChangeListener( RepositoryConstants.CONFIG, SERVER_FILTERS, filtersEventListener, 1000, 5000); } private void printFilters(MgnlFilter rootFilter) { log.debug("Here is the root filter as configured:"); printFilter(0, rootFilter); } private void printFilter(int indentation, MgnlFilter filter) { log.debug("{}{} ({})", new Object[]{StringUtils.repeat(" ", indentation), filter.getName(), filter.toString()}); if (filter instanceof CompositeFilter) { for (MgnlFilter nestedFilter : ((CompositeFilter) filter).getFilters()) { printFilter(indentation + 2, nestedFilter); } } } private <T, E extends Throwable> void doInSystemContextAsync(final String threadName, final MgnlContext.Op<T, E> op, final boolean releaseAfterExecution) { new Thread() { { setName(threadName); } @Override public void run() { try { MgnlContext.doInSystemContext(op, releaseAfterExecution); } catch (Throwable e) { log.error("Exception caught when executing asynchronous operation: " + e.getMessage(), e); } } }.start(); } }