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)
095    throws CmsResourceInitException, CmsPermissionViolationException {
096
097        // only do something if the resource was not found
098        if (resource == null) {
099            String uri = cms.getRequestContext().getUri();
100            // check if the resource starts with the PERMALINK_HANDLER
101            Matcher matcher = m_simplePermalinkPattern.matcher(uri);
102            if (matcher.find()) {
103                CmsResource resource1 = resource;
104                // get the id of the real resource
105                String id = matcher.group(1);
106                String storedSiteRoot = cms.getRequestContext().getSiteRoot();
107                try {
108                    // we now must switch to the root site to read the resource
109                    cms.getRequestContext().setSiteRoot("/");
110                    // read the resource
111                    boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject();
112                    CmsResourceFilter filter = online ? CmsResourceFilter.DEFAULT : CmsResourceFilter.IGNORE_EXPIRATION;
113                    resource1 = cms.readDefaultFile(id, filter);
114                } catch (CmsPermissionViolationException e) {
115                    throw e;
116                } catch (Throwable e) {
117                    CmsMessageContainer msg = Messages.get().container(Messages.ERR_PERMALINK_1, id);
118                    if (LOG.isErrorEnabled()) {
119                        LOG.error(msg.key(), e);
120                    }
121                    throw new CmsResourceInitException(msg, e);
122                } finally {
123                    // restore the siteroot
124                    cms.getRequestContext().setSiteRoot(storedSiteRoot);
125                    // resource may be null in case of an error
126                    if (resource1 != null) {
127                        // modify the uri to the one of the real resource
128                        cms.getRequestContext().setUri(cms.getSitePath(resource1));
129                    }
130                }
131                resource = resource1;
132            } else {
133                matcher = m_detailPattern.matcher(uri);
134                // detail page permalink. Handle the cases 'getI18NInfo' and 'showResource' differently:
135                // In the 'showResource' case, we do a redirect to the real detail page URL
136                // In the 'getI18NInfo' case, we return the container page so the locale in the CmsRequestContext is set correctly for the 'showResource' case
137                if (matcher.find()) {
138                    try {
139                        CmsUUID pageId = new CmsUUID(matcher.group(1));
140                        CmsUUID detailId = new CmsUUID(matcher.group(2));
141                        CmsResource pageResource = cms.readResource(pageId);
142                        if (res != null) {
143                            CmsResource detailResource = cms.readResource(detailId);
144                            String detailName = cms.getDetailName(
145                                detailResource,
146                                cms.getRequestContext().getLocale(), // the locale in the request context should be the locale of the container page
147                                OpenCms.getLocaleManager().getDefaultLocales());
148                            CmsResource parentFolder;
149                            if (pageResource.isFile()) {
150                                parentFolder = cms.readParentFolder(pageResource.getStructureId());
151                            } else {
152                                parentFolder = pageResource;
153                            }
154                            String baseLink = OpenCms.getLinkManager().substituteLink(cms, parentFolder);
155                            String redirectLink = baseLink + (baseLink.endsWith("/") ? "" : "/") + detailName;
156                            CmsResourceInitException resInitException = new CmsResourceInitException(getClass());
157
158                            resInitException.setClearErrors(true);
159                            res.sendRedirect(redirectLink);
160                            throw resInitException;
161                        } else {
162                            // we're being called from getI18NInfo; assume that the locale for the container page is the locale we want
163                            return pageResource;
164                        }
165                    } catch (CmsResourceInitException e) {
166                        throw e;
167                    } catch (Exception e) {
168                        LOG.error(e.getLocalizedMessage(), e);
169                        throw new CmsResourceInitException(getClass());
170                    }
171                }
172                return null;
173            }
174        }
175        if ((resource != null) && resource.isInternalOrInInternalFolder()) {
176            return null;
177        }
178        return resource;
179    }
180
181}