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.main;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.i18n.CmsMessageContainer;
034import org.opencms.security.CmsPermissionViolationException;
035import org.opencms.util.CmsUUID;
036
037import java.util.regex.Matcher;
038import java.util.regex.Pattern;
039
040import javax.servlet.http.HttpServletRequest;
041import javax.servlet.http.HttpServletResponse;
042
043import org.apache.commons.logging.Log;
044
045/**
046 * Resource init handler that loads a resource given its permalink.<p>
047 *
048 * The permalink must have following format:<br>
049 * <code>/${CONTEXT}/${SERVLET}/permalink/${UUID}.${EXT}</code><p>
050 *
051 * for example:<br>
052 * <code>/opencms/opencms/permalink/a7b5d298-b3ab-11d8-b3e3-514d35713fed.html</code><p>
053 *
054 * @since 6.3
055 */
056public class CmsPermalinkResourceHandler implements I_CmsResourceInit {
057
058    /** Regex for capturing a UUID. */
059    public static final String CAPTURE_UUID_REGEX = "(" + CmsUUID.UUID_REGEX + ")";
060
061    /** The permalink handler path. */
062    public static final String PERMALINK_HANDLER = "/permalink/";
063
064    /** Regex for the optional file extension. */
065    public static final String SUFFIX_REGEX = "(?:\\.[a-zA-Z0-9]*)?$";
066
067    /** The log object for this class. */
068    private static final Log LOG = CmsLog.getLog(CmsPermalinkResourceHandler.class);
069
070    /** The compiled pattern for detail page permalinks. */
071    private Pattern m_detailPattern;
072
073    /** The pattern used to match permalink uris and extract the structure id. */
074    private Pattern m_simplePermalinkPattern;
075
076    /**
077     * Default constructor.<p>
078     */
079    public CmsPermalinkResourceHandler() {
080
081        String uriRegex = PERMALINK_HANDLER + CAPTURE_UUID_REGEX + SUFFIX_REGEX;
082        String detailUriRegex = PERMALINK_HANDLER + CAPTURE_UUID_REGEX + ":" + CAPTURE_UUID_REGEX + SUFFIX_REGEX;
083        m_simplePermalinkPattern = Pattern.compile(uriRegex);
084        m_detailPattern = Pattern.compile(detailUriRegex);
085    }
086
087    /**
088     * @see org.opencms.main.I_CmsResourceInit#initResource(org.opencms.file.CmsResource, org.opencms.file.CmsObject, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
089     */
090    public CmsResource initResource(
091        CmsResource resource,
092        CmsObject cms,
093        HttpServletRequest req,
094        HttpServletResponse res) throws CmsResourceInitException, CmsPermissionViolationException {
095
096        // only do something if the resource was not found
097        if (resource == null) {
098            String uri = cms.getRequestContext().getUri();
099            // check if the resource starts with the PERMALINK_HANDLER
100            Matcher matcher = m_simplePermalinkPattern.matcher(uri);
101            if (matcher.find()) {
102                CmsResource resource1 = resource;
103                // get the id of the real resource
104                String id = matcher.group(1);
105                String storedSiteRoot = cms.getRequestContext().getSiteRoot();
106                try {
107                    // we now must switch to the root site to read the resource
108                    cms.getRequestContext().setSiteRoot("/");
109                    // read the resource
110                    boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject();
111                    CmsResourceFilter filter = online ? CmsResourceFilter.DEFAULT : CmsResourceFilter.IGNORE_EXPIRATION;
112                    resource1 = cms.readDefaultFile(id, filter);
113                } catch (CmsPermissionViolationException e) {
114                    throw e;
115                } catch (Throwable e) {
116                    CmsMessageContainer msg = Messages.get().container(Messages.ERR_PERMALINK_1, id);
117                    if (LOG.isErrorEnabled()) {
118                        LOG.error(msg.key(), e);
119                    }
120                    throw new CmsResourceInitException(msg, e);
121                } finally {
122                    // restore the siteroot
123                    cms.getRequestContext().setSiteRoot(storedSiteRoot);
124                    // resource may be null in case of an error
125                    if (resource1 != null) {
126                        // modify the uri to the one of the real resource
127                        cms.getRequestContext().setUri(cms.getSitePath(resource1));
128                    }
129                }
130                resource = resource1;
131            } else {
132                matcher = m_detailPattern.matcher(uri);
133                // detail page permalink. Handle the cases 'getI18NInfo' and 'showResource' differently:
134                // In the 'showResource' case, we do a redirect to the real detail page URL
135                // In the 'getI18NInfo' case, we return the container page so the locale in the CmsRequestContext is set correctly for the 'showResource' case
136                if (matcher.find()) {
137                    try {
138                        CmsUUID pageId = new CmsUUID(matcher.group(1));
139                        CmsUUID detailId = new CmsUUID(matcher.group(2));
140                        CmsResource pageResource = cms.readResource(pageId);
141                        if (res != null) {
142                            CmsResource detailResource = cms.readResource(detailId);
143                            String detailName = cms.getDetailName(
144                                detailResource,
145                                cms.getRequestContext().getLocale(), // the locale in the request context should be the locale of the container page
146                                OpenCms.getLocaleManager().getDefaultLocales());
147                            CmsResource parentFolder;
148                            if (pageResource.isFile()) {
149                                parentFolder = cms.readParentFolder(pageResource.getStructureId());
150                            } else {
151                                parentFolder = pageResource;
152                            }
153                            String baseLink = OpenCms.getLinkManager().substituteLink(cms, parentFolder);
154                            String redirectLink = baseLink + (baseLink.endsWith("/") ? "" : "/") + detailName;
155                            CmsResourceInitException resInitException = new CmsResourceInitException(getClass());
156
157                            resInitException.setClearErrors(true);
158                            res.sendRedirect(redirectLink);
159                            throw resInitException;
160                        } else {
161                            // we're being called from getI18NInfo; assume that the locale for the container page is the locale we want
162                            return pageResource;
163                        }
164                    } catch (CmsResourceInitException e) {
165                        throw e;
166                    } catch (Exception e) {
167                        LOG.error(e.getLocalizedMessage(), e);
168                        throw new CmsResourceInitException(getClass());
169                    }
170                }
171                return null;
172            }
173        }
174        return resource;
175    }
176
177}