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