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