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.history;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.file.Messages;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.CmsResourceInitException;
037import org.opencms.main.I_CmsResourceInit;
038import org.opencms.util.CmsRequestUtil;
039
040import java.util.Map;
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 * Resource init handler that loads historical versions of resources.<p>
050 *
051 * @since 6.9.1
052 */
053public class CmsHistoryResourceHandler implements I_CmsResourceInit {
054
055    /** Constant for the historical version request attribute name. */
056    public static final String ATTRIBUTE_NAME = "org.opencms.file.history.CmsHistoryResourceHandler";
057
058    /** The historical version handler path. */
059    public static final String HISTORY_HANDLER = "/system/config/showversion";
060
061    /** Request parameter name for the version number. */
062    public static final String PARAM_VERSION = "version";
063
064    /** Constant for the offline project version. */
065    public static final int PROJECT_OFFLINE_VERSION = Integer.MAX_VALUE;
066
067    /** The static log object for this class. */
068    private static final Log LOG = CmsLog.getLog(CmsHistoryResourceHandler.class);
069
070    /**
071     * Returns the historical version of a resource,
072     * if the given request is displaying a history version.<p>
073     *
074     * @param req the request to check
075     *
076     * @return the historical resource if the given request is displaying an historical version
077     */
078    public static I_CmsHistoryResource getHistoryResource(ServletRequest req) {
079
080        return (I_CmsHistoryResource)req.getAttribute(ATTRIBUTE_NAME);
081    }
082
083    /**
084     * Appends the <code>version</code> parameter to the URI if needed.<p>
085     *
086     * @param uri the resource URI
087     * @param req the current request
088     *
089     * @return the same URI, with additional parameters in case of a historical request
090     */
091    public static String getHistoryResourceURI(String uri, ServletRequest req) {
092
093        String histUri = uri;
094        if (CmsHistoryResourceHandler.isHistoryRequest(req)) {
095            String version = req.getParameter(CmsHistoryResourceHandler.PARAM_VERSION);
096            histUri = CmsRequestUtil.appendParameter(uri, CmsHistoryResourceHandler.PARAM_VERSION, version);
097        }
098        return histUri;
099    }
100
101    /**
102     * Returns the correct resource for the given URI, taken into account historical versions
103     * marked by the <code>version</code> parameter.<p>
104     *
105     * @param cms the current CMS context
106     * @param resourceUri the resource URI
107     *
108     * @return the resource, which can be an instance of {@link org.opencms.file.history.I_CmsHistoryResource}
109     *
110     * @throws CmsException if something goes wrong
111     */
112    public static CmsResource getResourceWithHistory(CmsObject cms, String resourceUri) throws CmsException {
113
114        CmsResource resource = null;
115
116        if (resourceUri.contains(CmsRequestUtil.URL_DELIMITER)) {
117            int pos = resourceUri.indexOf(CmsRequestUtil.URL_DELIMITER);
118            Map<String, String[]> params = CmsRequestUtil.createParameterMap(resourceUri.substring(pos));
119            if (params.containsKey(CmsHistoryResourceHandler.PARAM_VERSION)) {
120                int version = Integer.parseInt(params.get(CmsHistoryResourceHandler.PARAM_VERSION)[0]);
121                String sitemapPath = resourceUri.substring(0, pos);
122                resource = cms.readResource(sitemapPath);
123                resource = (CmsResource)cms.readResource(resource.getStructureId(), version);
124            }
125        }
126        if (resource == null) {
127            resource = cms.readResource(resourceUri);
128        }
129        return resource;
130    }
131
132    /**
133     * Returns <code>true</code> if the given request is displaying an historical version.<p>
134     *
135     * @param req the request to check
136     *
137     * @return <code>true</code> if the given request is displaying a historical version
138     */
139    public static boolean isHistoryRequest(ServletRequest req) {
140
141        return (null != req.getAttribute(ATTRIBUTE_NAME));
142    }
143
144    /**
145     * @see org.opencms.main.I_CmsResourceInit#initResource(org.opencms.file.CmsResource, org.opencms.file.CmsObject, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
146     */
147    public CmsResource initResource(
148        CmsResource resource,
149        CmsObject cms,
150        HttpServletRequest req,
151        HttpServletResponse res)
152    throws CmsResourceInitException {
153
154        // we only have to check for history resources if the handler was called
155        // during a real request and NOT during a dummy-request while doing
156        // a static export
157        if (req != null) {
158            String uri = cms.getRequestContext().getUri();
159            // check if the resource starts with the HISTORY_HANDLER
160            if (uri.startsWith(HISTORY_HANDLER)) {
161                String version = req.getParameter(PARAM_VERSION);
162
163                // only do something if the resource was not found and there was a "versionid" parameter included
164                if ((resource == null) && (version != null)) {
165
166                    // test if the current user is allowed to read historical versions of resources
167                    // this can be done by trying to read the history handler resource
168                    if (cms.existsResource(HISTORY_HANDLER)) {
169                        String storedSiteRoot = cms.getRequestContext().getSiteRoot();
170                        try {
171                            // we now must switch to the root site to read the history resource
172                            cms.getRequestContext().setSiteRoot("/");
173
174                            // extract the "real" resourcename
175                            uri = uri.substring(HISTORY_HANDLER.length(), uri.length());
176                            int id = Integer.valueOf(version).intValue();
177                            if (id == CmsHistoryResourceHandler.PROJECT_OFFLINE_VERSION) {
178                                resource = new CmsHistoryFile(cms.readFile(uri, CmsResourceFilter.IGNORE_EXPIRATION));
179                            } else {
180                                // get the current resource
181                                CmsResource currRes = cms.readResource(uri, CmsResourceFilter.IGNORE_EXPIRATION);
182                                // get the historical version of the resource
183                                CmsHistoryFile hisRes = (CmsHistoryFile)cms.readResource(
184                                    cms.readResource(uri, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId(),
185                                    id);
186
187                                // the resource root path is not changed after the resource is moved or renamed
188                                // change the resource root path to current root path, so e.g. properties can be read if necessary
189                                if (!currRes.getRootPath().equals(hisRes.getRootPath())) {
190
191                                    resource = new CmsHistoryFile(
192                                        hisRes.getPublishTag(),
193                                        hisRes.getStructureId(),
194                                        hisRes.getResourceId(),
195                                        currRes.getRootPath(),
196                                        hisRes.getTypeId(),
197                                        hisRes.getFlags(),
198                                        hisRes.getProjectLastModified(),
199                                        hisRes.getState(),
200                                        hisRes.getDateCreated(),
201                                        hisRes.getUserCreated(),
202                                        hisRes.getDateLastModified(),
203                                        hisRes.getUserLastModified(),
204                                        hisRes.getDateReleased(),
205                                        hisRes.getDateExpired(),
206                                        hisRes.getLength(),
207                                        hisRes.getDateContent(),
208                                        hisRes.getVersion(),
209                                        hisRes.getParentId(),
210                                        null,
211                                        hisRes.getResourceVersion(),
212                                        hisRes.getStructureVersion());
213                                } else {
214                                    resource = hisRes;
215                                }
216                                if (resource instanceof CmsHistoryFile) {
217                                    try {
218                                        resource = cms.readFile(resource);
219                                    } catch (Exception e) {
220                                        LOG.info(e.getLocalizedMessage(), e);
221                                    }
222                                }
223                            }
224                            if (res != null) {
225                                // store a request attribute to indicate that this is in fact a historical version
226                                req.setAttribute(ATTRIBUTE_NAME, resource);
227                            }
228                        } catch (CmsException e) {
229                            if (LOG.isErrorEnabled()) {
230                                LOG.error(Messages.get().getBundle().key(Messages.ERR_HISTORYRESOURCE_2, uri, version));
231                            }
232                            throw new CmsResourceInitException(
233                                Messages.get().container(Messages.ERR_SHOWVERSION_2, uri, version),
234                                e);
235                        } finally {
236                            // restore the siteroot and modify the uri to the one of the correct resource
237                            cms.getRequestContext().setSiteRoot(storedSiteRoot);
238                            if (resource != null) {
239                                // resource may be null in case of an error
240                                cms.getRequestContext().setUri(cms.getSitePath(resource));
241                            }
242                        }
243                    }
244                }
245            }
246        }
247        return resource;
248    }
249}