001/*
002 * (C) Copyright 2006-2018 Nuxeo (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.lang3.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, String propertyXPath,
082            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.getService(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, i.e. if its mime type is not
183     * blacklisted.
184     * <p>
185     * For now, the list of blacklisted mime types for HTML conversion are:
186     * <ul>
187     * <li>PDF</li>
188     * <li>Office spreadsheet mime types</li>
189     * <li>Office presentation mime types</li>
190     * </ul>
191     * This is configurable with the {@code htmlConversionBlacklistedMimeTypes} extension point, see
192     * <a href="https://jira.nuxeo.com/browse/NXP-25208">NXP-25208</a>.
193     *
194     * @see <a href="https://jira.nuxeo.com/browse/NXP-9421">NXP-9421</a>
195     * @see <a href="https://jira.nuxeo.com/browse/NXP-9431">NXP-9431</a>
196     */
197    public static boolean isDisplayHtmlConversion(Serializable property) {
198
199        // Always relevant except for the blacklisted mime types
200        if (isContentProperty(property)) {
201            Blob blob = (Blob) property;
202            String mimeType = blob.getMimeType();
203            if (Framework.getService(ContentDiffAdapterManager.class)
204                         .getHtmlConversionBlacklistedMimeTypes()
205                         .contains(mimeType)) {
206                return false;
207            }
208        }
209        return true;
210    }
211
212    /**
213     * Checks if the text conversion content diff is relevant for the specified property.
214     */
215    public static boolean isDisplayTextConversion(Serializable property) {
216
217        // Must be a content property
218        if (!isContentProperty(property)) {
219            return false;
220        }
221        // Not relevant for the mime types associated to a content differ (see
222        // the mimeTypeContentDiffer extension point)
223        Blob blob = (Blob) property;
224        String mimeType = blob.getMimeType();
225
226        ContentDiffAdapterManager contentDiffAdapterManager = Framework.getService(ContentDiffAdapterManager.class);
227        MimeTypeContentDiffer mimeTypeContentDiffer = contentDiffAdapterManager.getContentDiffer(mimeType);
228
229        if (mimeTypeContentDiffer != null) {
230            return false;
231        }
232        return true;
233    }
234
235    /**
236     * Checks if the specified property is a content property, ie. {@code instanceof Blob}.
237     */
238    public static boolean isContentProperty(Serializable property) {
239        return property instanceof Blob;
240    }
241
242    /**
243     * Gets the list of blacklisted mime types for HTML conversion.
244     * <p>
245     * For now:
246     * <ul>
247     * <li>PDF</li>
248     * <li>Office spreadsheet mime types</li>
249     * <li>Office presentation mime types</li>
250     * </ul>
251     * </p>
252     *
253     * @see <a href="https://jira.nuxeo.com/browse/NXP-9421">NXP-9421</a>
254     * @see <a href="https://jira.nuxeo.com/browse/NXP-9431">NXP-9431</a>
255     * @deprecated since 10.10
256     */
257    @Deprecated
258    protected static List<String> getHtmlConversionBlackListedMimeTypes() {
259
260        List<String> blackListedMimeTypes = new ArrayList<>();
261
262        // PDF
263        blackListedMimeTypes.add("application/pdf");
264
265        // Office spreadsheet
266        blackListedMimeTypes.add("application/vnd.ms-excel");
267        blackListedMimeTypes.add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
268        blackListedMimeTypes.add("application/vnd.sun.xml.calc");
269        blackListedMimeTypes.add("application/vnd.sun.xml.calc.template");
270        blackListedMimeTypes.add("application/vnd.oasis.opendocument.spreadsheet");
271        blackListedMimeTypes.add("application/vnd.oasis.opendocument.spreadsheet-template");
272
273        // Office presentation
274        blackListedMimeTypes.add("application/vnd.ms-powerpoint");
275        blackListedMimeTypes.add("application/vnd.openxmlformats-officedocument.presentationml.presentation");
276        blackListedMimeTypes.add("application/vnd.sun.xml.impress");
277        blackListedMimeTypes.add("application/vnd.sun.xml.impress.template");
278        blackListedMimeTypes.add("application/vnd.oasis.opendocument.presentation");
279        blackListedMimeTypes.add("application/vnd.oasis.opendocument.presentation-template");
280
281        return blackListedMimeTypes;
282    }
283
284    public static BlobHolder getBlobHolder(DocumentModel doc, String xPath) throws ContentDiffException {
285        // TODO: manage other property types than Blob / String?
286        Serializable prop = doc.getPropertyValue(xPath);
287        if (prop instanceof Blob) {
288            return new DocumentBlobHolder(doc, xPath);
289        }
290        if (prop instanceof String) {
291            // Default mime type is text/plain. For a Note, use the
292            // "note:mime_type" property, otherwise if the property value is
293            // HTML use text/html.
294            String mimeType = "text/plain";
295            if ("note:note".equals(xPath)) {
296                mimeType = (String) doc.getPropertyValue("note:mime_type");
297            } else {
298                if (HtmlGuesser.isHtml((String) prop)) {
299                    mimeType = "text/html";
300                }
301            }
302            return new DocumentStringBlobHolder(doc, xPath, mimeType);
303        }
304        throw new ContentDiffException(
305                String.format("Cannot get BlobHolder for doc '%s' and xpath '%s'.", doc.getTitle(), xPath));
306    }
307}