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.file;
029
030import org.opencms.main.CmsIllegalArgumentException;
031import org.opencms.main.OpenCms;
032import org.opencms.security.CmsOrganizationalUnit;
033import org.opencms.site.CmsSiteMatcher;
034import org.opencms.util.CmsResourceTranslator;
035import org.opencms.util.CmsUUID;
036import org.opencms.workplace.CmsWorkplace;
037
038import java.util.Hashtable;
039import java.util.Locale;
040import java.util.Map;
041
042/**
043 * Stores the information about the current users OpenCms context,
044 * for example the requested URI, the current project, the selected site and more.<p>
045 *
046 * @since 6.0.0
047 */
048public final class CmsRequestContext {
049
050    /** Request context attribute for indicating that an editor is currently open. */
051    public static final String ATTRIBUTE_EDITOR = CmsRequestContext.class.getName() + ".ATTRIBUTE_EDITOR";
052
053    /** Request context attribute for indicating we want full links generated for HTML fields. */
054    public static final String ATTRIBUTE_FULLLINKS = CmsRequestContext.class.getName() + ".ATTRIBUTE_FULLLINKS";
055
056    /** Request context attribute for indicating the model file for a create resource operation. */
057    public static final String ATTRIBUTE_MODEL = CmsRequestContext.class.getName() + ".ATTRIBUTE_MODEL";
058
059    /** Request context attribute for indicating content locale for a create resource operation. */
060    public static final String ATTRIBUTE_NEW_RESOURCE_LOCALE = CmsRequestContext.class.getName()
061        + ".NEW_RESOURCE_LOCALE";
062
063    /** A map for storing (optional) request context attributes. */
064    private Map<String, Object> m_attributeMap;
065
066    /** The current project. */
067    private CmsProject m_currentProject;
068
069    /** The detail content resource (possibly null). */
070    private CmsResource m_detailResource;
071
072    /** Directory name translator. */
073    private CmsResourceTranslator m_directoryTranslator;
074
075    /** Current encoding. */
076    private String m_encoding;
077
078    /** File name translator. */
079    private CmsResourceTranslator m_fileTranslator;
080
081    /** Flag to control whether links should be absolute even if we're linking to the current site. */
082    private boolean m_forceAbsoluteLinks;
083
084    /** The secure request flag. */
085    private boolean m_isSecureRequest;
086
087    /** The locale used by this request context. */
088    private Locale m_locale;
089
090    /** The fully qualified name of the organizational unit for this request. */
091    private String m_ouFqn;
092
093    /** The remote ip address. */
094    private String m_remoteAddr;
095
096    /** the matcher for the current request, that is the host part of the URI from the original http request. */
097    private CmsSiteMatcher m_requestMatcher;
098
099    /** The current request time. */
100    private long m_requestTime;
101
102    /** The name of the root, e.g. /site_a/vfs. */
103    private String m_siteRoot;
104
105    /** Flag to indicate that this context should not update the user session. */
106    private boolean m_updateSession;
107
108    /** The URI for getUri() in case it is "overwritten".  */
109    private String m_uri;
110
111    /** The current user. */
112    private CmsUser m_user;
113
114    /**
115     * Constructs a new request context.<p>
116     *
117     * @param user the current user
118     * @param project the current project
119     * @param requestedUri the requested OpenCms VFS URI
120     * @param requestMatcher the matcher for the current request, that is the host part of the URI from the original http request
121     * @param siteRoot the users current site root
122     * @param isSecureRequest true if this is a secure request
123     * @param locale the users current locale
124     * @param encoding the encoding to use for this request
125     * @param remoteAddr the remote IP address of the user
126     * @param requestTime the time of the request (used for resource publication / expiration date)
127     * @param directoryTranslator the directory translator
128     * @param fileTranslator the file translator
129     * @param ouFqn the fully qualified name of the organizational unit
130     * @param forceAbsoluteLinks if true, links should be generated with a server prefix even if we're linking to the current site
131     */
132    public CmsRequestContext(
133        CmsUser user,
134        CmsProject project,
135        String requestedUri,
136        CmsSiteMatcher requestMatcher,
137        String siteRoot,
138        boolean isSecureRequest,
139        Locale locale,
140        String encoding,
141        String remoteAddr,
142        long requestTime,
143        CmsResourceTranslator directoryTranslator,
144        CmsResourceTranslator fileTranslator,
145        String ouFqn,
146        boolean forceAbsoluteLinks) {
147
148        m_updateSession = true;
149        m_user = user;
150        m_currentProject = project;
151        m_uri = requestedUri;
152        m_requestMatcher = requestMatcher;
153        m_isSecureRequest = isSecureRequest;
154        setSiteRoot(siteRoot);
155        m_locale = locale;
156        m_encoding = encoding;
157        m_remoteAddr = remoteAddr;
158        m_requestTime = requestTime;
159        m_directoryTranslator = directoryTranslator;
160        m_fileTranslator = fileTranslator;
161        setOuFqn(ouFqn);
162        m_forceAbsoluteLinks = forceAbsoluteLinks;
163    }
164
165    /**
166     * Returns the adjusted site root for a resource using the provided site root as a base.<p>
167     *
168     * Usually, this would be the site root for the current site.
169     * However, if a resource from the <code>/system/</code> folder is requested,
170     * this will be the empty String.<p>
171     *
172     * @param siteRoot the site root of the current site
173     * @param resourcename the resource name to get the adjusted site root for
174     *
175     * @return the adjusted site root for the resource
176     */
177    public static String getAdjustedSiteRoot(String siteRoot, String resourcename) {
178
179        if (resourcename.startsWith(CmsWorkplace.VFS_PATH_SYSTEM)
180            || OpenCms.getSiteManager().startsWithShared(resourcename)
181            || (resourcename.startsWith(CmsWorkplace.VFS_PATH_SITES) && !resourcename.startsWith(siteRoot))) {
182            return "";
183        } else {
184            return siteRoot;
185        }
186    }
187
188    /**
189     * Adds the current site root of this context to the given resource name,
190     * and also translates the resource name with the configured the directory translator.<p>
191     *
192     * @param resourcename the resource name
193     * @return the translated resource name including site root
194     * @see #addSiteRoot(String, String)
195     */
196    public String addSiteRoot(String resourcename) {
197
198        return addSiteRoot(m_siteRoot, resourcename);
199    }
200
201    /**
202     * Adds the given site root of this context to the given resource name,
203     * taking into account special folders like "/system" where no site root must be added,
204     * and also translates the resource name with the configured the directory translator.<p>
205     *
206     * @param siteRoot the site root to add
207     * @param resourcename the resource name
208     * @return the translated resource name including site root
209     */
210    public String addSiteRoot(String siteRoot, String resourcename) {
211
212        if ((resourcename == null) || (siteRoot == null)) {
213            return null;
214        }
215        siteRoot = getAdjustedSiteRoot(siteRoot, resourcename);
216        StringBuffer result = new StringBuffer(128);
217        result.append(siteRoot);
218        if (((siteRoot.length() == 0) || (siteRoot.charAt(siteRoot.length() - 1) != '/'))
219            && ((resourcename.length() == 0) || (resourcename.charAt(0) != '/'))) {
220            // add slash between site root and resource if required
221            result.append('/');
222        }
223        result.append(resourcename);
224        return m_directoryTranslator.translateResource(result.toString());
225    }
226
227    /**
228     * Returns the current project of the current user.
229     *
230     * @return the current project of the current user
231     *
232     * @deprecated use {@link #getCurrentProject()} instead
233     */
234    @Deprecated
235    public CmsProject currentProject() {
236
237        return getCurrentProject();
238    }
239
240    /**
241     * Returns the current user object.<p>
242     *
243     * @return the current user object
244     *
245     * @deprecated use {@link #getCurrentUser()} instead
246     */
247    @Deprecated
248    public CmsUser currentUser() {
249
250        return getCurrentUser();
251    }
252
253    /**
254     * Returns the adjusted site root for a resource this context current site root.<p>
255     *
256     * @param resourcename the resource name to get the adjusted site root for
257     *
258     * @return the adjusted site root for the resource
259     *
260     * @see #getAdjustedSiteRoot(String, String)
261     */
262    public String getAdjustedSiteRoot(String resourcename) {
263
264        return getAdjustedSiteRoot(m_siteRoot, resourcename);
265    }
266
267    /**
268     * Gets the value of an attribute from the OpenCms request context attribute list.<p>
269     *
270     * @param attributeName the attribute name
271     * @return Object the attribute value, or <code>null</code> if the attribute was not found
272     */
273    public Object getAttribute(String attributeName) {
274
275        if (m_attributeMap == null) {
276            return null;
277        }
278        return m_attributeMap.get(attributeName);
279    }
280
281    /**
282     * Returns the current project of the current user.
283     *
284     * @return the current project of the current user
285     */
286    public CmsProject getCurrentProject() {
287
288        return m_currentProject;
289    }
290
291    /**
292     * Returns the current user object.<p>
293     *
294     * @return the current user object
295     */
296    public CmsUser getCurrentUser() {
297
298        return m_user;
299    }
300
301    /**
302     * Gets the detail content structure id (or null if no detail content has been loaded).<p>
303     *
304     * @return the detail content id
305     */
306    public CmsUUID getDetailContentId() {
307
308        if (m_detailResource == null) {
309            return null;
310        }
311        return m_detailResource.getStructureId();
312    }
313
314    /**
315     * Gets the detail content resource (or null if no detail content has been loaded).<p>
316     *
317     * @return the detail content resource
318     */
319    public CmsResource getDetailResource() {
320
321        return m_detailResource;
322    }
323
324    /**
325     * Returns the directory name translator this context was initialized with.<p>
326     *
327     * The directory translator is used to translate old VFS path information
328     * to a new location. Example: <code>/bodys/index.html --> /system/bodies/</code>.<p>
329     *
330     * @return the directory name translator this context was initialized with
331     */
332    public CmsResourceTranslator getDirectoryTranslator() {
333
334        return m_directoryTranslator;
335    }
336
337    /**
338     * Returns the current content encoding to be used in HTTP response.<p>
339     *
340     * @return the encoding
341     */
342    public String getEncoding() {
343
344        return m_encoding;
345    }
346
347    /**
348     * Returns the file name translator this context was initialized with.<p>
349     *
350     * The file name translator is used to translate filenames from uploaded files
351     * to valid OpenCms filenames. Example: <code>W&uuml;ste W&ouml;rter.doc --> Wueste_Woerter.doc</code>.<p>
352     *
353     * @return the file name translator this context was initialized with
354     */
355    public CmsResourceTranslator getFileTranslator() {
356
357        return m_fileTranslator;
358    }
359
360    /**
361     * Gets the name of the parent folder of the requested file.<p>
362     *
363     * @return the name of the parent folder of the requested file
364     */
365    public String getFolderUri() {
366
367        return CmsResource.getFolderPath(m_uri);
368    }
369
370    /**
371     * Returns the locale used by this request context.<p>
372     *
373     * In normal operation, the request context locale is initialized using
374     * {@link org.opencms.i18n.I_CmsLocaleHandler#getI18nInfo(javax.servlet.http.HttpServletRequest, CmsUser, CmsProject, String)}
375     * depending on the requested resource URI.<p>
376     *
377     * @return the locale used by this request context
378     *
379     * @see org.opencms.i18n.I_CmsLocaleHandler#getI18nInfo(javax.servlet.http.HttpServletRequest, CmsUser, CmsProject, String)
380     * @see org.opencms.i18n.CmsLocaleManager#getDefaultLocale(CmsObject, String)
381     */
382    public Locale getLocale() {
383
384        return m_locale;
385    }
386
387    /**
388     * Returns the fully qualified name of the organizational unit.<p>
389     *
390     * @return the fully qualified name of the organizational unit
391     */
392    public String getOuFqn() {
393
394        return m_ouFqn;
395    }
396
397    /**
398     * Returns the remote ip address.<p>
399     *
400     * @return the remote ip address as string
401     */
402    public String getRemoteAddress() {
403
404        return m_remoteAddr;
405    }
406
407    /**
408     * Returns the matcher for the current request, that is the host part of the URI from the original http request.<p>
409     *
410     * @return the matcher for the current request, that is the host part of the URI from the original http request
411     */
412    public CmsSiteMatcher getRequestMatcher() {
413
414        return m_requestMatcher;
415    }
416
417    /**
418     * Returns the current request time.<p>
419     *
420     * @return the current request time
421     */
422    public long getRequestTime() {
423
424        return m_requestTime;
425    }
426
427    /**
428     * Returns this request contexts uri extended with the current site root path.<p>
429     *
430     * @return this request contexts uri extended with the current site root path
431     *
432     * @see #getUri()
433     * @see #addSiteRoot(String)
434     */
435    public String getRootUri() {
436
437        return addSiteRoot(m_siteRoot, m_uri);
438    }
439
440    /**
441     * Adjusts the absolute resource root path for the current site.<p>
442     *
443     * The full root path of a resource is always available using
444     * <code>{@link CmsResource#getRootPath()}</code>. From this name this method cuts
445     * of the current site root using
446     * <code>{@link CmsRequestContext#removeSiteRoot(String)}</code>.<p>
447     *
448     * If the resource root path does not start with the current site root,
449     * it is left untouched.<p>
450     *
451     * @param resource the resource to get the adjusted site root path for
452     *
453     * @return the absolute resource path adjusted for the current site
454     *
455     * @see #removeSiteRoot(String)
456     * @see CmsResource#getRootPath()
457     * @see CmsObject#getSitePath(CmsResource)
458     */
459    public String getSitePath(CmsResource resource) {
460
461        return removeSiteRoot(resource.getRootPath());
462    }
463
464    /**
465     * Returns the current root directory in the virtual file system.<p>
466     *
467     * @return the current root directory in the virtual file system
468     */
469    public String getSiteRoot() {
470
471        return m_siteRoot;
472    }
473
474    /**
475     * Returns the OpenCms VFS URI of the requested resource.<p>
476     *
477     * @return the OpenCms VFS URI of the requested resource
478     */
479    public String getUri() {
480
481        return m_uri;
482    }
483
484    /**
485     * Returns true if links to the current site should be generated as absolute links, i.e. with a server prefix.
486     *
487     * @return true if links to the current site should be absolute
488     */
489    public boolean isForceAbsoluteLinks() {
490
491        return m_forceAbsoluteLinks;
492    }
493
494    /**
495     * Returns true if this is a secure request.<p>
496     *
497     * @return true if this is secure
498     */
499    public boolean isSecureRequest() {
500
501        return m_isSecureRequest;
502    }
503
504    /**
505     * Check if this request context will update the session.<p>
506     *
507     * This is used mainly for CmsReports that continue to use the
508     * users context, even after the http request is already finished.<p>
509     *
510     * @return true if this request context will update the session, false otherwise
511     */
512    public boolean isUpdateSessionEnabled() {
513
514        return m_updateSession;
515    }
516
517    /**
518     * Removes an attribute from the request context.<p>
519     *
520     * @param key the name of the attribute to remove
521     *
522     * @return the removed attribute, or <code>null</code> if no attribute was set with this name
523     */
524    public Object removeAttribute(String key) {
525
526        if (m_attributeMap != null) {
527            return m_attributeMap.remove(key);
528        }
529        return null;
530    }
531
532    /**
533     * Removes the current site root prefix from the absolute path in the resource name,
534     * that is adjusts the resource name for the current site root.<p>
535     *
536     * If the resource name does not start with the current site root,
537     * it is left untouched.<p>
538     *
539     * @param resourcename the resource name
540     *
541     * @return the resource name adjusted for the current site root
542     *
543     * @see #getSitePath(CmsResource)
544     */
545    public String removeSiteRoot(String resourcename) {
546
547        String siteRoot = getAdjustedSiteRoot(m_siteRoot, resourcename);
548        if ((siteRoot == m_siteRoot)
549            && resourcename.startsWith(siteRoot)
550            && ((resourcename.length() == siteRoot.length()) || (resourcename.charAt(siteRoot.length()) == '/'))) {
551            resourcename = resourcename.substring(siteRoot.length());
552        }
553        if (resourcename.length() == 0) {
554            // input was a site root folder without trailing slash
555            resourcename = "/";
556        }
557        return resourcename;
558    }
559
560    /**
561     * Sets an attribute in the request context.<p>
562     *
563     * @param key the attribute name
564     * @param value the attribute value
565     */
566    public void setAttribute(String key, Object value) {
567
568        if (m_attributeMap == null) {
569            // hash table is still the most efficient form of a synchronized Map
570            m_attributeMap = new Hashtable<String, Object>();
571        }
572        m_attributeMap.put(key, value);
573    }
574
575    /**
576     * Sets the current project for the user.<p>
577     *
578     * @param project the project to be set as current project
579     *
580     * @return the CmsProject instance
581     */
582    public CmsProject setCurrentProject(CmsProject project) {
583
584        if (project != null) {
585            m_currentProject = project;
586        }
587        return m_currentProject;
588    }
589
590    /**
591     * Sets the detail content resource.<p>
592     *
593     * @param detailResource the detail content resource
594     */
595    public void setDetailResource(CmsResource detailResource) {
596
597        m_detailResource = detailResource;
598    }
599
600    /**
601     * Sets the current content encoding to be used in HTTP response.<p>
602     *
603     * @param encoding the encoding
604     */
605    public void setEncoding(String encoding) {
606
607        m_encoding = encoding;
608    }
609
610    /**
611     * Enables/disables link generation with full server prefix for the current site.
612     *
613     * @param forceAbsoluteLinks true if links to the current site should be generated with server prefix
614     */
615    public void setForceAbsoluteLinks(boolean forceAbsoluteLinks) {
616
617        m_forceAbsoluteLinks = forceAbsoluteLinks;
618    }
619
620    /**
621     * Sets the locale used by this request context.<p>
622     *
623     * @param locale the locale to set
624     *
625     * @see #getLocale() for more information about how the locale is set in normal operation
626     */
627    public void setLocale(Locale locale) {
628
629        m_locale = locale;
630    }
631
632    /**
633     * Sets the organizational unit fully qualified name.<p>
634     *
635     * @param ouFqn the organizational unit fully qualified name
636     */
637    public void setOuFqn(String ouFqn) {
638
639        String userOu = CmsOrganizationalUnit.getParentFqn(m_user.getName());
640        if (ouFqn != null) {
641            if (ouFqn.startsWith(userOu)
642                || (ouFqn.startsWith(CmsOrganizationalUnit.SEPARATOR) && ouFqn.substring(1).startsWith(userOu))) {
643                m_ouFqn = ouFqn;
644            } else {
645                throw new CmsIllegalArgumentException(
646                    Messages.get().container(Messages.ERR_BAD_ORGUNIT_2, ouFqn, userOu));
647            }
648        } else {
649            m_ouFqn = userOu;
650        }
651        m_ouFqn = CmsOrganizationalUnit.removeLeadingSeparator(m_ouFqn);
652    }
653
654    /**
655     * Sets the current request time.<p>
656     *
657     * @param time the request time
658     */
659    public void setRequestTime(long time) {
660
661        m_requestTime = time;
662    }
663
664    /**
665     * Sets the 'secure request' status.<p>
666     *
667     * @param secureRequest the new value
668     */
669    public void setSecureRequest(boolean secureRequest) {
670
671        m_isSecureRequest = secureRequest;
672    }
673
674    /**
675     * Sets the current root directory in the virtual file system.<p>
676     *
677     * @param root the name of the new root directory
678     */
679    public void setSiteRoot(String root) {
680
681        // site roots must never end with a "/"
682        if (root.endsWith("/")) {
683            m_siteRoot = root.substring(0, root.length() - 1);
684        } else {
685            m_siteRoot = root;
686        }
687    }
688
689    /**
690     * Mark this request context to update the session or not.<p>
691     *
692     * @param value true if this request context will update the session, false otherwise
693     */
694    public void setUpdateSessionEnabled(boolean value) {
695
696        m_updateSession = value;
697    }
698
699    /**
700     * Set the requested resource OpenCms VFS URI, that is the value returned by {@link #getUri()}.<p>
701     *
702     * Use this with caution! Many things (caches etc.) depend on this value.
703     * If you change this value, better make sure that you change it only temporarily
704     * and reset it in a <code>try { // do something // } finally { // reset URI // }</code> statement.<p>
705     *
706     * @param value the value to set the Uri to, must be a complete OpenCms path name like /system/workplace/style.css
707     */
708    public void setUri(String value) {
709
710        m_uri = value;
711    }
712
713    /**
714     * Switches the user in the context, required after a login.<p>
715     *
716     * @param user the new user to use
717     * @param project the new users current project
718     * @param ouFqn the organizational unit
719     */
720    protected void switchUser(CmsUser user, CmsProject project, String ouFqn) {
721
722        m_user = user;
723        m_currentProject = project;
724        setOuFqn(ouFqn);
725    }
726}