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.lang3.StringUtils;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.platform.web.common.ServletHelper;
038import org.nuxeo.ecm.platform.web.common.requestcontroller.filter.BufferingHttpServletResponse;
039import org.nuxeo.ecm.webengine.WebEngine;
040import org.nuxeo.ecm.webengine.model.WebContext;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.runtime.transaction.TransactionHelper;
043
044/**
045 * This filter must be declared after the nuxeo authentication filter since it needs an authentication info. The session
046 * synchronization is done only if NuxeoRequestControllerFilter was not already done it and stateful flag for the
047 * request path is true.
048 */
049public class WebEngineFilter implements Filter {
050
051    protected WebEngine engine;
052
053    protected boolean isAutoTxEnabled;
054
055    protected boolean isStatefull;
056
057    protected static Log log = LogFactory.getLog(WebEngineFilter.class);
058
059    @Override
060    public void init(FilterConfig filterConfig) throws ServletException {
061        engine = Framework.getService(WebEngine.class);
062    }
063
064    @Override
065    public void destroy() {
066        engine = null;
067    }
068
069    @Override
070    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
071            throws IOException, ServletException {
072        if (!(request instanceof HttpServletRequest)) {
073            chain.doFilter(request, response);
074            return;
075        }
076        new UnitOfWork((HttpServletRequest) request, (HttpServletResponse)response).doFilter(chain);
077    }
078
079    private static class UnitOfWork {
080
081        private final boolean txStarted;
082
083        private final boolean isStatic;
084
085        private final String pathInfo;
086
087        private final DefaultContext context;
088
089        private UnitOfWork(HttpServletRequest req, HttpServletResponse resp) throws IOException {
090            pathInfo = StringUtils.isEmpty(req.getPathInfo()) ? "/" :  req.getPathInfo();
091            isStatic = req.getServletPath().contains("/skin") || pathInfo.contains("/skin/");
092            txStarted = !isStatic && !TransactionHelper.isTransactionActive()
093                    && ServletHelper.startTransaction(req);
094            context = new DefaultContext(req, txStarted ? new BufferingHttpServletResponse(resp) : resp);
095            req.setAttribute(WebContext.class.getName(), context);
096        }
097
098        private void doFilter(FilterChain chain) throws ServletException, IOException {
099            boolean completedAbruptly = true;
100            try {
101                preRequest();
102                chain.doFilter(context.getRequest(), context.getResponse());
103                postRequest();
104                completedAbruptly = false;
105            } catch (IOException | ServletException | RuntimeException error) {
106                context.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
107                        error.getMessage());
108                throw error;
109            } finally {
110                cleanup(completedAbruptly);
111            }
112        }
113
114        @Override
115        public String toString() {
116            StringBuffer sb = new StringBuffer();
117            sb.append("WebEngine Filter:");
118            sb.append("\nPath Info:");
119            sb.append(pathInfo);
120            sb.append("\nStatic:");
121            sb.append(isStatic);
122            return sb.toString();
123        }
124
125        void cleanup(boolean completedAbruptly) throws IOException {
126            context.getRequest().removeAttribute(WebContext.class.getName());
127
128            if (!txStarted) {
129                return;
130            }
131
132            if (completedAbruptly) {
133                TransactionHelper.setTransactionRollbackOnly();
134            }
135            try {
136                TransactionHelper.commitOrRollbackTransaction();
137            } catch (RuntimeException cause) {
138                context.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, cause.getMessage());
139            } finally {
140                ((BufferingHttpServletResponse) context.getResponse()).stopBuffering();
141            }
142        }
143
144        void preRequest() {
145            // need to set the encoding of characters manually
146            HttpServletRequest request = context.getRequest();
147            if (request.getCharacterEncoding() == null) {
148                try {
149                    request.setCharacterEncoding("UTF-8");
150                } catch (UnsupportedEncodingException e) {
151                    throw new RuntimeException(e);
152                }
153            }
154        }
155
156        void postRequest() {
157            HttpServletRequest request = context.getRequest();
158            HttpServletResponse response = context.getResponse();
159            // check if the target resource don't want automatic headers to be
160            // inserted
161            if (null != request.getAttribute("org.nuxeo.webengine.DisableAutoHeaders")) {
162                // insert automatic headers
163                response.addHeader("Pragma", "no-cache");
164                response.addHeader("Cache-Control", "no-cache");
165                response.addHeader("Cache-Control", "no-store");
166                response.addHeader("Cache-Control", "must-revalidate");
167                response.addHeader("Expires", "0");
168                response.setDateHeader("Expires", 0); // prevents caching
169            }
170        }
171    }
172}