001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.main;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.i18n.CmsMessageContainer;
035import org.opencms.site.CmsSite;
036import org.opencms.staticexport.CmsStaticExportData;
037import org.opencms.staticexport.CmsStaticExportRequest;
038import org.opencms.util.CmsRequestUtil;
039import org.opencms.util.CmsStringUtil;
040
041import java.io.IOException;
042
043import javax.servlet.ServletConfig;
044import javax.servlet.ServletException;
045import javax.servlet.http.HttpServlet;
046import javax.servlet.http.HttpServletRequest;
047import javax.servlet.http.HttpServletResponse;
048
049import org.apache.commons.logging.Log;
050
051/**
052 * This the main servlet of the OpenCms system.<p>
053 *
054 * From here, all operations that are results of HTTP requests are invoked.
055 * Any incoming request is handled in multiple steps:
056 *
057 * <ol><li>The requesting <code>{@link org.opencms.file.CmsUser}</code> is authenticated
058 * and a <code>{@link org.opencms.file.CmsObject}</code> with this users context information
059 * is created. This <code>{@link org.opencms.file.CmsObject}</code> is used to access all functions of OpenCms, limited by
060 * the authenticated users permissions. If the user is not identified, it is set to the default user, usually named "Guest".</li>
061 *
062 * <li>The requested <code>{@link org.opencms.file.CmsResource}</code> is loaded into OpenCms and depending on its type
063 * (and the users persmissions to display or modify it),
064 * it is send to one of the OpenCms <code>{@link org.opencms.loader.I_CmsResourceLoader}</code> implementations
065 * do be processed.</li>
066 *
067 * <li>
068 * The <code>{@link org.opencms.loader.I_CmsResourceLoader}</code> will then decide what to do with the
069 * contents of the requested <code>{@link org.opencms.file.CmsResource}</code>.
070 * In case of a JSP resource the JSP handling mechanism is invoked with the <code>{@link org.opencms.loader.CmsJspLoader}</code>,
071 * in case of an image (or another static resource) this will be returned by the <code>{@link org.opencms.loader.CmsDumpLoader}</code>
072 * etc.
073 * </li></ol>
074 *
075 * @since 6.0.0
076 *
077 * @see org.opencms.main.CmsShell
078 * @see org.opencms.file.CmsObject
079 * @see org.opencms.main.OpenCms
080 */
081public class OpenCmsServlet extends HttpServlet implements I_CmsRequestHandler {
082
083    /** The current request in a threadlocal. */
084    public static final ThreadLocal<HttpServletRequest> currentRequest = new ThreadLocal<HttpServletRequest>();
085
086    /** GWT RPC services suffix. */
087    public static final String HANDLE_GWT = ".gwt";
088
089    /** Handler prefix. */
090    public static final String HANDLE_PATH = "/handle";
091
092    /** Name of the <code>DefaultWebApplication</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */
093    public static final String SERVLET_PARAM_DEFAULT_WEB_APPLICATION = "DefaultWebApplication";
094
095    /** Name of the <code>OpenCmsHome</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */
096    public static final String SERVLET_PARAM_OPEN_CMS_HOME = "OpenCmsHome";
097
098    /** Name of the <code>OpenCmsServlet</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */
099    public static final String SERVLET_PARAM_OPEN_CMS_SERVLET = "OpenCmsServlet";
100
101    /** Name of the <code>WebApplicationContext</code> parameter in the <code>web.xml</code> OpenCms servlet configuration. */
102    public static final String SERVLET_PARAM_WEB_APPLICATION_CONTEXT = "WebApplicationContext";
103
104    /** Path to handler "error page" files in the VFS. */
105    private static final String HANDLE_VFS_PATH = "/system/handler" + HANDLE_PATH;
106
107    /** Handler "error page" file suffix. */
108    private static final String HANDLE_VFS_SUFFIX = ".html";
109
110    /** Handler implementation names. */
111    private static final String[] HANDLER_NAMES = {"404"};
112
113    /** The log object for this class. */
114    private static final Log LOG = CmsLog.getLog(OpenCmsServlet.class);
115
116    /** Serial version UID required for safe serialization. */
117    private static final long serialVersionUID = 4729951599966070050L;
118
119    /**
120     * OpenCms servlet main request handling method.<p>
121     *
122     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
123     */
124    @Override
125    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
126
127        currentRequest.set(req);
128        try {
129
130            // check to OpenCms runlevel
131            int runlevel = OpenCmsCore.getInstance().getRunLevel();
132
133            // write OpenCms server identification in the response header
134            res.setHeader(CmsRequestUtil.HEADER_SERVER, OpenCmsCore.getInstance().getSystemInfo().getVersion());
135
136            if (runlevel != OpenCms.RUNLEVEL_4_SERVLET_ACCESS) {
137                // not the "normal" servlet runlevel
138                if (runlevel == OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
139                    // we have shell runlevel only, upgrade to servlet runlevel (required after setup wizard)
140                    init(getServletConfig());
141                } else {
142                    // illegal runlevel, we can't process requests
143                    // sending status code 403, indicating the server understood the request but refused to fulfill it
144                    res.sendError(HttpServletResponse.SC_FORBIDDEN);
145                    // goodbye
146                    return;
147                }
148            }
149
150            String path = OpenCmsCore.getInstance().getPathInfo(req);
151            if (path.startsWith(HANDLE_PATH)) {
152                // this is a request to an OpenCms handler URI
153                invokeHandler(req, res);
154            } else if (path.endsWith(HANDLE_GWT)) {
155                // handle GWT rpc services
156                String serviceName = CmsResource.getName(path);
157                serviceName = serviceName.substring(0, serviceName.length() - HANDLE_GWT.length());
158                OpenCmsCore.getInstance().invokeGwtService(serviceName, req, res, getServletConfig());
159            } else {
160                // standard request to a URI in the OpenCms VFS
161                OpenCmsCore.getInstance().showResource(req, res);
162            }
163        } finally {
164            currentRequest.remove();
165        }
166    }
167
168    /**
169     * OpenCms servlet POST request handling method,
170     * will just call {@link #doGet(HttpServletRequest, HttpServletResponse)}.<p>
171     *
172     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
173     */
174    @Override
175    public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
176
177        doGet(req, res);
178    }
179
180    /**
181     * @see org.opencms.main.I_CmsRequestHandler#getHandlerNames()
182     */
183    public String[] getHandlerNames() {
184
185        return HANDLER_NAMES;
186    }
187
188    /**
189     * @see org.opencms.main.I_CmsRequestHandler#handle(HttpServletRequest, HttpServletResponse, String)
190     */
191    public void handle(HttpServletRequest req, HttpServletResponse res, String name)
192    throws IOException, ServletException {
193
194        int errorCode;
195        try {
196            errorCode = Integer.valueOf(name).intValue();
197        } catch (NumberFormatException nf) {
198            res.sendError(HttpServletResponse.SC_FORBIDDEN);
199            LOG.debug("Error parsing handler name.", nf);
200            return;
201        }
202        switch (errorCode) {
203            case 404:
204                CmsObject cms = null;
205                CmsStaticExportData exportData = null;
206                try {
207                    // this will be set in the root site
208                    cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserExport());
209                    exportData = OpenCms.getStaticExportManager().getExportData(req, cms);
210                } catch (CmsException e) {
211                    // unlikely to happen
212                    if (LOG.isWarnEnabled()) {
213                        LOG.warn(
214                            Messages.get().getBundle().key(
215                                Messages.LOG_INIT_CMSOBJECT_IN_HANDLER_2,
216                                name,
217                                OpenCmsCore.getInstance().getPathInfo(req)),
218                            e);
219                    }
220                }
221                if (exportData != null) {
222                    try {
223                        // generate a static export request wrapper
224                        CmsStaticExportRequest exportReq = new CmsStaticExportRequest(req, exportData);
225                        // export the resource and set the response status according to the result
226                        res.setStatus(OpenCms.getStaticExportManager().export(exportReq, res, cms, exportData));
227                    } catch (Throwable t) {
228                        if (LOG.isWarnEnabled()) {
229                            LOG.warn(Messages.get().getBundle().key(Messages.LOG_ERROR_EXPORT_1, exportData), t);
230                        }
231                        openErrorHandler(req, res, errorCode);
232                    }
233                } else {
234                    openErrorHandler(req, res, errorCode);
235                }
236                break;
237            default:
238                openErrorHandler(req, res, errorCode);
239        }
240    }
241
242    /**
243     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
244     */
245    @Override
246    public synchronized void init(ServletConfig config) throws ServletException {
247
248        super.init(config);
249        try {
250            // upgrade the runlevel
251            // usually this should have already been done by the context listener
252            // however, after a fresh install / setup this will be done from here
253            OpenCmsCore.getInstance().upgradeRunlevel(config.getServletContext());
254            // finalize OpenCms initialization
255            OpenCmsCore.getInstance().initServlet(this);
256        } catch (CmsInitException e) {
257            if (Messages.ERR_CRITICAL_INIT_WIZARD_0.equals(e.getMessageContainer().getKey())) {
258                // if wizard is still enabled - allow retry of initialization (required for setup wizard)
259                // this means the servlet init() call must be terminated by an exception
260                if (CmsServletContainerSettings.isServletThrowsException()) {
261                    throw new ServletException(e.getMessage());
262                } else {
263                    // this is needed since some servlet containers does not like the servlet to throw exceptions,
264                    // like BEA WLS 9.x and Resin
265                    LOG.error(Messages.get().getBundle().key(Messages.LOG_ERROR_GENERIC_0), e);
266                }
267            }
268        } catch (Throwable t) {
269            LOG.error(Messages.get().getBundle().key(Messages.LOG_ERROR_GENERIC_0), t);
270        }
271    }
272
273    /**
274     * Manages requests to internal OpenCms request handlers.<p>
275     *
276     * @param req the current request
277     * @param res the current response
278     * @throws ServletException in case an error occurs
279     * @throws ServletException in case an error occurs
280     * @throws IOException in case an error occurs
281     */
282    protected void invokeHandler(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
283
284        String name = OpenCmsCore.getInstance().getPathInfo(req).substring(HANDLE_PATH.length());
285        I_CmsRequestHandler handler = OpenCmsCore.getInstance().getRequestHandler(name);
286        if ((handler == null) && name.contains("/")) {
287            // if the name contains a '/', also check for handlers matching the first path fragment only
288            name = name.substring(0, name.indexOf("/"));
289            handler = OpenCmsCore.getInstance().getRequestHandler(name);
290        }
291        if (handler != null) {
292            handler.handle(req, res, name);
293        } else {
294            openErrorHandler(req, res, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
295        }
296    }
297
298    /**
299     * Displays an error code handler loaded from the OpenCms VFS,
300     * or if such a page does not exist,
301     * displays the default servlet container error code.<p>
302     *
303     * @param req the current request
304     * @param res the current response
305     * @param errorCode the error code to display
306     * @throws IOException if something goes wrong
307     * @throws ServletException if something goes wrong
308     */
309    protected void openErrorHandler(HttpServletRequest req, HttpServletResponse res, int errorCode)
310    throws IOException, ServletException {
311
312        String handlerUri = (new StringBuffer(64)).append(HANDLE_VFS_PATH).append(errorCode).append(
313            HANDLE_VFS_SUFFIX).toString();
314        // provide the original error code in a request attribute
315        req.setAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE, new Integer(errorCode));
316        CmsObject cms;
317        CmsFile file;
318        try {
319            // create OpenCms context, this will be set in the root site
320            cms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest());
321            cms.getRequestContext().setSecureRequest(OpenCms.getSiteManager().usesSecureSite(req));
322        } catch (CmsException e) {
323            // unlikely to happen as the OpenCms "Guest" context can always be initialized
324            CmsMessageContainer container = Messages.get().container(
325                Messages.LOG_INIT_CMSOBJECT_IN_HANDLER_2,
326                new Integer(errorCode),
327                handlerUri);
328            if (LOG.isWarnEnabled()) {
329                LOG.warn(org.opencms.jsp.Messages.getLocalizedMessage(container, req), e);
330            }
331            // however, if it _does_ happen, then we really can't continue here
332            if (!res.isCommitted()) {
333                // since the handler file is not accessible, display the default error page
334                res.sendError(errorCode, e.getLocalizedMessage());
335            }
336            return;
337        }
338        try {
339            if (!tryCustomErrorPage(cms, req, res, errorCode)) {
340                cms.getRequestContext().setUri(handlerUri);
341                cms.getRequestContext().setSecureRequest(OpenCms.getSiteManager().usesSecureSite(req));
342                // read the error handler file
343                file = cms.readFile(handlerUri, CmsResourceFilter.IGNORE_EXPIRATION);
344                OpenCms.getResourceManager().loadResource(cms, file, req, res);
345            }
346        } catch (CmsException e) {
347            // unable to load error page handler VFS resource
348            CmsMessageContainer container = Messages.get().container(
349                Messages.ERR_SHOW_ERR_HANDLER_RESOURCE_2,
350                new Integer(errorCode),
351                handlerUri);
352            throw new ServletException(org.opencms.jsp.Messages.getLocalizedMessage(container, req), e);
353        }
354    }
355
356    /**
357     * Tries to load the custom error page at the given rootPath.
358     * @param cms {@link CmsObject} used for reading the resource (site root and uri get adjusted!)
359     * @param req the current request
360     * @param res the current response
361     * @param rootPath the VFS root path to the error page resource
362     * @return a flag, indicating if the error page could be loaded
363     */
364    private boolean loadCustomErrorPage(
365        CmsObject cms,
366        HttpServletRequest req,
367        HttpServletResponse res,
368        String rootPath) {
369
370        try {
371
372            // get the site of the error page resource
373            CmsSite errorSite = OpenCms.getSiteManager().getSiteForRootPath(rootPath);
374            cms.getRequestContext().setSiteRoot(errorSite.getSiteRoot());
375            String relPath = cms.getRequestContext().removeSiteRoot(rootPath);
376            if (cms.existsResource(relPath)) {
377                cms.getRequestContext().setUri(relPath);
378                OpenCms.getResourceManager().loadResource(cms, cms.readResource(relPath), req, res);
379                return true;
380            } else {
381                return false;
382            }
383        } catch (Throwable e) {
384            // something went wrong log the exception and return false
385            LOG.error(e.getMessage(), e);
386            return false;
387        }
388    }
389
390    /**
391     * Tries to load a site specific error page. If
392     * @param cms {@link CmsObject} used for reading the resource (site root and uri get adjusted!)
393     * @param req the current request
394     * @param res the current response
395     * @param errorCode the error code to display
396     * @return a flag, indicating if the custom error page could be loaded.
397     */
398    private boolean tryCustomErrorPage(CmsObject cms, HttpServletRequest req, HttpServletResponse res, int errorCode) {
399
400        String siteRoot = OpenCms.getSiteManager().matchRequest(req).getSiteRoot();
401        CmsSite site = OpenCms.getSiteManager().getSiteForSiteRoot(siteRoot);
402        if (site != null) {
403            // store current site root and URI
404            String currentSiteRoot = cms.getRequestContext().getSiteRoot();
405            String currentUri = cms.getRequestContext().getUri();
406            try {
407                if (site.getErrorPage() != null) {
408                    String rootPath = site.getErrorPage();
409                    if (loadCustomErrorPage(cms, req, res, rootPath)) {
410                        return true;
411                    }
412                }
413                String rootPath = CmsStringUtil.joinPaths(siteRoot, "/.errorpages/handle" + errorCode + ".html");
414                if (loadCustomErrorPage(cms, req, res, rootPath)) {
415                    return true;
416                }
417            } finally {
418                cms.getRequestContext().setSiteRoot(currentSiteRoot);
419                cms.getRequestContext().setUri(currentUri);
420            }
421        }
422        return false;
423    }
424}