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