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.flex;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.history.CmsHistoryResourceHandler;
032import org.opencms.jsp.util.CmsJspStandardContextBean;
033import org.opencms.loader.CmsJspLoader;
034import org.opencms.main.CmsEvent;
035import org.opencms.main.CmsLog;
036import org.opencms.main.I_CmsEventListener;
037import org.opencms.main.OpenCms;
038import org.opencms.security.CmsRole;
039import org.opencms.staticexport.CmsLinkManager;
040import org.opencms.util.CmsCollectionsGenericWrapper;
041import org.opencms.util.CmsParameterEscaper;
042import org.opencms.util.CmsRequestUtil;
043
044import java.util.Arrays;
045import java.util.Collections;
046import java.util.Enumeration;
047import java.util.HashMap;
048import java.util.Iterator;
049import java.util.List;
050import java.util.Map;
051import java.util.Set;
052import java.util.Vector;
053
054import javax.servlet.ServletRequest;
055import javax.servlet.http.HttpServletRequest;
056import javax.servlet.http.HttpServletRequestWrapper;
057
058import org.apache.commons.logging.Log;
059
060import com.google.common.collect.Sets;
061
062/**
063 * Wrapper class for a HttpServletRequest.<p>
064 *
065 * This class wraps the standard HttpServletRequest so that it's output can be delivered to
066 * the CmsFlexCache.<p>
067 *
068 * @since 6.0.0
069 */
070public class CmsFlexRequest extends HttpServletRequestWrapper {
071
072    /** Request parameter for FlexCache commands. */
073    public static final String PARAMETER_FLEX = "_flex";
074
075    /** The log object for this class. */
076    private static final Log LOG = CmsLog.getLog(CmsFlexRequest.class);
077
078    /** JSP Loader instance. */
079    private static CmsJspLoader m_jspLoader;
080
081    /** The max allowed recursive include number.*/
082    private static final int MAX_INCLUDE_RECURSION = 7;
083
084    /** Map of attributes from the original request. */
085    private Map<String, Object> m_attributes;
086
087    /** Flag to decide if this request can be cached or not. */
088    private boolean m_canCache;
089
090    /** The CmsFlexController for this request. */
091    private CmsFlexController m_controller;
092
093    /** Flag to force a JSP recompile. */
094    private boolean m_doRecompile;
095
096    /** The requested resources element URI in the OpenCms VFS. */
097    private String m_elementUri;
098
099    /** The site root of the requested resource. */
100    private String m_elementUriSiteRoot;
101
102    /** The parameter escaper. */
103    private CmsParameterEscaper m_escaper;
104
105    /** List of all include calls (to prevent an endless inclusion loop). */
106    private List<String> m_includeCalls;
107
108    /** Flag to check if this request is in the online project or not. */
109    private boolean m_isOnline;
110
111    /** The CmsFlexRequestKey for this request. */
112    private CmsFlexRequestKey m_key;
113
114    /** Map of parameters from the original request. */
115    private Map<String, String[]> m_parameters;
116
117    /** Stores the request URI after it was once calculated. */
118    private String m_requestUri;
119
120    /** Stores the request URL after it was once calculated. */
121    private StringBuffer m_requestUrl;
122
123    /** A set of keys of parameters which should be stored in a cached include even if they were not passed as additional parameters to the include call. */
124    private Set<String> m_dynamicParameters = Sets.newHashSet();
125
126    /**
127     * Creates a new CmsFlexRequest wrapper which is most likely the "Top"
128     * request wrapper, i.e. the wrapper that is constructed around the
129     * first "real" (not wrapped) request.<p>
130     *
131     * @param req the request to wrap
132     * @param controller the controller to use
133     */
134    public CmsFlexRequest(HttpServletRequest req, CmsFlexController controller) {
135
136        super(req);
137        m_controller = controller;
138        CmsObject cms = m_controller.getCmsObject();
139        m_elementUri = cms.getSitePath(m_controller.getCmsResource());
140        m_elementUriSiteRoot = cms.getRequestContext().getSiteRoot();
141        m_includeCalls = new Vector<String>();
142        m_parameters = CmsCollectionsGenericWrapper.map(req.getParameterMap());
143        m_attributes = CmsRequestUtil.getAtrributeMap(req);
144        m_isOnline = cms.getRequestContext().getCurrentProject().isOnlineProject();
145        String[] params = req.getParameterValues(PARAMETER_FLEX);
146        boolean nocachepara = CmsHistoryResourceHandler.isHistoryRequest(req);
147        boolean dorecompile = false;
148        if (params != null) {
149            if (OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) {
150                List<String> paramList = Arrays.asList(params);
151                boolean firstCall = controller.isEmptyRequestList();
152                nocachepara |= paramList.contains("nocache");
153                dorecompile = paramList.contains("recompile");
154                boolean p_on = paramList.contains("online");
155                boolean p_off = paramList.contains("offline");
156                if (paramList.contains("purge") && firstCall) {
157                    OpenCms.fireCmsEvent(
158                        new CmsEvent(
159                            I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY,
160                            new HashMap<String, Object>(0)));
161                    OpenCms.fireCmsEvent(
162                        new CmsEvent(
163                            I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
164                            Collections.<String, Object> singletonMap(
165                                "action",
166                                Integer.valueOf(CmsFlexCache.CLEAR_ENTRIES))));
167                    dorecompile = false;
168                } else if ((paramList.contains("clearcache") || dorecompile) && firstCall) {
169                    if (!(p_on || p_off)) {
170                        OpenCms.fireCmsEvent(
171                            new CmsEvent(
172                                I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
173                                Collections.<String, Object> singletonMap(
174                                    "action",
175                                    Integer.valueOf(CmsFlexCache.CLEAR_ALL))));
176                    } else {
177                        if (p_on) {
178                            OpenCms.fireCmsEvent(
179                                new CmsEvent(
180                                    I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
181                                    Collections.<String, Object> singletonMap(
182                                        "action",
183                                        Integer.valueOf(CmsFlexCache.CLEAR_ONLINE_ALL))));
184                        }
185                        if (p_off) {
186                            OpenCms.fireCmsEvent(
187                                new CmsEvent(
188                                    I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
189                                    Collections.<String, Object> singletonMap(
190                                        "action",
191                                        Integer.valueOf(CmsFlexCache.CLEAR_OFFLINE_ALL))));
192                        }
193                    }
194                } else if (paramList.contains("clearvariations") && firstCall) {
195                    if (!(p_on || p_off)) {
196                        OpenCms.fireCmsEvent(
197                            new CmsEvent(
198                                I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
199                                Collections.<String, Object> singletonMap(
200                                    "action",
201                                    Integer.valueOf(CmsFlexCache.CLEAR_ENTRIES))));
202                    } else {
203                        if (p_on) {
204                            OpenCms.fireCmsEvent(
205                                new CmsEvent(
206                                    I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
207                                    Collections.<String, Object> singletonMap(
208                                        "action",
209                                        Integer.valueOf(CmsFlexCache.CLEAR_ONLINE_ENTRIES))));
210                        }
211                        if (p_off) {
212                            OpenCms.fireCmsEvent(
213                                new CmsEvent(
214                                    I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR,
215                                    Collections.<String, Object> singletonMap(
216                                        "action",
217                                        Integer.valueOf(CmsFlexCache.CLEAR_OFFLINE_ENTRIES))));
218                        }
219                    }
220                }
221            }
222        }
223        m_canCache = ((((m_isOnline && m_controller.getCmsCache().isEnabled())
224            || (!m_isOnline && m_controller.getCmsCache().cacheOffline())) && !nocachepara) || dorecompile);
225        m_doRecompile = dorecompile;
226        if (LOG.isDebugEnabled()) {
227            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXREQUEST_CREATED_NEW_REQUEST_1, m_elementUri));
228        }
229    }
230
231    /**
232     * Constructs a new wrapper layer around an (already wrapped) CmsFlexRequest.<p>
233     *
234     * @param req the request to be wrapped
235     * @param controller the controller to use
236     * @param resource the target resource that has been requested
237     */
238    CmsFlexRequest(HttpServletRequest req, CmsFlexController controller, String resource) {
239
240        super(req);
241        m_controller = controller;
242        m_elementUri = CmsLinkManager.getAbsoluteUri(resource, m_controller.getCurrentRequest().getElementUri());
243        m_elementUriSiteRoot = m_controller.getCurrentRequest().m_elementUriSiteRoot;
244        m_isOnline = m_controller.getCurrentRequest().isOnline();
245        m_canCache = m_controller.getCurrentRequest().isCacheable();
246        m_doRecompile = m_controller.getCurrentRequest().isDoRecompile();
247        m_includeCalls = m_controller.getCurrentRequest().getCmsIncludeCalls();
248        m_parameters = CmsCollectionsGenericWrapper.map(req.getParameterMap());
249        m_attributes = CmsRequestUtil.getAtrributeMap(req);
250        if (LOG.isDebugEnabled()) {
251            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXREQUEST_REUSING_FLEX_REQUEST_1, m_elementUri));
252        }
253    }
254
255    /**
256     * Adds the specified Map to the attributes of the request,
257     * added attributes will not overwrite existing attributes in the
258     * request.<p>
259     *
260     * @param map the map to add
261     *
262     * @return the merged map of attributes
263     */
264    public Map<String, Object> addAttributeMap(Map<String, Object> map) {
265
266        if (map == null) {
267            return m_attributes;
268        }
269        if ((m_attributes == null) || (m_attributes.size() == 0)) {
270            m_attributes = new HashMap<String, Object>(map);
271        } else {
272            Map<String, Object> attributes = new HashMap<String, Object>();
273            attributes.putAll(m_attributes);
274            Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
275            while (it.hasNext()) {
276                Map.Entry<String, Object> entry = it.next();
277                String key = entry.getKey();
278                // prevent flexcache controller to be overwritten
279                if (CmsFlexController.ATTRIBUTE_NAME.equals(key)) {
280                    continue;
281                } else if (CmsJspStandardContextBean.ATTRIBUTE_NAME.equals(key)) {
282                    CmsJspStandardContextBean bean = (CmsJspStandardContextBean)entry.getValue();
283                    bean.updateCmsObject(m_controller.getCmsObject());
284                    bean.updateRequestData(this);
285                }
286                attributes.put(key, entry.getValue());
287            }
288            m_attributes = new HashMap<String, Object>(attributes);
289        }
290
291        return m_attributes;
292    }
293
294    /**
295     * Adds the specified Map to the parameters of the request,
296     * added parameters will not overwrite existing parameters in the
297     * request.<p>
298     *
299     * Remember that the value for a parameter name in
300     * a HttpRequest is a String array. If a parameter name already
301     * exists in the HttpRequest, the values will be added to the existing
302     * value array. Multiple occurrences of the same value for one
303     * parameter are also possible.<p>
304     *
305     * @param map the map to add
306     *
307     * @return the merged map of parameters
308     */
309    public Map<String, String[]> addParameterMap(Map<String, String[]> map) {
310
311        if (map == null) {
312            return m_parameters;
313        }
314        if ((m_parameters == null) || (m_parameters.size() == 0)) {
315            m_parameters = Collections.unmodifiableMap(map);
316        } else {
317            Map<String, String[]> parameters = new HashMap<String, String[]>();
318            parameters.putAll(m_parameters);
319
320            Iterator<Map.Entry<String, String[]>> it = map.entrySet().iterator();
321            while (it.hasNext()) {
322                Map.Entry<String, String[]> entry = it.next();
323                String key = entry.getKey();
324                // Check if the parameter name (key) exists
325                if (parameters.containsKey(key)) {
326
327                    String[] oldValues = parameters.get(key);
328                    String[] newValues = entry.getValue();
329
330                    String[] mergeValues = new String[oldValues.length + newValues.length];
331                    System.arraycopy(newValues, 0, mergeValues, 0, newValues.length);
332                    System.arraycopy(oldValues, 0, mergeValues, newValues.length, oldValues.length);
333
334                    parameters.put(key, mergeValues);
335                } else {
336                    // No: Add new value array
337                    parameters.put(key, entry.getValue());
338                }
339            }
340            m_parameters = Collections.unmodifiableMap(parameters);
341        }
342
343        return m_parameters;
344    }
345
346    /**
347     * Enables escaping for all parameters which are not in the list of exceptions.<p>
348     */
349    public void enableParameterEscaping() {
350
351        if (m_escaper == null) {
352            LOG.info("Enabling parameter escaping for the current flex request");
353            m_escaper = new CmsParameterEscaper();
354        }
355    }
356
357    /**
358     * Return the value of the specified request attribute, if any; otherwise,
359     * return <code>null</code>.<p>
360     *
361     * @param name the name of the desired request attribute
362     *
363     * @return the value of the specified request attribute
364     *
365     * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
366     */
367    @Override
368    public Object getAttribute(String name) {
369
370        Object object = m_attributes.get(name);
371        if (object == null) {
372            object = super.getAttribute(name);
373        }
374        return object;
375    }
376
377    /**
378     * Returns a <code>Map</code> of the attributes of this request.<p>
379     *
380     * @return a <code>Map</code> containing attribute names as keys
381     *  and attribute values as map values
382     */
383    public Map<String, Object> getAttributeMap() {
384
385        return m_attributes;
386    }
387
388    /**
389     * Return the names of all defined request attributes for this request.<p>
390     *
391     * @return the names of all defined request attributes for this request
392     *
393     * @see javax.servlet.ServletRequest#getAttributeNames
394     */
395    @Override
396    public Enumeration<String> getAttributeNames() {
397
398        Vector<String> v = new Vector<String>();
399        v.addAll(m_attributes.keySet());
400        return v.elements();
401    }
402
403    /**
404     * Gets the set of dynamic parameters.<p>
405     *
406     * Normally, when caching a JSP which includes another JSP, only the parameters given directly to the include call will be cached in the new flex cache entry's include list.
407     * But when the include call happens implicitly (e.g. for elements rendered in a cms:container tag), we can't pass it any parameters. In this case, we have to modify the parameter
408     * map of the current flex request in the JSP, and the keys for the modified parameters need to be stored in this set for the Flex cache to work correctly.
409     *
410     * @return the set of keys for the dynamic parameters
411     */
412    public Set<String> getDynamicParameters() {
413
414        return m_dynamicParameters;
415    }
416
417    /**
418     * Returns the full element URI site root path to the resource currently processed.<p>
419     *
420     * @return the name of the resource currently processed
421     */
422    public String getElementRootPath() {
423
424        return m_controller.getCmsObject().getRequestContext().addSiteRoot(m_elementUriSiteRoot, m_elementUri);
425    }
426
427    /**
428     * Returns the element URI of the resource currently processed,
429     * relative to the current site root.<p>
430     *
431     * This might be the name of an included resource,
432     * not necessarily the name the resource requested by the user.<p>
433     *
434     * @return the name of the resource currently processed
435     */
436    public String getElementUri() {
437
438        return m_elementUri;
439    }
440
441    /**
442     * Return the value of the specified request parameter, if any; otherwise,
443     * return <code>null</code>.<p>
444     *
445     * If there is more than one value defined,
446     * return only the first one.<p>
447     *
448     * @param name the name of the desired request parameter
449     *
450     * @return the value of the specified request parameter
451     *
452     * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
453     */
454    @Override
455    public String getParameter(String name) {
456
457        String[] values = m_parameters.get(name);
458        if (values != null) {
459            if (m_escaper != null) {
460                return m_escaper.escape(name, values[0]);
461            } else {
462                return (values[0]);
463            }
464        } else {
465            return (null);
466        }
467    }
468
469    /**
470     * Gets the parameter escaper.<p>
471     *
472     * @return the parameter escaper
473     */
474    public CmsParameterEscaper getParameterEscaper() {
475
476        return m_escaper;
477    }
478
479    /**
480     * Returns a <code>Map</code> of the parameters of this request.<p>
481     *
482     * Request parameters are extra information sent with the request.
483     * For HTTP servlets, parameters are contained in the query string
484     * or posted form data.<p>
485     *
486     * @return a <code>Map</code> containing parameter names as keys
487     *  and parameter values as map values
488     *
489     * @see javax.servlet.ServletRequest#getParameterMap()
490     */
491    @Override
492    public Map<String, String[]> getParameterMap() {
493
494        // NOTE: The parameters in this map are not escaped, so when escaping is enabled,
495        // its values may be different from those obtained via getParameter/getParameterValues
496        return m_parameters;
497    }
498
499    /**
500     * Return the names of all defined request parameters for this request.<p>
501     *
502     * @return the names of all defined request parameters for this request
503     *
504     * @see javax.servlet.ServletRequest#getParameterNames()
505     */
506    @Override
507    public Enumeration<String> getParameterNames() {
508
509        Vector<String> v = new Vector<String>();
510        v.addAll(m_parameters.keySet());
511        return (v.elements());
512    }
513
514    /**
515     * Returns the defined values for the specified request parameter, if any;
516     * otherwise, return <code>null</code>.<p>
517     *
518     * @param name Name of the desired request parameter
519     *
520     * @return the defined values for the specified request parameter, if any;
521     *          <code>null</code> otherwise
522     *
523     * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
524     */
525    @Override
526    public String[] getParameterValues(String name) {
527
528        if (m_escaper != null) {
529            return m_escaper.escape(name, m_parameters.get(name));
530        } else {
531            return m_parameters.get(name);
532        }
533    }
534
535    /**
536     * Allows requests to be dispatched to internal VFS resources or
537     * external JSP pages, overloads the standard servlet API <code>getRequestDispatcher()</code> method.<p>
538     *
539     * @param target the target for the request dispatcher
540     *
541     * @return a special RequestDispatcher that allows access to VFS resources
542     */
543    @Override
544    public javax.servlet.RequestDispatcher getRequestDispatcher(String target) {
545
546        String absolutUri = CmsLinkManager.getAbsoluteUri(target, m_controller.getCurrentRequest().getElementUri());
547        return new CmsFlexRequestDispatcher(
548            m_controller.getTopRequest().getRequestDispatcher(absolutUri),
549            absolutUri,
550            null);
551    }
552
553    /**
554     * Replacement for the standard servlet API getRequestDispatcher() method.<p>
555     *
556     * This variation is used if an external file (probably JSP) is dispatched to.
557     * This external file must have a "mirror" version, i.e. a file in the OpenCms VFS
558     * that represents the external file.<p>
559     *
560     * @param vfs_target the OpenCms file that is a "mirror" version of the external file
561     * @param ext_target the external file (outside the OpenCms VFS)
562     *
563     * @return the constructed CmsFlexRequestDispatcher
564     */
565    public CmsFlexRequestDispatcher getRequestDispatcherToExternal(String vfs_target, String ext_target) {
566
567        return new CmsFlexRequestDispatcher(
568            m_controller.getTopRequest().getRequestDispatcher(ext_target),
569            CmsLinkManager.getAbsoluteUri(vfs_target, m_controller.getCmsObject().getRequestContext().getUri()),
570            ext_target);
571    }
572
573    /**
574     * Wraps the request URI, overloading the standard API.<p>
575     *
576     * This ensures that any wrapped request will use the "faked"
577     * target parameters. Remember that for the real request,
578     * a mixture of PathInfo and other request information is used to
579     * identify the target.<p>
580     *
581     * @return a faked URI that will point to the wrapped target in the VFS
582     *
583     * @see javax.servlet.http.HttpServletRequest#getRequestURI()
584     */
585    @Override
586    public String getRequestURI() {
587
588        if (m_requestUri != null) {
589            return m_requestUri;
590        }
591        StringBuffer buf = new StringBuffer(128);
592        buf.append(OpenCms.getSystemInfo().getOpenCmsContext());
593        buf.append(getElementUri());
594        m_requestUri = buf.toString();
595        return m_requestUri;
596    }
597
598    /**
599     * Wraps the request URL, overloading the standard API,
600     * the wrapped URL will always point to the currently included VFS resource.<p>
601     *
602     * @return a faked URL that will point to the included target in the VFS
603     *
604     * @see javax.servlet.http.HttpServletRequest#getRequestURL()
605     */
606    @Override
607    public StringBuffer getRequestURL() {
608
609        if (m_requestUrl != null) {
610            return m_requestUrl;
611        }
612        StringBuffer buf = new StringBuffer(128);
613        buf.append(getScheme());
614        buf.append("://");
615        buf.append(getServerName());
616        buf.append(":");
617        buf.append(getServerPort());
618        buf.append(getRequestURI());
619        m_requestUrl = buf;
620        return m_requestUrl;
621    }
622
623    /**
624     * This is a work around for servlet containers creating a new application dispatcher
625     * instead of using our request dispatcher, so missing RFS JSP pages are not requested to
626     * OpenCms and the dispatcher is unable to load the included/forwarded JSP file.<p>
627     *
628     * @see javax.servlet.http.HttpServletRequestWrapper#getServletPath()
629     */
630    @Override
631    public String getServletPath() {
632
633        // unwrap the request to prevent multiple unneeded attempts to generate missing JSP files
634        // m_controller.getTopRequest() does not return the right request here when forwarding
635        // this method is generally called exactly once per request on different servlet containers
636        // only resin calls it twice
637        ServletRequest req = getRequest();
638        while (req instanceof CmsFlexRequest) {
639            req = ((CmsFlexRequest)req).getRequest();
640        }
641        String servletPath = null;
642        if (req instanceof HttpServletRequest) {
643            servletPath = ((HttpServletRequest)req).getServletPath();
644        } else {
645            servletPath = super.getServletPath();
646        }
647        // generate missing JSP file
648        CmsJspLoader jspLoader = getJspLoader();
649        if (jspLoader != null) {
650            jspLoader.updateJspFromRequest(servletPath, this);
651        }
652        return servletPath;
653    }
654
655    /**
656     * Checks if JSPs should always be recompiled.<p>
657     *
658     * This is useful in case directive based includes are used
659     * with &lt;%@ include file="..." %&gt; on a JSP.
660     * Note that this also forces the request not to be cached.<p>
661     *
662     * @return true if JSPs should be recompiled, false otherwise
663     */
664    public boolean isDoRecompile() {
665
666        return m_doRecompile;
667    }
668
669    /**
670     * Indicates that this request belongs to an online project.<p>
671     *
672     * This is required to distinguish between online and offline
673     * resources in the cache. Since the resources have the same name,
674     * a suffix [online] or [offline] is added to distinguish the strings
675     * when building cache keys.
676     * Any resource from a request that isOnline() will be saved with
677     * the [online] suffix and vice versa.<p>
678     *
679     * Resources in the OpenCms workplace are not distinguished between
680     * online and offline but have their own suffix [workplace].
681     * The assumption is that if you do change the workplace, this is
682     * only on true development machines so you can do the cache clearing
683     * manually if required.<p>
684     *
685     * The suffixes are used so that we have a simple String name
686     * for the resources in the cache. This makes it easy to
687     * use a standard HashMap for storage of the resources.<p>
688     *
689     * @return true if an online resource was requested, false otherwise
690     */
691    public boolean isOnline() {
692
693        return m_isOnline;
694    }
695
696    /**
697     * @see javax.servlet.ServletRequestWrapper#removeAttribute(java.lang.String)
698     */
699    @Override
700    public void removeAttribute(String name) {
701
702        m_attributes.remove(name);
703        m_controller.getTopRequest().removeAttribute(name);
704    }
705
706    /**
707     * @see javax.servlet.ServletRequestWrapper#setAttribute(java.lang.String, java.lang.Object)
708     */
709    @Override
710    public void setAttribute(String name, Object value) {
711
712        m_attributes.put(name, value);
713        m_controller.getTopRequest().setAttribute(name, value);
714    }
715
716    /**
717     * Sets the specified Map as attribute map of the request.<p>
718     *
719     * The map should be immutable.
720     * This will completely replace the attribute map.
721     * Use this in combination with {@link #getAttributeMap()} and
722     * {@link #addAttributeMap(Map)} in case you want to set the old status
723     * of the attribute map after you have modified it for
724     * a specific operation.<p>
725     *
726     * @param map the map to set
727     */
728    public void setAttributeMap(Map<String, Object> map) {
729
730        m_attributes = new HashMap<String, Object>(map);
731    }
732
733    /**
734     * Sets the set of dynamic parameters.<p>
735     *
736     * @param dynamicParams the set of dynamic parameters
737     */
738    public void setDynamicParameters(Set<String> dynamicParams) {
739
740        if (dynamicParams == null) {
741            dynamicParams = Sets.newHashSet();
742        }
743        m_dynamicParameters = dynamicParams;
744    }
745
746    /**
747     * Sets the specified Map as parameter map of the request.<p>
748     *
749     * The map should be immutable.
750     * This will completely replace the parameter map.
751     * Use this in combination with {@link #getParameterMap()} and
752     * {@link #addParameterMap(Map)} in case you want to set the old status
753     * of the parameter map after you have modified it for
754     * a specific operation.<p>
755     *
756     * @param map the map to set
757     */
758    public void setParameterMap(Map<String, String[]> map) {
759
760        m_parameters = map;
761    }
762
763    /**
764     * @see java.lang.Object#toString()
765     */
766    @Override
767    public String toString() {
768
769        // return the uri of the element requested for this request, useful in debugging
770        return m_elementUri;
771    }
772
773    /**
774     * Returns the List of include calls which will be passed to the next wrapping layer.<p>
775     *
776     * The set of include calls is maintained to detect
777     * an endless inclusion loop.<p>
778     *
779     * @return the List of include calls
780     */
781    protected List<String> getCmsIncludeCalls() {
782
783        return m_includeCalls;
784    }
785
786    /**
787     * Returns the jsp loader instance.<p>
788     *
789     * @return the jsp loader instance
790     */
791    protected CmsJspLoader getJspLoader() {
792
793        if (m_jspLoader == null) {
794            try {
795                m_jspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader(CmsJspLoader.RESOURCE_LOADER_ID);
796            } catch (ArrayIndexOutOfBoundsException e) {
797                // ignore, loader not configured
798            }
799        }
800        return m_jspLoader;
801    }
802
803    /**
804     * Adds another include call to this wrapper.<p>
805     *
806     * The set of include calls is maintained to detect
807     * an endless inclusion loop.<p>
808     *
809     * @param target the target name (absolute OpenCms URI) to add
810     */
811    void addInlucdeCall(String target) {
812
813        m_includeCalls.add(target);
814    }
815
816    /**
817     * Checks if a given target has been included earlier and exceeds the max allowed recursions.<p>
818     *
819     * The set of include calls is maintained to detect
820     * an endless inclusion loop.<p>
821     *
822     * @param target the target name (absolute OpenCms URI) to check for
823     * @return true if the target is already included, false otherwise
824     */
825    boolean exceedsCallLimit(String target) {
826
827        if (m_includeCalls.contains(target)) {
828            int count = 0;
829            for (String call : m_includeCalls) {
830                if (call.equals(target)) {
831                    count++;
832                    if (count > MAX_INCLUDE_RECURSION) {
833                        return true;
834                    }
835                }
836            }
837        }
838        return false;
839    }
840
841    /**
842     * Returns the CmsFlexCacheKey for this request,
843     * the key will be calculated if necessary.<p>
844     *
845     * @return the CmsFlexCacheKey for this request
846     */
847    CmsFlexRequestKey getCmsCacheKey() {
848
849        // The key for this request is only calculated if actually requested
850        if (m_key == null) {
851            m_key = new CmsFlexRequestKey(this, m_elementUri, m_isOnline);
852        }
853        return m_key;
854    }
855
856    /**
857     * This is needed to decide if this request can be cached or not.<p>
858     *
859     * Using the request to decide if caching is used or not
860     * makes it possible to set caching to false e.g. on a per-user
861     * or per-project basis.<p>
862     *
863     * @return <code>true</code> if the request is cacheable, false otherwise
864     */
865    boolean isCacheable() {
866
867        return m_canCache;
868    }
869
870    /**
871     * Removes an include call from this wrapper.<p>
872     *
873     * The set of include calls is maintained to detect
874     * an endless inclusion loop.<p>
875     *
876     * @param target the target name (absolute OpenCms URI) to remove
877     */
878    void removeIncludeCall(String target) {
879
880        m_includeCalls.remove(target);
881    }
882}