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.ade.detailpage.CmsDetailPageResourceHandler;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.main.CmsLog;
034import org.opencms.util.CmsRequestUtil;
035
036import java.io.IOException;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041import java.util.Vector;
042
043import javax.servlet.ServletRequest;
044import javax.servlet.http.HttpServletRequest;
045import javax.servlet.http.HttpServletResponse;
046
047import org.apache.commons.logging.Log;
048
049/**
050 * Controller for getting access to the CmsObject, should be used as a
051 * request attribute.<p>
052 *
053 * @since 6.0.0
054 */
055public class CmsFlexController {
056
057    /**
058     * Information about where to redirect to.
059     */
060    public static class RedirectInfo {
061
062        /** True if a permanent redirect should be used. */
063        private boolean m_permanent;
064
065        /** The redirect target. */
066        private String m_target;
067
068        /**
069         * Creates a new instance.
070         *
071         * @param target the redirect target
072         * @param permanent true if a permanent redirect should be used
073         */
074        public RedirectInfo(String target, boolean permanent) {
075
076            m_target = target;
077            m_permanent = permanent;
078
079        }
080
081        /**
082         * Executes the redirect on the given response with the stored information.
083         * 
084         * @param res the res to use for executing the redirect
085         */
086        public void executeRedirect(HttpServletResponse res) throws IOException {
087
088            if (isPermanent()) {
089                res.setHeader(CmsRequestUtil.HEADER_LOCATION, getTarget());
090                res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
091            } else {
092                res.sendRedirect(getTarget());
093            }
094
095        }
096
097        /**
098         * Gets the redirect target.
099         *
100         * @return the redirect target
101         */
102        public String getTarget() {
103
104            return m_target;
105        }
106
107        /**
108         * Returns true if a permanent redirect should be used.
109         *
110         * @return true if a permanent redirect should be used
111         */
112        public boolean isPermanent() {
113
114            return m_permanent;
115        }
116
117    }
118
119    /** Constant for the controller request attribute name. */
120    public static final String ATTRIBUTE_NAME = "org.opencms.flex.CmsFlexController";
121
122    /** The log object for this class. */
123    private static final Log LOG = CmsLog.getLog(CmsFlexController.class);
124
125    /** Set of uncacheable attributes. */
126    private static Set<String> uncacheableAttributes = new HashSet<String>();
127
128    /** The CmsFlexCache where the result will be cached in, required for the dispatcher. */
129    private CmsFlexCache m_cache;
130
131    /** The wrapped CmsObject provides JSP with access to the core system. */
132    private CmsObject m_cmsObject;
133
134    /** List of wrapped RequestContext info object. */
135    private List<CmsFlexRequestContextInfo> m_flexContextInfoList;
136
137    /** List of wrapped CmsFlexRequests. */
138    private List<CmsFlexRequest> m_flexRequestList;
139
140    /** List of wrapped CmsFlexResponses. */
141    private List<CmsFlexResponse> m_flexResponseList;
142
143    /** Indicates if this controller is currently in "forward" mode. */
144    private boolean m_forwardMode;
145
146    /** Information about where to redirect to. */
147    private RedirectInfo m_redirectInfo;
148
149    /** Wrapped top request. */
150    private HttpServletRequest m_req;
151
152    /** Wrapped top response. */
153    private HttpServletResponse m_res;
154
155    /** The CmsResource that was initialized by the original request, required for URI actions. */
156    private CmsResource m_resource;
157
158    /** Indicates if the response should be streamed. */
159    private boolean m_streaming;
160
161    /** Exception that was caught during inclusion of sub elements. */
162    private Throwable m_throwable;
163
164    /** URI of a VFS resource that caused the exception. */
165    private String m_throwableResourceUri;
166
167    /** Indicates if the request is the top request. */
168    private boolean m_top;
169
170    /**
171     * Creates a new controller form the old one, exchanging just the provided OpenCms user context.<p>
172     *
173     * @param cms the OpenCms user context for this controller
174     * @param base the base controller
175     */
176    public CmsFlexController(CmsObject cms, CmsFlexController base) {
177
178        m_cmsObject = cms;
179        m_resource = base.m_resource;
180        m_cache = base.m_cache;
181        m_req = base.m_req;
182        m_res = base.m_res;
183        m_streaming = base.m_streaming;
184        m_top = base.m_top;
185        m_flexRequestList = base.m_flexRequestList;
186        m_flexResponseList = base.m_flexResponseList;
187        m_flexContextInfoList = base.m_flexContextInfoList;
188        m_forwardMode = base.m_forwardMode;
189        m_throwableResourceUri = base.m_throwableResourceUri;
190        m_redirectInfo = base.m_redirectInfo;
191    }
192
193    /**
194     * Default constructor.<p>
195     *
196     * @param cms the initial CmsObject to wrap in the controller
197     * @param resource the file requested
198     * @param cache the instance of the flex cache
199     * @param req the current request
200     * @param res the current response
201     * @param streaming indicates if the response is streaming
202     * @param top indicates if the response is the top response
203     */
204    public CmsFlexController(
205        CmsObject cms,
206        CmsResource resource,
207        CmsFlexCache cache,
208        HttpServletRequest req,
209        HttpServletResponse res,
210        boolean streaming,
211        boolean top) {
212
213        m_cmsObject = cms;
214        m_resource = resource;
215        m_cache = cache;
216        m_req = req;
217        m_res = res;
218        m_streaming = streaming;
219        m_top = top;
220        m_flexRequestList = new Vector<CmsFlexRequest>();
221        m_flexResponseList = new Vector<CmsFlexResponse>();
222        m_flexContextInfoList = new Vector<CmsFlexRequestContextInfo>();
223        m_forwardMode = false;
224        m_throwableResourceUri = null;
225    }
226
227    /**
228     * Returns the wrapped CmsObject form the provided request, or <code>null</code> if the
229     * request is not running inside OpenCms.<p>
230     *
231     * @param req the current request
232     * @return the wrapped CmsObject
233     */
234    public static CmsObject getCmsObject(ServletRequest req) {
235
236        CmsFlexController controller = (CmsFlexController)req.getAttribute(ATTRIBUTE_NAME);
237        if (controller != null) {
238            return controller.getCmsObject();
239        } else {
240            return null;
241        }
242    }
243
244    /**
245     * Returns the controller from the given request, or <code>null</code> if the
246     * request is not running inside OpenCms.<p>
247     *
248     * @param req the request to get the controller from
249     *
250     * @return the controller from the given request, or <code>null</code> if the request is not running inside OpenCms
251     */
252    public static CmsFlexController getController(ServletRequest req) {
253
254        return (CmsFlexController)req.getAttribute(ATTRIBUTE_NAME);
255    }
256
257    /**
258     * Provides access to a root cause Exception that might have occurred in a complex include scenario.<p>
259     *
260     * @param req the current request
261     *
262     * @return the root cause exception or null if no root cause exception is available
263     *
264     * @see #getThrowable()
265     */
266    public static Throwable getThrowable(ServletRequest req) {
267
268        CmsFlexController controller = (CmsFlexController)req.getAttribute(ATTRIBUTE_NAME);
269        if (controller != null) {
270            return controller.getThrowable();
271        } else {
272            return null;
273        }
274    }
275
276    /**
277     * Provides access to URI of a VFS resource that caused an exception that might have occurred in a complex include scenario.<p>
278     *
279     * @param req the current request
280     *
281     * @return to URI of a VFS resource that caused an exception, or <code>null</code>
282     *
283     * @see #getThrowableResourceUri()
284     */
285    public static String getThrowableResourceUri(ServletRequest req) {
286
287        CmsFlexController controller = (CmsFlexController)req.getAttribute(ATTRIBUTE_NAME);
288        if (controller != null) {
289            return controller.getThrowableResourceUri();
290        } else {
291            return null;
292        }
293    }
294
295    /**
296     * Checks if the provided request is running in OpenCms and the current users project is the online project.<p>
297     *
298     * @param req the current request
299     *
300     * @return <code>true</code> if the request is running in OpenCms and the current users project is
301     *      the online project, <code>false</code> otherwise
302     */
303    public static boolean isCmsOnlineRequest(ServletRequest req) {
304
305        if (req == null) {
306            return false;
307        }
308        return getController(req).getCmsObject().getRequestContext().getCurrentProject().isOnlineProject();
309    }
310
311    /**
312     * Checks if the provided request is running in OpenCms.<p>
313     *
314     * @param req the current request
315     *
316     * @return <code>true</code> if the request is running in OpenCms, <code>false</code> otherwise
317     */
318    public static boolean isCmsRequest(ServletRequest req) {
319
320        return ((req != null) && (req.getAttribute(ATTRIBUTE_NAME) != null));
321    }
322
323    /**
324     * Checks if the request has the "If-Modified-Since" header set, and if so,
325     * if the header date value is equal to the provided last modification date.<p>
326     *
327     * @param req the request to set the "If-Modified-Since" date header from
328     * @param dateLastModified the date to compare the header with
329     *
330     * @return <code>true</code> if the header is set and the header date is equal to the provided date
331     */
332    public static boolean isNotModifiedSince(HttpServletRequest req, long dateLastModified) {
333
334        // check if the request contains a last modified header
335        try {
336            long lastModifiedHeader = req.getDateHeader(CmsRequestUtil.HEADER_IF_MODIFIED_SINCE);
337            // if last modified header is set (> -1), compare it to the requested resource
338            return ((lastModifiedHeader > -1) && (((dateLastModified / 1000) * 1000) == lastModifiedHeader));
339        } catch (Exception ex) {
340            // some clients (e.g. User-Agent: BlackBerry7290/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/111)
341            // send an invalid "If-Modified-Since" header (e.g. in german locale)
342            // which breaks with http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
343            // this has to be caught because the subsequent request for the 500 error handler
344            // would run into the same exception.
345            LOG.warn(
346                Messages.get().getBundle().key(
347                    Messages.ERR_HEADER_IFMODIFIEDSINCE_FORMAT_3,
348                    new Object[] {
349                        CmsRequestUtil.HEADER_IF_MODIFIED_SINCE,
350                        req.getHeader(CmsRequestUtil.HEADER_USER_AGENT),
351                        req.getHeader(CmsRequestUtil.HEADER_IF_MODIFIED_SINCE)}));
352        }
353        return false;
354    }
355
356    /**
357     * Tells the flex controller to never cache the given attribute.<p>
358     *
359     * @param attributeName the attribute which shouldn't be cached
360     */
361    public static void registerUncacheableAttribute(String attributeName) {
362
363        uncacheableAttributes.add(attributeName);
364    }
365
366    /**
367     * Removes the controller attribute from a request.<p>
368     *
369     * @param req the request to remove the controller from
370     */
371    public static void removeController(ServletRequest req) {
372
373        CmsFlexController controller = (CmsFlexController)req.getAttribute(ATTRIBUTE_NAME);
374        if (controller != null) {
375            controller.clear();
376        }
377    }
378
379    /**
380     * Stores the given controller in the given request (using a request attribute).<p>
381     *
382     * @param req the request where to store the controller in
383     * @param controller the controller to store
384     */
385    public static void setController(ServletRequest req, CmsFlexController controller) {
386
387        req.setAttribute(CmsFlexController.ATTRIBUTE_NAME, controller);
388    }
389
390    /**
391     * Sets the <code>Expires</code> date header for a given http request.<p>
392     *
393     * Also sets the <code>cache-control: max-age</code> header to the time of the expiration.
394     * A certain upper limit is imposed on the expiration date parameter to ensure the resources are
395     * not cached to long in proxies. This can be controlled by the <code>maxAge</code> parameter.
396     * If <code>maxAge</code> is lower then 0, then a default max age of 86400000 msec (1 day) is used.<p>
397     *
398     * @param res the response to set the "Expires" date header for
399     * @param maxAge maximum amount of time in milliseconds the response remains valid
400     * @param dateExpires the date to set (if this is not in the future, it is ignored)
401     */
402    public static void setDateExpiresHeader(HttpServletResponse res, long dateExpires, long maxAge) {
403
404        long now = System.currentTimeMillis();
405        if ((dateExpires > now) && (dateExpires != CmsResource.DATE_EXPIRED_DEFAULT)) {
406
407            // important: many caches (browsers or proxy) use the "Expires" header
408            // to avoid re-loading of pages that are not expired
409            // while this is right in general, no changes before the expiration date
410            // will be displayed
411            // therefore it is better to not use an expiration to far in the future
412
413            // if no valid max age is set, restrict it to 24 hrs
414            if (maxAge < 0L) {
415                maxAge = 86400000;
416            }
417
418            if ((dateExpires - now) > maxAge) {
419                // set "Expires" header max one day into the future
420                dateExpires = now + maxAge;
421            }
422            res.setDateHeader(CmsRequestUtil.HEADER_EXPIRES, dateExpires);
423
424            // setting the "Expires" header only is not sufficient - even expired documents seems to be cached
425            // therefore, the "cache-control: max-age" is also set
426            res.setHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, CmsRequestUtil.HEADER_VALUE_MAX_AGE + (maxAge / 1000L));
427        }
428    }
429
430    /**
431     * Sets the "last modified" date header for a given http request.<p>
432     *
433     * @param res the response to set the "last modified" date header for
434     * @param dateLastModified the date to set (if this is lower then 0, the current time is set)
435     */
436    public static void setDateLastModifiedHeader(HttpServletResponse res, long dateLastModified) {
437
438        if (dateLastModified > -1) {
439            // set date last modified header (precision is only second, not millisecond
440            res.setDateHeader(CmsRequestUtil.HEADER_LAST_MODIFIED, (dateLastModified / 1000) * 1000);
441        } else {
442            // this resource can not be optimized for "last modified", use current time as header
443            res.setDateHeader(CmsRequestUtil.HEADER_LAST_MODIFIED, System.currentTimeMillis());
444            // avoiding issues with IE8+
445            res.addHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, "public, max-age=0");
446        }
447    }
448
449    /**
450     * Clears all data of this controller.<p>
451     */
452    public void clear() {
453
454        if (m_flexRequestList != null) {
455            m_flexRequestList.clear();
456        }
457        m_flexRequestList = null;
458        if (m_flexResponseList != null) {
459            m_flexResponseList.clear();
460        }
461        m_flexResponseList = null;
462        if (m_req != null) {
463            m_req.removeAttribute(ATTRIBUTE_NAME);
464        }
465        m_req = null;
466        m_res = null;
467        m_cmsObject = null;
468        m_resource = null;
469        m_cache = null;
470        m_throwable = null;
471    }
472
473    /**
474     * Returns the CmsFlexCache instance where all results from this request will be cached in.<p>
475     *
476     * This is public so that pages like the Flex Cache Administration page
477     * have a way to access the cache object.<p>
478     *
479     * @return the CmsFlexCache instance where all results from this request will be cached in
480     */
481    public CmsFlexCache getCmsCache() {
482
483        return m_cache;
484    }
485
486    /**
487     * Returns the wrapped CmsObject.<p>
488     *
489     * @return the wrapped CmsObject
490     */
491    public CmsObject getCmsObject() {
492
493        return m_cmsObject;
494    }
495
496    /**
497     * This method provides access to the top-level CmsResource of the request
498     * which is of a type that supports the FlexCache,
499     * i.e. usually the CmsFile that is identical to the file uri requested by the user,
500     * not he current included element.<p>
501     *
502     * @return the requested top-level CmsFile
503     */
504    public CmsResource getCmsResource() {
505
506        return m_resource;
507    }
508
509    /**
510     * Returns the current flex request.<p>
511     *
512     * @return the current flex request
513     */
514    public CmsFlexRequest getCurrentRequest() {
515
516        return m_flexRequestList.get(m_flexRequestList.size() - 1);
517    }
518
519    /**
520     * Returns the current flex response.<p>
521     *
522     * @return the current flex response
523     */
524    public CmsFlexResponse getCurrentResponse() {
525
526        return m_flexResponseList.get(m_flexResponseList.size() - 1);
527    }
528
529    /**
530     * Returns the combined "expires" date for all resources read during this request.<p>
531     *
532     * @return the combined "expires" date for all resources read during this request
533     */
534    public long getDateExpires() {
535
536        int pos = m_flexContextInfoList.size() - 1;
537        if (pos < 0) {
538            // ensure a valid position is used
539            return CmsResource.DATE_EXPIRED_DEFAULT;
540        }
541        return (m_flexContextInfoList.get(pos)).getDateExpires();
542    }
543
544    /**
545     * Returns the combined "last modified" date for all resources read during this request.<p>
546     *
547     * @return the combined "last modified" date for all resources read during this request
548     */
549    public long getDateLastModified() {
550
551        int pos = m_flexContextInfoList.size() - 1;
552        if (pos < 0) {
553            // ensure a valid position is used
554            return CmsResource.DATE_RELEASED_DEFAULT;
555        }
556        return (m_flexContextInfoList.get(pos)).getDateLastModified();
557    }
558
559    /**
560     * Gets the information about where to redirect to.
561     *
562     * @return the redirect information
563     */
564    public RedirectInfo getRedirectInfo() {
565
566        return m_redirectInfo;
567    }
568
569    /**
570     * Returns the size of the response stack.<p>
571     *
572     * @return the size of the response stack
573     */
574    public int getResponseStackSize() {
575
576        return m_flexResponseList.size();
577    }
578
579    /**
580     * Returns an exception (Throwable) that was caught during inclusion of sub elements,
581     * or null if no exceptions where thrown in sub elements.<p>
582     *
583     * @return an exception (Throwable) that was caught during inclusion of sub elements
584     */
585    public Throwable getThrowable() {
586
587        return m_throwable;
588    }
589
590    /**
591     * Returns the URI of a VFS resource that caused the exception that was caught during inclusion of sub elements,
592     * might return null if no URI information was available for the exception.<p>
593     *
594     * @return the URI of a VFS resource that caused the exception that was caught during inclusion of sub elements
595     */
596    public String getThrowableResourceUri() {
597
598        return m_throwableResourceUri;
599    }
600
601    /**
602     * Returns the current http request.<p>
603     *
604     * @return the current http request
605     */
606    public HttpServletRequest getTopRequest() {
607
608        return m_req;
609    }
610
611    /**
612     * Returns the current http response.<p>
613     *
614     * @return the current http response
615     */
616    public HttpServletResponse getTopResponse() {
617
618        return m_res;
619    }
620
621    /**
622     * Returns <code>true</code> if the controller does not yet contain any requests.<p>
623     *
624     * @return <code>true</code> if the controller does not yet contain any requests
625     */
626    public boolean isEmptyRequestList() {
627
628        return (m_flexRequestList != null) && m_flexRequestList.isEmpty();
629    }
630
631    /**
632     * Returns <code>true</code> if this controller is currently in "forward" mode.<p>
633     *
634     * @return <code>true</code> if this controller is currently in "forward" mode
635     */
636    public boolean isForwardMode() {
637
638        return m_forwardMode;
639    }
640
641    /**
642     * Returns <code>true</code> if the generated output of the response should
643     * be written to the stream directly.<p>
644     *
645     * @return <code>true</code> if the generated output of the response should be written to the stream directly
646     */
647    public boolean isStreaming() {
648
649        return m_streaming;
650    }
651
652    /**
653     * Returns <code>true</code> if this controller was generated as top level controller.<p>
654     *
655     * If a resource (e.g. a JSP) is processed and it's content is included in
656     * another resource, then this will be <code>false</code>.
657     *
658     * @return <code>true</code> if this controller was generated as top level controller
659     *
660     * @see org.opencms.loader.I_CmsResourceLoader#dump(CmsObject, CmsResource, String, java.util.Locale, HttpServletRequest, HttpServletResponse)
661     * @see org.opencms.jsp.CmsJspActionElement#getContent(String)
662     */
663    public boolean isTop() {
664
665        return m_top;
666    }
667
668    /**
669     * Removes the topmost request/response pair from the stack.<p>
670     */
671    public void pop() {
672
673        if ((m_flexRequestList != null) && !m_flexRequestList.isEmpty()) {
674            m_flexRequestList.remove(m_flexRequestList.size() - 1);
675        }
676        if ((m_flexResponseList != null) && !m_flexRequestList.isEmpty()) {
677            m_flexResponseList.remove(m_flexResponseList.size() - 1);
678        }
679        if ((m_flexContextInfoList != null) && !m_flexContextInfoList.isEmpty()) {
680            CmsFlexRequestContextInfo info = m_flexContextInfoList.remove(m_flexContextInfoList.size() - 1);
681            if (m_flexContextInfoList.size() > 0) {
682                (m_flexContextInfoList.get(0)).merge(info);
683                updateRequestContextInfo();
684            }
685        }
686    }
687
688    /**
689     * Adds another flex request/response pair to the stack.<p>
690     *
691     * @param req the request to add
692     * @param res the response to add
693     */
694    public void push(CmsFlexRequest req, CmsFlexResponse res) {
695
696        m_flexRequestList.add(req);
697        m_flexResponseList.add(res);
698        m_flexContextInfoList.add(new CmsFlexRequestContextInfo());
699        updateRequestContextInfo();
700    }
701
702    /**
703     * Removes request attributes which shouldn't be cached in flex cache entries from a map.<p>
704     *
705     * @param attributeMap the map of attributes
706     */
707    public void removeUncacheableAttributes(Map<String, Object> attributeMap) {
708
709        for (String uncacheableAttribute : uncacheableAttributes) {
710            attributeMap.remove(uncacheableAttribute);
711        }
712        attributeMap.remove(CmsFlexController.ATTRIBUTE_NAME);
713        attributeMap.remove(CmsDetailPageResourceHandler.ATTR_DETAIL_CONTENT_RESOURCE);
714        attributeMap.remove(CmsDetailPageResourceHandler.ATTR_DETAIL_FUNCTION_PAGE);
715    }
716
717    /**
718     * Sets the value of the "forward mode" flag.<p>
719     *
720     * @param value the forward mode to set
721     */
722    public void setForwardMode(boolean value) {
723
724        m_forwardMode = value;
725    }
726
727    /**
728     * Sets the information about where to redirect to.
729     *
730     * @param redirectInfo the redirect information
731     */
732    public void setRedirectInfo(RedirectInfo redirectInfo) {
733
734        m_redirectInfo = redirectInfo;
735    }
736
737    /**
738     * Sets an exception (Throwable) that was caught during inclusion of sub elements.<p>
739     *
740     * If another exception is already set in this controller, then the additional exception
741     * is ignored.<p>
742     *
743     * @param throwable the exception (Throwable) to set
744     * @param resource the URI of the VFS resource the error occurred on (might be <code>null</code> if unknown)
745     *
746     * @return the exception stored in the controller
747     */
748    public Throwable setThrowable(Throwable throwable, String resource) {
749
750        if (m_throwable == null) {
751            m_throwable = throwable;
752            m_throwableResourceUri = resource;
753        } else {
754            if (LOG.isDebugEnabled()) {
755                if (resource != null) {
756                    LOG.debug(
757                        Messages.get().getBundle().key(Messages.LOG_FLEXCONTROLLER_IGNORED_EXCEPTION_1, resource));
758                } else {
759                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCONTROLLER_IGNORED_EXCEPTION_0));
760                }
761            }
762        }
763        return m_throwable;
764    }
765
766    /**
767     * Puts the response in a suspended state.<p>
768     */
769    public void suspendFlexResponse() {
770
771        for (int i = 0; i < m_flexResponseList.size(); i++) {
772            CmsFlexResponse res = m_flexResponseList.get(i);
773            res.setSuspended(true);
774        }
775    }
776
777    /**
778     * Updates the "last modified" date and the "expires" date
779     * for all resources read during this request with the given values.<p>
780     *
781     * The currently stored value for "last modified" is only updated with the new value if
782     * the new value is either larger (i.e. newer) then the stored value,
783     * or if the new value is less then zero, which indicates that the "last modified"
784     * optimization can not be used because the element is dynamic.<p>
785     *
786     * The stored "expires" value is only updated if the new value is smaller
787     * then the stored value.<p>
788     *
789     * @param dateLastModified the value to update the "last modified" date with
790     * @param dateExpires the value to update the "expires" date with
791     */
792    public void updateDates(long dateLastModified, long dateExpires) {
793
794        int pos = m_flexContextInfoList.size() - 1;
795        if (pos < 0) {
796            // ensure a valid position is used
797            return;
798        }
799        (m_flexContextInfoList.get(pos)).updateDates(dateLastModified, dateExpires);
800    }
801
802    /**
803     * Updates the context info of the request context.<p>
804     */
805    private void updateRequestContextInfo() {
806
807        if ((m_flexContextInfoList != null) && !m_flexContextInfoList.isEmpty()) {
808            m_cmsObject.getRequestContext().setAttribute(
809                CmsRequestUtil.HEADER_LAST_MODIFIED,
810                m_flexContextInfoList.get(m_flexContextInfoList.size() - 1));
811        }
812    }
813}