001/*
002 * (C) Copyright 2012 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 * Contributors:
014 * Nuxeo - initial API and implementation
015 */
016
017package org.nuxeo.ecm.platform.rendition.url;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.apache.commons.lang.StringUtils;
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.common.utils.URIUtils;
030import org.nuxeo.ecm.core.api.DocumentLocation;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.core.api.DocumentRef;
033import org.nuxeo.ecm.core.api.IdRef;
034import org.nuxeo.ecm.core.api.PathRef;
035import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
036import org.nuxeo.ecm.platform.url.DocumentViewImpl;
037import org.nuxeo.ecm.platform.url.api.DocumentView;
038import org.nuxeo.ecm.platform.url.service.AbstractDocumentViewCodec;
039
040/**
041 * Base class for Rendition url codec.
042 * <p>
043 * This class is shared with Template rendering system.
044 * <p>
045 * Codec handling a document repository, id, view and additional request parameters. View is used to represent the
046 * Rendition name.
047 * <p>
048 * This codec supports both path abd id based urls.
049 *
050 * @since 5.6
051 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
052 */
053public class RenditionBasedCodec extends AbstractDocumentViewCodec {
054
055    protected static final Log log = LogFactory.getLog(DocumentRenditionCodec.class);
056
057    public static final int URL_MAX_LENGTH = 2000;
058
059    /**
060     * @since 6.0
061     */
062    public static final String RENDITION_PARAM_NAME = "rendition";
063
064    /**
065     * @since 6.0
066     */
067    public static final String RENDITION_VIEW_ID = "rendition";
068
069    public static final String PATH_URL_PATTERN = "/" // slash
070            + "([\\w\\.]+)" // server name (group 1)
071            + "(?:/(.*))?" // path (group 2) (optional)
072            + "@([\\w\\-\\.\\%]+)" // renditionName (group 3)
073            + "/?" // final slash (optional)
074            + "(?:\\?(.*)?)?";
075
076    public static final String ID_URL_PATTERN = "/(\\w+)/([a-zA-Z_0-9\\-]+)(/([\\w\\-\\.\\%]+))?(/)?(\\?(.*)?)?";
077
078    public static String getRenditionUrl(DocumentModel doc, String renditionName) {
079        DocumentView docView = new DocumentViewImpl(doc);
080        docView.setViewId(renditionName);
081        return new DocumentRenditionCodec().getUrlFromDocumentView(docView);
082    }
083
084    @Override
085    public DocumentView getDocumentViewFromUrl(String url) {
086        final Pattern pathPattern = Pattern.compile(getPrefix() + PATH_URL_PATTERN);
087        Matcher pathMatcher = pathPattern.matcher(url);
088        if (pathMatcher.matches()) {
089
090            final String server = pathMatcher.group(1);
091            String path = pathMatcher.group(2);
092            if (path != null) {
093                // add leading slash to make it absolute if it's not the root
094                path = "/" + URIUtils.unquoteURIPathComponent(path);
095            } else {
096                path = "/";
097            }
098            final DocumentRef docRef = new PathRef(path);
099
100            final String renditionName = URIUtils.unquoteURIPathComponent(pathMatcher.group(3));
101
102            // get other parameters
103            String query = pathMatcher.group(4);
104            Map<String, String> params = URIUtils.getRequestParameters(query);
105            if (params == null) {
106                params = new HashMap<String, String>();
107            }
108            params.put(RENDITION_PARAM_NAME, renditionName);
109            final DocumentLocation docLoc = new DocumentLocationImpl(server, docRef);
110            return new DocumentViewImpl(docLoc, RENDITION_VIEW_ID, params);
111        } else {
112            final Pattern idPattern = Pattern.compile(getPrefix() + ID_URL_PATTERN);
113            Matcher idMatcher = idPattern.matcher(url);
114            if (idMatcher.matches()) {
115                if (idMatcher.groupCount() >= 4) {
116
117                    final String server = idMatcher.group(1);
118                    String uuid = idMatcher.group(2);
119                    final DocumentRef docRef = new IdRef(uuid);
120                    final String renditionName = URIUtils.unquoteURIPathComponent(idMatcher.group(4));
121
122                    // get other parameters
123
124                    Map<String, String> params = null;
125                    if (idMatcher.groupCount() > 6) {
126                        String query = idMatcher.group(7);
127                        params = URIUtils.getRequestParameters(query);
128                    }
129                    if (params == null) {
130                        params = new HashMap<String, String>();
131                    }
132                    params.put(RENDITION_PARAM_NAME, renditionName);
133
134                    final DocumentLocation docLoc = new DocumentLocationImpl(server, docRef);
135                    return new DocumentViewImpl(docLoc, RENDITION_VIEW_ID, params);
136                }
137            }
138        }
139        return null;
140    }
141
142    protected String getUrlFromDocumentViewWithId(DocumentView docView) {
143        DocumentLocation docLoc = docView.getDocumentLocation();
144        if (docLoc != null) {
145            List<String> items = new ArrayList<String>();
146            items.add(getPrefix());
147            items.add(docLoc.getServerName());
148            IdRef docRef = docLoc.getIdRef();
149            if (docRef == null) {
150                return null;
151            }
152            items.add(docRef.toString());
153            String renditionName = docView.getParameter(RENDITION_PARAM_NAME);
154            if (StringUtils.isBlank(renditionName)) {
155                // fall-back on view id
156                renditionName = docView.getViewId();
157            }
158            if (renditionName != null) {
159                items.add(URIUtils.quoteURIPathComponent(renditionName, true));
160            }
161            String uri = StringUtils.join(items, "/");
162            Map<String, String> params = new HashMap<>();
163            Map<String, String> dcparams = docView.getParameters();
164            if (dcparams != null) {
165                params.putAll(dcparams);
166            }
167            if (params != null && params.containsKey(RENDITION_PARAM_NAME)) {
168                params.remove(RENDITION_PARAM_NAME);
169            }
170            return URIUtils.addParametersToURIQuery(uri, params);
171        }
172        return null;
173    }
174
175    @Override
176    public String getUrlFromDocumentView(DocumentView docView) {
177
178        // Use DocumentIdCodec if the document is a version
179        if ("true".equals(docView.getParameter("version"))) {
180            if (docView.getDocumentLocation().getIdRef() != null) {
181                return getUrlFromDocumentViewWithId(docView);
182            }
183        }
184
185        DocumentLocation docLoc = docView.getDocumentLocation();
186        if (docLoc != null) {
187            List<String> items = new ArrayList<String>();
188            items.add(getPrefix());
189            items.add(docLoc.getServerName());
190            PathRef docRef = docLoc.getPathRef();
191            if (docRef == null) {
192                return null;
193            }
194            // this is a path, get rid of leading slash
195            String path = docRef.toString();
196            if (path.startsWith("/")) {
197                path = path.substring(1);
198            }
199            if (path.length() > 0) {
200                items.add(URIUtils.quoteURIPathComponent(path, false));
201            }
202            String uri = StringUtils.join(items, "/");
203            String renditionName = docView.getParameter(RENDITION_PARAM_NAME);
204            if (StringUtils.isBlank(renditionName)) {
205                // fall-back on view id
206                renditionName = docView.getViewId();
207            }
208
209            if (renditionName != null) {
210                uri += "@" + URIUtils.quoteURIPathComponent(renditionName, true);
211            }
212
213            Map<String, String> params = new HashMap<>();
214            Map<String, String> dcparams = docView.getParameters();
215            if (dcparams != null) {
216                params.putAll(dcparams);
217            }
218            if (dcparams != null && dcparams.containsKey(RENDITION_PARAM_NAME)) {
219                params.remove(RENDITION_PARAM_NAME);
220            }
221            String uriWithParam = URIUtils.addParametersToURIQuery(uri, params);
222
223            // If the URL with the Path codec is to long, it use the URL with
224            // the Id Codec.
225            if (uriWithParam.length() > URL_MAX_LENGTH) {
226
227                // If the DocumentLocation did not contains the document Id, it
228                // use the Path Codec even if the Url is too long for IE.
229                if (null == docView.getDocumentLocation().getIdRef()) {
230                    log.error("The DocumentLocation did not contains the RefId.");
231                    return uriWithParam;
232                }
233
234                return getUrlFromDocumentViewWithId(docView);
235
236            } else {
237                return uriWithParam;
238            }
239        }
240        return null;
241    }
242}