001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Antoine Taillefer
018 */
019
020package org.nuxeo.ecm.diff.content;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.lang.StringUtils;
029import org.nuxeo.common.utils.URIUtils;
030import org.nuxeo.ecm.core.api.Blob;
031import org.nuxeo.ecm.core.api.DocumentLocation;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.NuxeoException;
034import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
035import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder;
036import org.nuxeo.ecm.core.api.blobholder.DocumentStringBlobHolder;
037import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
038import org.nuxeo.ecm.diff.content.adapter.ContentDiffAdapterManager;
039import org.nuxeo.ecm.diff.content.adapter.MimeTypeContentDiffer;
040import org.nuxeo.ecm.platform.ui.web.rest.RestHelper;
041import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService;
042import org.nuxeo.ecm.platform.url.DocumentViewImpl;
043import org.nuxeo.ecm.platform.url.api.DocumentView;
044import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
045import org.nuxeo.runtime.api.Framework;
046
047/**
048 * Helper for content diff.
049 */
050public final class ContentDiffHelper {
051
052    public static final String CONTENT_DIFF_FANCYBOX_VIEW = "content_diff_fancybox";
053
054    public static final String LABEL_URL_PARAM_NAME = "label";
055
056    public static final String XPATH_URL_PARAM_NAME = "xPath";
057
058    public static final String CONVERSION_TYPE_URL_PARAM_NAME = "conversionType";
059
060    public static final String LOCALE_URL_PARAM_NAME = "locale";
061
062    public static final String CONTENT_DIFF_URL_PREFIX = "restAPI/contentDiff/";
063
064    public static final String DEFAULT_XPATH = "default";
065
066    /**
067     * Final class constructor.
068     */
069    private ContentDiffHelper() {
070    }
071
072    /**
073     * Gets the content diff fancy box URL.
074     *
075     * @param currentDoc the current doc
076     * @param propertyLabel the property label
077     * @param propertyXPath the property xpath
078     * @param conversionType the conversion type
079     * @return the content diff fancy box URL
080     */
081    public static String getContentDiffFancyBoxURL(DocumentModel currentDoc, String propertyLabel,
082            String propertyXPath, String conversionType) {
083        DocumentLocation docLocation = new DocumentLocationImpl(currentDoc.getRepositoryName(), currentDoc.getRef());
084        DocumentView docView = new DocumentViewImpl(docLocation, CONTENT_DIFF_FANCYBOX_VIEW);
085        docView.setPatternName("id");
086        URLPolicyService urlPolicyService = Framework.getLocalService(URLPolicyService.class);
087        String docUrl = urlPolicyService.getUrlFromDocumentView(docView, VirtualHostHelper.getContextPathProperty());
088        if (docUrl == null) {
089            throw new NuxeoException(
090                    "Cannot get URL from document view, probably because of a missing urlPattern contribution.");
091        }
092        Map<String, String> requestParams = new LinkedHashMap<>();
093        requestParams.put(LABEL_URL_PARAM_NAME, propertyLabel);
094        requestParams.put(XPATH_URL_PARAM_NAME, propertyXPath);
095        if (!StringUtils.isEmpty(conversionType)) {
096            requestParams.put(CONVERSION_TYPE_URL_PARAM_NAME, conversionType);
097        }
098        docUrl = URIUtils.addParametersToURIQuery(docUrl, requestParams);
099        return RestHelper.addCurrentConversationParameters(docUrl);
100    }
101
102    /**
103     * Gets the content diff URL.
104     *
105     * @param leftDoc the left doc
106     * @param rightDoc the right doc
107     * @param conversionType the conversion type
108     * @param locale the locale
109     * @return the content diff URL
110     */
111    public static String getContentDiffURL(DocumentModel leftDoc, DocumentModel rightDoc, String conversionType,
112            String locale) {
113
114        return getContentDiffURL(leftDoc.getRepositoryName(), leftDoc, rightDoc, DEFAULT_XPATH, conversionType, locale);
115    }
116
117    /**
118     * Gets the content diff URL.
119     *
120     * @param leftDoc the left doc
121     * @param rightDoc the right doc
122     * @param propertyXPath the property xpath
123     * @param conversionType the conversion type
124     * @param locale the locale
125     * @return the content diff URL
126     */
127    public static String getContentDiffURL(DocumentModel leftDoc, DocumentModel rightDoc, String propertyXPath,
128            String conversionType, String locale) {
129
130        return getContentDiffURL(leftDoc.getRepositoryName(), leftDoc, rightDoc, propertyXPath, conversionType, locale);
131    }
132
133    /**
134     * Gets the content diff URL.
135     *
136     * @param repositoryName the repository name
137     * @param leftDoc the left doc
138     * @param rightDoc the right doc
139     * @param propertyXPath the xpath
140     * @param conversionType the conversion type
141     * @param locale the locale
142     * @return the content diff URL
143     */
144    public static String getContentDiffURL(String repositoryName, DocumentModel leftDoc, DocumentModel rightDoc,
145            String propertyXPath, String conversionType, String locale) {
146
147        if (propertyXPath == null) {
148            propertyXPath = DEFAULT_XPATH;
149        }
150
151        StringBuilder sb = new StringBuilder();
152
153        sb.append(CONTENT_DIFF_URL_PREFIX);
154        sb.append(repositoryName);
155        sb.append("/");
156        sb.append(leftDoc.getId());
157        sb.append("/");
158        sb.append(rightDoc.getId());
159        sb.append("/");
160        sb.append(propertyXPath);
161        sb.append("/");
162        boolean isQueryParam = false;
163        if (!StringUtils.isEmpty(conversionType)) {
164            sb.append("?");
165            sb.append(CONVERSION_TYPE_URL_PARAM_NAME);
166            sb.append("=");
167            sb.append(conversionType);
168            isQueryParam = true;
169        }
170        if (!StringUtils.isEmpty(locale)) {
171            sb.append(isQueryParam ? "&" : "?");
172            sb.append(LOCALE_URL_PARAM_NAME);
173            sb.append("=");
174            sb.append(locale);
175            isQueryParam = true;
176        }
177
178        return sb.toString();
179    }
180
181    /**
182     * Checks if the HTML conversion content diff is relevant for the specified property.
183     */
184    public static boolean isDisplayHtmlConversion(Serializable property) {
185
186        // Always relevant except for the blacklisted mime types
187        if (isContentProperty(property)) {
188            Blob blob = (Blob) property;
189            String mimeType = blob.getMimeType();
190            if (getHtmlConversionBlackListedMimeTypes().contains(mimeType)) {
191                return false;
192            }
193        }
194        return true;
195    }
196
197    /**
198     * Checks if the text conversion content diff is relevant for the specified property.
199     */
200    public static boolean isDisplayTextConversion(Serializable property) {
201
202        // Must be a content property
203        if (!isContentProperty(property)) {
204            return false;
205        }
206        // Not relevant for the mime types associated to a content differ (see
207        // the mimeTypeContentDiffer extension point)
208        Blob blob = (Blob) property;
209        String mimeType = blob.getMimeType();
210
211        ContentDiffAdapterManager contentDiffAdapterManager = Framework.getLocalService(ContentDiffAdapterManager.class);
212        MimeTypeContentDiffer mimeTypeContentDiffer = contentDiffAdapterManager.getContentDiffer(mimeType);
213
214        if (mimeTypeContentDiffer != null) {
215            return false;
216        }
217        return true;
218    }
219
220    /**
221     * Checks if the specified property is a content property, ie. {@code instanceof Blob}.
222     */
223    public static boolean isContentProperty(Serializable property) {
224        return property instanceof Blob;
225    }
226
227    /**
228     * Gets the list of blacklisted mime types for HTML conversion.
229     * <p>
230     * For now:
231     * <ul>
232     * <li>PDF</li>
233     * <li>Office spreadsheet mime types</li>
234     * <li>Office presentation mime types</li>
235     * </ul>
236     * </p>
237     *
238     * @see https://jira.nuxeo.com/browse/NXP-9421
239     * @see https://jira.nuxeo.com/browse/NXP-9431
240     */
241    protected static List<String> getHtmlConversionBlackListedMimeTypes() {
242
243        List<String> blackListedMimeTypes = new ArrayList<String>();
244
245        // PDF
246        blackListedMimeTypes.add("application/pdf");
247
248        // Office spreadsheet
249        blackListedMimeTypes.add("application/vnd.ms-excel");
250        blackListedMimeTypes.add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
251        blackListedMimeTypes.add("application/vnd.sun.xml.calc");
252        blackListedMimeTypes.add("application/vnd.sun.xml.calc.template");
253        blackListedMimeTypes.add("application/vnd.oasis.opendocument.spreadsheet");
254        blackListedMimeTypes.add("application/vnd.oasis.opendocument.spreadsheet-template");
255
256        // Office presentation
257        blackListedMimeTypes.add("application/vnd.ms-powerpoint");
258        blackListedMimeTypes.add("application/vnd.openxmlformats-officedocument.presentationml.presentation");
259        blackListedMimeTypes.add("application/vnd.sun.xml.impress");
260        blackListedMimeTypes.add("application/vnd.sun.xml.impress.template");
261        blackListedMimeTypes.add("application/vnd.oasis.opendocument.presentation");
262        blackListedMimeTypes.add("application/vnd.oasis.opendocument.presentation-template");
263
264        return blackListedMimeTypes;
265    }
266
267    public static BlobHolder getBlobHolder(DocumentModel doc, String xPath) throws ContentDiffException {
268        // TODO: manage other property types than Blob / String?
269        Serializable prop = doc.getPropertyValue(xPath);
270        if (prop instanceof Blob) {
271            return new DocumentBlobHolder(doc, xPath);
272        }
273        if (prop instanceof String) {
274            // Default mime type is text/plain. For a Note, use the
275            // "note:mime_type" property, otherwise if the property value is
276            // HTML use text/html.
277            String mimeType = "text/plain";
278            if ("note:note".equals(xPath)) {
279                mimeType = (String) doc.getPropertyValue("note:mime_type");
280            } else {
281                if (HtmlGuesser.isHtml((String) prop)) {
282                    mimeType = "text/html";
283                }
284            }
285            return new DocumentStringBlobHolder(doc, xPath, mimeType);
286        }
287        throw new ContentDiffException(String.format("Cannot get BlobHolder for doc '%s' and xpath '%s'.",
288                doc.getTitle(), xPath));
289    }
290}