001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.webengine.app;
021
022import java.io.IOException;
023import java.io.UnsupportedEncodingException;
024
025import javax.servlet.Filter;
026import javax.servlet.FilterChain;
027import javax.servlet.FilterConfig;
028import javax.servlet.ServletException;
029import javax.servlet.ServletRequest;
030import javax.servlet.ServletResponse;
031import javax.servlet.http.HttpServletRequest;
032import javax.servlet.http.HttpServletResponse;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.ecm.platform.web.common.ServletHelper;
037import org.nuxeo.ecm.platform.web.common.requestcontroller.filter.BufferingHttpServletResponse;
038import org.nuxeo.ecm.webengine.WebEngine;
039import org.nuxeo.ecm.webengine.model.WebContext;
040import org.nuxeo.ecm.webengine.model.impl.AbstractWebContext;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.runtime.transaction.TransactionHelper;
043import org.nuxeo.runtime.transaction.TransactionRuntimeException;
044
045/**
046 * This filter must be declared after the nuxeo authentication filter since it needs an authentication info. The session
047 * synchronization is done only if NuxeoRequestControllerFilter was not already done it and stateful flag for the
048 * request path is true.
049 */
050public class WebEngineFilter implements Filter {
051
052    protected WebEngine engine;
053
054    protected boolean isAutoTxEnabled;
055
056    protected boolean isStatefull;
057
058    protected static Log log = LogFactory.getLog(WebEngineFilter.class);
059
060    @Override
061    public void init(FilterConfig filterConfig) throws ServletException {
062        initIfNeeded();
063    }
064
065    protected void initIfNeeded() {
066        if (engine != null || Framework.getRuntime() == null) {
067            return;
068        }
069        engine = Framework.getLocalService(WebEngine.class);
070    }
071
072    @Override
073    public void destroy() {
074        engine = null;
075    }
076
077    @Override
078    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
079            ServletException {
080        initIfNeeded();
081        if (request instanceof HttpServletRequest) {
082            HttpServletRequest req = (HttpServletRequest) request;
083            HttpServletResponse resp = (HttpServletResponse) response;
084            Config config = new Config(req);
085            AbstractWebContext ctx = initRequest(config, req, resp);
086            if (config.txStarted) {
087                resp = new BufferingHttpServletResponse(resp);
088            }
089            boolean completedAbruptly = true;
090            try {
091                preRequest(req, resp);
092                chain.doFilter(request, resp);
093                postRequest(req, resp);
094                completedAbruptly = false;
095            } finally {
096                if (completedAbruptly) {
097                    TransactionHelper.setTransactionRollbackOnly();
098                }
099                try {
100                    cleanup(config, ctx, req, resp);
101                } catch (TransactionRuntimeException e) {
102                    // commit failed, report this to the client before stopping buffering
103                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
104                    throw e;
105                } finally {
106                    if (config.txStarted) {
107                        ((BufferingHttpServletResponse) resp).stopBuffering();
108                    }
109                }
110            }
111        } else {
112            chain.doFilter(request, response);
113        }
114    }
115
116    public void preRequest(HttpServletRequest request, HttpServletResponse response) {
117        // need to set the encoding of characters manually
118        if (request.getCharacterEncoding() == null) {
119            try {
120                request.setCharacterEncoding("UTF-8");
121            } catch (UnsupportedEncodingException e) {
122                throw new RuntimeException(e);
123            }
124        }
125    }
126
127    public void postRequest(HttpServletRequest request, HttpServletResponse response) {
128        // check if the target resource don't want automatic headers to be
129        // inserted
130        if (null != request.getAttribute("org.nuxeo.webengine.DisableAutoHeaders")) {
131            // insert automatic headers
132            response.addHeader("Pragma", "no-cache");
133            response.addHeader("Cache-Control", "no-cache");
134            response.addHeader("Cache-Control", "no-store");
135            response.addHeader("Cache-Control", "must-revalidate");
136            response.addHeader("Expires", "0");
137            response.setDateHeader("Expires", 0); // prevents caching
138        }
139    }
140
141    public AbstractWebContext initRequest(Config config, HttpServletRequest request, HttpServletResponse response) {
142        initTx(config, request);
143        // user session is registered even for static resources - because some
144        // static resources are served by JAX-RS resources that needs a user
145        // session
146        DefaultContext ctx = new DefaultContext((HttpServletRequest) request);
147        request.setAttribute(WebContext.class.getName(), ctx);
148        return ctx;
149    }
150
151    public void cleanup(Config config, AbstractWebContext ctx, HttpServletRequest request, HttpServletResponse response) {
152        try {
153            closeTx(config, request);
154        } finally {
155            request.removeAttribute(WebContext.class.getName());
156        }
157    }
158
159    public void initTx(Config config, HttpServletRequest req) {
160        if (!config.isStatic && !TransactionHelper.isTransactionActive()) {
161            config.txStarted = ServletHelper.startTransaction(req);
162        }
163    }
164
165    public void closeTx(Config config, HttpServletRequest req) {
166        if (config.txStarted) {
167            TransactionHelper.commitOrRollbackTransaction();
168        }
169    }
170
171    protected static class Config {
172
173        boolean txStarted;
174
175        boolean isStatic;
176
177        String pathInfo;
178
179        public Config(HttpServletRequest req) {
180            pathInfo = req.getPathInfo();
181            if (pathInfo == null || pathInfo.length() == 0) {
182                pathInfo = "/";
183            }
184            String spath = req.getServletPath();
185            isStatic = spath.contains("/skin") || pathInfo.contains("/skin/");
186        }
187
188        @Override
189        public String toString() {
190            StringBuffer sb = new StringBuffer();
191            sb.append("WebEngineFilter&Confi:");
192            sb.append("\nPath Info:");
193            sb.append(pathInfo);
194            sb.append("\nStatic:");
195            sb.append(isStatic);
196            return sb.toString();
197        }
198    }
199}