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.util.A_CmsModeStringEnumeration;
031import org.opencms.util.CmsFileUtil;
032import org.opencms.util.CmsStringUtil;
033
034import java.io.File;
035import java.net.MalformedURLException;
036import java.net.URL;
037
038import javax.servlet.ServletContext;
039
040import org.apache.commons.logging.Log;
041
042/**
043 * Stores specific servlet container options, that might influence OpenCms behavior.<p>
044 *
045 * @since 7.0.5
046 */
047public class CmsServletContainerSettings {
048
049    /**
050     *  Enumeration class for the configuration mode.<p>
051     */
052    public static final class CmsServletContainerCfgMode extends A_CmsModeStringEnumeration {
053
054        /** Auto configuration mode. */
055        protected static final CmsServletContainerCfgMode MODE_AUTO = new CmsServletContainerCfgMode("auto");
056
057        /** Manual configuration mode. */
058        protected static final CmsServletContainerCfgMode MODE_MANUAL = new CmsServletContainerCfgMode("manual");
059
060        /** No set configuration mode. */
061        protected static final CmsServletContainerCfgMode MODE_NONE = new CmsServletContainerCfgMode("none");
062
063        /** Version id required for safe serialization. */
064        private static final long serialVersionUID = -8191582624108081577L;
065
066        /**
067         * Private constructor.<p>
068         *
069         * @param mode the remote command execution return type integer representation
070         */
071        private CmsServletContainerCfgMode(String mode) {
072
073            super(mode);
074        }
075
076        /**
077         * Returns the parsed mode object if the string representation matches, or <code>null</code> if not.<p>
078         *
079         * @param mode the string representation to parse
080         *
081         * @return the parsed mode object
082         */
083        public static CmsServletContainerCfgMode valueOf(String mode) {
084
085            if (mode == null) {
086                return null;
087            }
088            if (mode.equalsIgnoreCase(MODE_NONE.getMode())) {
089                return MODE_NONE;
090            }
091            if (mode.equalsIgnoreCase(MODE_MANUAL.getMode())) {
092                return MODE_MANUAL;
093            }
094            if (mode.equalsIgnoreCase(MODE_AUTO.getMode())) {
095                return MODE_AUTO;
096            }
097            return null;
098        }
099
100        /**
101         * Checks if this is the auto mode.<p>
102         *
103         * @return <code>true</code> if this is the auto mode
104         */
105        public boolean isAuto() {
106
107            return this == MODE_AUTO;
108        }
109
110        /**
111         * Checks if this is the manual mode.<p>
112         *
113         * @return <code>true</code> if this is the manual mode
114         */
115        public boolean isManual() {
116
117            return this == MODE_MANUAL;
118        }
119
120        /**
121         * Checks if this is the none mode.<p>
122         *
123         * @return <code>true</code> if this is the none mode
124         */
125        public boolean isNone() {
126
127            return this == MODE_NONE;
128        }
129    }
130
131    /** String remote command execution return type. */
132    public static final CmsServletContainerCfgMode CFG_MODE_AUTO = CmsServletContainerCfgMode.MODE_AUTO;
133
134    /** Map remote command execution return type. */
135    public static final CmsServletContainerCfgMode CFG_MODE_MANUAL = CmsServletContainerCfgMode.MODE_MANUAL;
136
137    /** List remote command execution return type. */
138    public static final CmsServletContainerCfgMode CFG_MODE_NONE = CmsServletContainerCfgMode.MODE_NONE;
139
140    /** The static log object for this class. */
141    private static final Log LOG = CmsLog.getLog(CmsServletContainerSettings.class);
142
143    /** If the servlet can throw an exception if initialization fails, for instance, Weblogic and Resin have problems with the exception. */
144    private static boolean m_servletThrowsException = true;
145
146    /**
147     * The replacement request attribute for the {@link javax.servlet.http.HttpServletRequest#getPathInfo()} method,
148     * which is needed because this method is not properly implemented in BEA WLS 9.x.<p>
149     */
150    private static final String REQUEST_ERROR_PAGE_ATTRIBUTE_WEBLOGIC = "weblogic.servlet.errorPage";
151
152    /** Constant name to identify GlassFish servers. */
153    // 2.1: "Sun GlassFish Enterprise Server v2.1"
154    private static final String SERVLET_CONTAINER_GLASSFISH = "GlassFish";
155
156    /** Constant name to identify Resin servers. */
157    private static final String SERVLET_CONTAINER_RESIN = "Resin";
158
159    /** Constant name to identify BEA WebLogic servers. */
160    private static final String SERVLET_CONTAINER_WEBLOGIC = "WebLogic Server";
161
162    /** Constant name to identify IBM Websphere servers. */
163    private static final String SERVLET_CONTAINER_WEBSPHERE = "IBM WebSphere Application Server";
164
165    /** The context path of the web application. */
166    private String m_contextPath;
167
168    /** The default web application (usually "ROOT"). */
169    private String m_defaultWebApplicationName;
170
171    /** The configuration mode. */
172    private CmsServletContainerCfgMode m_mode = CFG_MODE_NONE;
173
174    /** The OpenCms context and servlet path, e.g. <code>/opencms/opencms</code>. */
175    private String m_openCmsContext;
176
177    /** If the flex response has to prevent buffer flushing, for instance, Websphere does not allow to set headers afterwards, so we have to prevent it. */
178    private boolean m_preventResponseFlush;
179
180    /** If the tags need to be released after ending, this has to be prevented when running with Resin, for example. */
181    private boolean m_releaseTagsAfterEnd = true;
182
183    /**
184       * The request error page attribute to use if {@link javax.servlet.http.HttpServletRequest#getPathInfo()}
185       * is not working properly, like in BEA WLS 9.x.
186       */
187    private String m_requestErrorPageAttribute;
188
189    /** The name of the servlet container running OpenCms. */
190    private String m_servletContainerName;
191
192    /** The servlet path for the OpenCms servlet. */
193    private String m_servletPath;
194
195    /** The web application name. */
196    private String m_webApplicationName;
197
198    /** The OpenCms web application servlet container folder path (in the "real" file system). */
199    private String m_webApplicationRfsPath;
200
201    /** The OpenCms web application "WEB-INF" path (in the "real" file system). */
202    private String m_webInfRfsPath;
203
204    /**
205     * Creates a new object.<p>
206     *
207     * @param context used to find out specifics of the servlet container
208     */
209    public CmsServletContainerSettings(ServletContext context) {
210
211        // CmsSystemInfo<init> has to call this with null (for setup)
212        if (context != null) {
213            // check for OpenCms home (base) directory path
214            String webInfRfsPath = context.getInitParameter(OpenCmsServlet.SERVLET_PARAM_OPEN_CMS_HOME);
215            if (CmsStringUtil.isEmpty(webInfRfsPath)) {
216                webInfRfsPath = CmsFileUtil.searchWebInfFolder(context.getRealPath("/"));
217                if (CmsStringUtil.isEmpty(webInfRfsPath)) {
218                    throw new CmsInitException(Messages.get().container(Messages.ERR_CRITICAL_INIT_FOLDER_0));
219                }
220            }
221
222            // set the default web application name
223            // read the the default name from the servlet context parameters
224            String defaultWebApplication = context.getInitParameter("DefaultWebApplication");
225
226            // read the the OpenCms servlet mapping from the servlet context parameters
227            String servletMapping = context.getInitParameter(OpenCmsServlet.SERVLET_PARAM_OPEN_CMS_SERVLET);
228            if (servletMapping == null) {
229                throw new CmsInitException(Messages.get().container(Messages.ERR_CRITICAL_INIT_SERVLET_0));
230            }
231
232            // read the servlet container name
233            String servletContainerName = context.getServerInfo();
234
235            // web application context:
236            // read it from the servlet context parameters
237            //      this is needed in case an application server specific deployment descriptor is used to changed the webapp context
238            String webApplicationContext = context.getInitParameter(
239                OpenCmsServlet.SERVLET_PARAM_WEB_APPLICATION_CONTEXT);
240            if (CmsStringUtil.isEmptyOrWhitespaceOnly(webApplicationContext)) {
241                try {
242                    URL contextRelativeUrl = context.getResource("/");
243                    webApplicationContext = contextRelativeUrl.getPath();
244                    String[] pathTokens = CmsStringUtil.splitAsArray(webApplicationContext, '/');
245                    if (pathTokens.length == 1) {
246                        /*
247                         * There may be a "" context configured (e.g. in GlassFish).
248                         */
249                        webApplicationContext = "";
250                    } else {
251
252                        webApplicationContext = pathTokens[pathTokens.length - 1];
253                    }
254                } catch (MalformedURLException e) {
255                    LOG.error(Messages.get().getBundle().key(Messages.LOG_INIT_CONTEXTNAME_0), e);
256                }
257
258            }
259            // init values:
260            init(webInfRfsPath, defaultWebApplication, servletMapping, servletContainerName, webApplicationContext);
261            // finally care for the speciality of different servlet containers:
262            initContainerSpecifics(context);
263        }
264    }
265
266    /**
267     * Creates an instance based on the given values.<p>
268     *
269     * This is intended for <code>{@link CmsShell}</code> access.<p>
270     *
271     * @param webInfRfsPath the OpenCms web application "WEB-INF" path in the "real" file system) to set
272     * @param servletMapping the OpenCms servlet mapping  (e.g. "/opencms/*")
273     * @param webApplicationContext the name/path of the OpenCms web application context (optional, will be calculated form the path if null)
274     * @param defaultWebApplication the default web application name (usually "ROOT")
275     * @param servletContainerName the name of the servlet container running OpenCms
276     */
277    protected CmsServletContainerSettings(
278        String webInfRfsPath,
279        String defaultWebApplication,
280        String servletMapping,
281        String servletContainerName,
282        String webApplicationContext) {
283
284        init(webInfRfsPath, defaultWebApplication, servletMapping, servletContainerName, webApplicationContext);
285    }
286
287    /**
288     * Checks if the servlet can throw an exception if initialization fails.<p>
289     *
290     * @return <code>true</code> if the servlet can throw an exception if initialization fails
291     */
292    public static boolean isServletThrowsException() {
293
294        return m_servletThrowsException;
295    }
296
297    /**
298     * Returns the web application context path, e.g. "" (empty String) if the web application
299     * is the default web application (usually "ROOT"), or "/opencms" if the web application
300     * is called "opencms".<p>
301     *
302     * <i>From the Java Servlet Specification v2.4:</i><br>
303     * <b>Context Path:</b> The path prefix associated with the ServletContext that this
304     * servlet is a part of. If this context is the "default" context rooted at the base of
305     * the web server's URL name space, this path will be an empty string. Otherwise,
306     * if the context is not rooted at the root of the server's name space, the path starts
307     * with a "/" character but does not end with a "/" character.<p>
308     *
309     * @return the web application context path
310     * @see #getWebApplicationName()
311     * @see #getServletPath()
312     * @see #getOpenCmsContext()
313     */
314    public String getContextPath() {
315
316        return m_contextPath;
317    }
318
319    /**
320     * Returns the default web application name (usually "ROOT").<p>
321     *
322     * @return the default web application name
323     */
324    public String getDefaultWebApplicationName() {
325
326        return m_defaultWebApplicationName;
327    }
328
329    /**
330     * Returns the mode.<p>
331     *
332     * @return the mode
333     */
334    public CmsServletContainerCfgMode getMode() {
335
336        return m_mode;
337    }
338
339    /**
340     * Returns the OpenCms request context, e.g. "/opencms/opencms".<p>
341     *
342     * The OpenCms context will always start with a "/" and never have a trailing "/".
343     * The OpenCms context is identical to <code>getContexPath() + getServletPath()</code>.<p>
344     *
345     * @return the OpenCms request context, e.g. "/opencms/opencms"
346     * @see #getContextPath()
347     * @see #getServletPath()
348     */
349    public String getOpenCmsContext() {
350
351        return m_openCmsContext;
352    }
353
354    /**
355     * Returns the request error page attribute.<p>
356     *
357     * @return the request error page attribute
358     */
359    public String getRequestErrorPageAttribute() {
360
361        return m_requestErrorPageAttribute;
362    }
363
364    /**
365     * Returns the name of the servlet container running OpenCms.<p>
366     *
367     * @return the name of the servlet container running OpenCms
368     */
369    public String getServletContainerName() {
370
371        return m_servletContainerName;
372    }
373
374    /**
375     * Returns the OpenCms servlet path, e.g. "/opencms".<p>
376     *
377     * <i>From the Java Servlet Specification v2.4:</i><br>
378     * <b>Servlet Path:</b> The path section that directly corresponds to the mapping
379     * which activated this request. This path starts with a?/? character except in the
380     * case where the request is matched with the ?/*? pattern, in which case it is the
381     * empty string.<p>
382     *
383     * @return the OpenCms servlet path
384     * @see #getContextPath()
385     * @see #getWebApplicationName()
386     * @see #getOpenCmsContext()
387     */
388    public String getServletPath() {
389
390        return m_servletPath;
391    }
392
393    /**
394     * Returns the OpenCms web application name, e.g. "opencms" or "ROOT" (no leading or trailing "/").<p>
395     *
396     * The web application name is stored for informational purposes only.
397     * If you want to construct an URI, use either {@link #getContextPath()} and
398     * {@link #getServletPath()}, or for links to the OpenCms VFS use {@link  #getOpenCmsContext()}.<p>
399     *
400     * @return the OpenCms web application name
401     * @see #getContextPath()
402     * @see #getServletPath()
403     * @see #getOpenCmsContext()
404     */
405    public String getWebApplicationName() {
406
407        return m_webApplicationName;
408    }
409
410    /**
411     * Returns the OpenCms web application folder in the servlet container.<p>
412     *
413     * @return the OpenCms web application folder in the servlet container
414     */
415    public String getWebApplicationRfsPath() {
416
417        return m_webApplicationRfsPath;
418    }
419
420    /**
421     * Returns the OpenCms web application "WEB-INF" directory path.<p>
422     *
423     * @return the OpenCms web application "WEB-INF" directory path
424     */
425    public String getWebInfRfsPath() {
426
427        return m_webInfRfsPath;
428    }
429
430    /**
431     * Checks if the flex response has to prevent buffer flushing.<p>
432     *
433     * @return <code>true</code> if the flex response has to prevent buffer flushing
434     */
435    public boolean isPreventResponseFlush() {
436
437        return m_preventResponseFlush;
438    }
439
440    /**
441     * Checks if the tags need to be released after ending.<p>
442     *
443     * @return <code>true</code> if the tags need to be released after ending
444     */
445    public boolean isReleaseTagsAfterEnd() {
446
447        return m_releaseTagsAfterEnd;
448    }
449
450    /**
451     * Sets the mode from the configuration.<p>
452     *
453     * @param configValue the mode to set
454     */
455    public void setMode(String configValue) {
456
457        m_mode = CmsServletContainerCfgMode.valueOf(configValue);
458    }
459
460    /**
461     * Sets if the flex response has to prevent buffer flushing.<p>
462     *
463     * @param preventResponseFlush the flag to set
464     */
465    public void setPreventResponseFlush(boolean preventResponseFlush) {
466
467        m_preventResponseFlush = preventResponseFlush;
468    }
469
470    /**
471     * Sets if the tags need to be released after ending.<p>
472     *
473     * @param releaseTagsAfterEnd the flag to set
474     */
475    public void setReleaseTagsAfterEnd(boolean releaseTagsAfterEnd) {
476
477        m_releaseTagsAfterEnd = releaseTagsAfterEnd;
478    }
479
480    /**
481     * Sets the request error page attribute.<p>
482     *
483     * @param requestErrorPageAttribute the request error page attribute to set
484     */
485    public void setRequestErrorPageAttribute(String requestErrorPageAttribute) {
486
487        m_requestErrorPageAttribute = requestErrorPageAttribute;
488    }
489
490    /**
491     * Sets if the servlet can throw an exception if initialization fails.<p>
492     *
493     * @param servletThrowsException the flag to set
494     */
495    public void setServletThrowsException(boolean servletThrowsException) {
496
497        m_servletThrowsException = servletThrowsException;
498    }
499
500    /**
501     * Initialization code common to both constructors.<p>
502     *
503     * While the "webapplication - mode" constructor obtains all values from the
504     * <code>{@link ServletContext}</code> the <code>{@link CmsShell}</code> constructor
505     * accepts them as arguments and passes them here. <p>
506     *
507     * @param webInfRfsPath the OpenCms web application "WEB-INF" path in the "real" file system) to set
508     * @param servletMapping the OpenCms servlet mapping  (e.g. "/opencms/*")
509     * @param webApplicationContext the name/path of the OpenCms web application context (optional, will be calculated form the path if null)
510     * @param defaultWebApplication the default web application name (usually "ROOT")
511     * @param servletContainerName the name of the servlet container running OpenCms
512     */
513    private void init(
514        String webInfRfsPath,
515        String defaultWebApplication,
516        String servletMapping,
517        String servletContainerName,
518        String webApplicationContext) {
519
520        // WEB-INF RFS path
521
522        webInfRfsPath = webInfRfsPath.replace('\\', '/');
523        if (!webInfRfsPath.endsWith("/")) {
524            webInfRfsPath = webInfRfsPath + "/";
525        }
526        m_webInfRfsPath = CmsFileUtil.normalizePath(webInfRfsPath);
527
528        // default web application
529        if (defaultWebApplication == null) {
530            defaultWebApplication = "";
531        }
532        if (defaultWebApplication.endsWith("/")) {
533            defaultWebApplication = defaultWebApplication.substring(0, defaultWebApplication.length() - 1);
534        }
535        if (defaultWebApplication.startsWith("/")) {
536            defaultWebApplication = defaultWebApplication.substring(1);
537        }
538        m_defaultWebApplicationName = defaultWebApplication;
539
540        // servlet mapping
541        if (!servletMapping.startsWith("/")) {
542            servletMapping = "/" + servletMapping;
543        }
544        if (servletMapping.endsWith("/*")) {
545            // usually a mapping must be in the form "/opencms/*", cut off all slashes
546            servletMapping = servletMapping.substring(0, servletMapping.length() - 2);
547        }
548        m_servletPath = servletMapping;
549
550        // servlet container name
551        if (servletContainerName == null) {
552            m_servletContainerName = "";
553        }
554        m_servletContainerName = servletContainerName;
555
556        // set the web application name
557        File path = new File(m_webInfRfsPath);
558        m_webApplicationName = path.getParentFile().getName();
559
560        String contextPath = webApplicationContext;
561        // whitespace is OK because e.g. on glassfish the "" context may be configured
562        if (contextPath == null) {
563            contextPath = m_webApplicationName;
564            // this fixes an issue with context names in JBoss
565            if (contextPath.endsWith(".war")) {
566                contextPath = contextPath.substring(0, contextPath.length() - 4);
567            }
568        }
569        // set the context path
570        if (contextPath.equals(getDefaultWebApplicationName()) || "".equals(contextPath)) {
571            m_contextPath = "";
572        } else {
573            m_contextPath = "/" + contextPath;
574        }
575        // set the OpenCms context
576        m_openCmsContext = m_contextPath + m_servletPath;
577
578        // set the web application rfs path
579        m_webApplicationRfsPath = path.getParentFile().getAbsolutePath();
580        if (!m_webApplicationRfsPath.endsWith(File.separator)) {
581            m_webApplicationRfsPath += File.separator;
582        }
583
584        // fill some defaults:
585        m_releaseTagsAfterEnd = false;
586        m_requestErrorPageAttribute = null;
587        m_servletThrowsException = true;
588        m_preventResponseFlush = false;
589    }
590
591    /**
592     * Initializes these container settings with container specific settings.<p>
593     *
594     * @param context
595     *      the servlet context to find out information about the servlet container
596     */
597    private void initContainerSpecifics(ServletContext context) {
598
599        // the tags behavior
600        m_releaseTagsAfterEnd = !(m_servletContainerName.indexOf(SERVLET_CONTAINER_RESIN) > -1);
601
602        // the request error page attribute
603        if (m_servletContainerName.indexOf(SERVLET_CONTAINER_WEBLOGIC) > -1) {
604            m_requestErrorPageAttribute = REQUEST_ERROR_PAGE_ATTRIBUTE_WEBLOGIC;
605        }
606
607        // the failed initialization behavior
608        m_servletThrowsException = true;
609        m_servletThrowsException &= (m_servletContainerName.indexOf(SERVLET_CONTAINER_RESIN) < 0);
610        m_servletThrowsException &= (m_servletContainerName.indexOf(SERVLET_CONTAINER_WEBLOGIC) < 0);
611        m_servletThrowsException &= (m_servletContainerName.indexOf(SERVLET_CONTAINER_GLASSFISH) < 0);
612
613        // the flush flex response behavior
614        m_preventResponseFlush = false;
615        m_preventResponseFlush |= (m_servletContainerName.indexOf(SERVLET_CONTAINER_WEBSPHERE) > -1);
616        m_preventResponseFlush |= (m_servletContainerName.indexOf(SERVLET_CONTAINER_RESIN) > -1);
617    }
618}