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