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