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.PathDescriptor;
039import org.nuxeo.ecm.webengine.WebEngine;
040import org.nuxeo.ecm.webengine.model.WebContext;
041import org.nuxeo.ecm.webengine.model.impl.AbstractWebContext;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.runtime.transaction.TransactionHelper;
044import org.nuxeo.runtime.transaction.TransactionRuntimeException;
045
046/**
047 * This filter must be declared after the nuxeo authentication filter since it needs an authentication info. The session
048 * synchronization is done only if NuxeoRequestControllerFilter was not already done it and stateful flag for the
049 * request path is true.
050 */
051public class WebEngineFilter implements Filter {
052
053    protected WebEngine engine;
054
055    protected boolean isAutoTxEnabled;
056
057    protected boolean isStatefull;
058
059    protected static Log log = LogFactory.getLog(WebEngineFilter.class);
060
061    @Override
062    public void init(FilterConfig filterConfig) throws ServletException {
063        initIfNeeded();
064    }
065
066    protected void initIfNeeded() {
067        if (engine != null || Framework.getRuntime() == null) {
068            return;
069        }
070        engine = Framework.getLocalService(WebEngine.class);
071    }
072
073    @Override
074    public void destroy() {
075        engine = null;
076    }
077
078    @Override
079    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
080            ServletException {
081        initIfNeeded();
082        if (request instanceof HttpServletRequest) {
083            HttpServletRequest req = (HttpServletRequest) request;
084            HttpServletResponse resp = (HttpServletResponse) response;
085            PathDescriptor pd = engine.getRequestConfiguration().getMatchingConfiguration(req);
086            Config config = new Config(req, pd);
087            AbstractWebContext ctx = initRequest(config, req, resp);
088            if (config.txStarted) {
089                resp = new BufferingHttpServletResponse(resp);
090            }
091            boolean completedAbruptly = true;
092            try {
093                preRequest(req, resp);
094                chain.doFilter(request, resp);
095                postRequest(req, resp);
096                completedAbruptly = false;
097            } finally {
098                if (completedAbruptly) {
099                    TransactionHelper.setTransactionRollbackOnly();
100                }
101                try {
102                    cleanup(config, ctx, req, resp);
103                } catch (TransactionRuntimeException e) {
104                    // commit failed, report this to the client before stopping buffering
105                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
106                    throw e;
107                } finally {
108                    if (config.txStarted) {
109                        ((BufferingHttpServletResponse) resp).stopBuffering();
110                    }
111                }
112            }
113        } else {
114            chain.doFilter(request, response);
115        }
116    }
117
118    public void preRequest(HttpServletRequest request, HttpServletResponse response) {
119        // need to set the encoding of characters manually
120        if (request.getCharacterEncoding() == null) {
121            try {
122                request.setCharacterEncoding("UTF-8");
123            } catch (UnsupportedEncodingException e) {
124                throw new RuntimeException(e);
125            }
126        }
127    }
128
129    public void postRequest(HttpServletRequest request, HttpServletResponse response) {
130        // check if the target resource don't want automatic headers to be
131        // inserted
132        if (null != request.getAttribute("org.nuxeo.webengine.DisableAutoHeaders")) {
133            // insert automatic headers
134            response.addHeader("Pragma", "no-cache");
135            response.addHeader("Cache-Control", "no-cache");
136            response.addHeader("Cache-Control", "no-store");
137            response.addHeader("Cache-Control", "must-revalidate");
138            response.addHeader("Expires", "0");
139            response.setDateHeader("Expires", 0); // prevents caching
140        }
141    }
142
143    public AbstractWebContext initRequest(Config config, HttpServletRequest request, HttpServletResponse response) {
144        initTx(config, request);
145        // user session is registered even for static resources - because some
146        // static resources are served by JAX-RS resources that needs a user
147        // session
148        DefaultContext ctx = new DefaultContext((HttpServletRequest) request);
149        request.setAttribute(WebContext.class.getName(), ctx);
150        return ctx;
151    }
152
153    public void cleanup(Config config, AbstractWebContext ctx, HttpServletRequest request, HttpServletResponse response) {
154        try {
155            closeTx(config, request);
156        } finally {
157            request.removeAttribute(WebContext.class.getName());
158        }
159    }
160
161    public void initTx(Config config, HttpServletRequest req) {
162        if (!config.isStatic && config.autoTx && !TransactionHelper.isTransactionActive()) {
163            config.txStarted = ServletHelper.startTransaction(req);
164        }
165    }
166
167    public void closeTx(Config config, HttpServletRequest req) {
168        if (config.txStarted) {
169            TransactionHelper.commitOrRollbackTransaction();
170        }
171    }
172
173    protected static class Config {
174        boolean autoTx;
175
176        boolean txStarted;
177
178        boolean locked;
179
180        boolean isStatic;
181
182        String pathInfo;
183
184        public Config(HttpServletRequest req, PathDescriptor pd) {
185            autoTx = pd == null ? true : pd.isAutoTx(true);
186            pathInfo = req.getPathInfo();
187            if (pathInfo == null || pathInfo.length() == 0) {
188                pathInfo = "/";
189            }
190            String spath = req.getServletPath();
191            isStatic = spath.contains("/skin") || pathInfo.contains("/skin/");
192        }
193
194        @Override
195        public String toString() {
196            StringBuffer sb = new StringBuffer();
197            sb.append("WebEngineFilter&Confi:");
198            sb.append("\nPath Info:");
199            sb.append(pathInfo);
200            sb.append("\nAuto TX:");
201            sb.append(autoTx);
202            sb.append("\nStatic:");
203            sb.append(isStatic);
204            return sb.toString();
205        }
206    }
207}