001/*
002 * (C) Copyright 2006-2016 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 */
017package org.nuxeo.ecm.platform.preview.adapter.base;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.Collections;
023import java.util.List;
024
025import org.apache.commons.codec.digest.DigestUtils;
026import org.apache.logging.log4j.LogManager;
027import org.apache.logging.log4j.Logger;
028import org.nuxeo.ecm.core.api.Blob;
029import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
030import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder;
031import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
032import org.nuxeo.ecm.core.convert.api.ConversionException;
033import org.nuxeo.ecm.core.convert.api.ConversionService;
034import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException;
035import org.nuxeo.ecm.platform.mimetype.MimetypeNotFoundException;
036import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
037import org.nuxeo.ecm.platform.preview.adapter.MimeTypePreviewer;
038import org.nuxeo.ecm.platform.preview.adapter.PreviewAdapterManager;
039import org.nuxeo.ecm.platform.preview.api.NothingToPreviewException;
040import org.nuxeo.ecm.platform.preview.api.PreviewException;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.runtime.services.config.ConfigurationService;
043
044/**
045 * Base class for preview based on "on the fly" HTML transformers
046 *
047 * @author tiry
048 */
049public class ConverterBasedHtmlPreviewAdapter extends AbstractHtmlPreviewAdapter {
050
051    private static final Logger log = LogManager.getLogger(ConverterBasedHtmlPreviewAdapter.class);
052
053    protected String defaultFieldXPath;
054
055    /**
056     * @deprecated since 11.1. Use {@link Framework#getService(Class)} with {@link MimetypeRegistry} instead.
057     */
058    @Deprecated
059    protected MimetypeRegistry mimeTypeService;
060
061    /**
062     * @since 8.10
063     */
064    protected static final String ALLOW_ZIP_PREVIEW = "nuxeo.preview.zip.enabled";
065
066    public ConversionService getConversionService() {
067        return Framework.getService(ConversionService.class);
068    }
069
070    protected static String getMimeType(Blob blob) {
071        if (blob == null) {
072            return null;
073        }
074
075        String srcMT = blob.getMimeType();
076        if (srcMT == null || srcMT.startsWith("application/octet-stream")) {
077            // call MT Service
078            try {
079                MimetypeRegistry mtr = Framework.getService(MimetypeRegistry.class);
080                srcMT = mtr.getMimetypeFromFilenameAndBlobWithDefault(blob.getFilename(), blob,
081                        "application/octet-stream");
082                log.debug("mime type service returned " + srcMT);
083            } catch (MimetypeDetectionException e) {
084                log.warn("error while calling Mimetype service", e);
085            }
086        }
087        return srcMT;
088    }
089
090    protected String getMimeType(String xpath) {
091        BlobHolder blobHolder2preview = getBlobHolder2preview(xpath);
092        Blob blob = getBlob2preview(blobHolder2preview);
093        return getMimeType(blob);
094    }
095
096    protected String getDefaultPreviewFieldXPath() {
097        return defaultFieldXPath;
098    }
099
100    public void setDefaultPreviewFieldXPath(String xPath) {
101        defaultFieldXPath = xPath;
102    }
103
104    @Override
105    public List<Blob> getPreviewBlobs() throws PreviewException {
106        return getPreviewBlobs(getDefaultPreviewFieldXPath());
107    }
108
109    @Override
110    public boolean hasPreview(String xpath) {
111        String srcMT;
112        try {
113            srcMT = getMimeType(xpath);
114        } catch (NothingToPreviewException e) {
115            return false;
116        }
117        if ("application/zip".equals(srcMT)
118                && Framework.getService(ConfigurationService.class).isBooleanFalse(ALLOW_ZIP_PREVIEW)) {
119            return false;
120        }
121        MimeTypePreviewer mtPreviewer = getPreviewManager().getPreviewer(srcMT);
122        return mtPreviewer != null || getConversionService().getConverterName(srcMT, "text/html") != null;
123    }
124
125    @Override
126    public List<Blob> getPreviewBlobs(String xpath) throws PreviewException {
127        BlobHolder blobHolder2preview = getBlobHolder2preview(xpath);
128        Blob blob2Preview;
129        try {
130            blob2Preview = getBlob2preview(blobHolder2preview);
131        } catch (NothingToPreviewException e) {
132            return Collections.emptyList();
133        }
134
135        String srcMT = getMimeType(xpath);
136        log.debug("Source type for HTML preview =" + srcMT);
137        MimeTypePreviewer mtPreviewer = getPreviewManager().getPreviewer(srcMT);
138        if (mtPreviewer != null) {
139            List<Blob> result = mtPreviewer.getPreview(blob2Preview, adaptedDoc);
140            if (result != null) {
141                return result;
142            }
143        }
144
145        String converterName = getConversionService().getConverterName(srcMT, "text/html");
146        if (converterName == null) {
147            log.debug("No dedicated converter found, using generic");
148            converterName = "any2html";
149        }
150
151        BlobHolder result;
152        try {
153            result = getConversionService().convert(converterName, blobHolder2preview, null);
154            setMimeType(result);
155            setDigest(result);
156            return result.getBlobs();
157        } catch (ConversionException e) {
158            throw new PreviewException(e.getMessage(), e);
159        }
160    }
161
162    /**
163     * @since 5.7.3
164     */
165    private Blob getBlob2preview(BlobHolder blobHolder2preview) throws PreviewException {
166        Blob blob2Preview;
167        try {
168            blob2Preview = blobHolder2preview.getBlob();
169        } catch (PropertyNotFoundException e) {
170            blob2Preview = null;
171        }
172        if (blob2Preview == null) {
173            throw new NothingToPreviewException("Can not preview a document without blob");
174        } else {
175            return blob2Preview;
176        }
177    }
178
179    /**
180     * Returns a blob holder suitable for a preview.
181     *
182     * @since 5.7.3
183     */
184    private BlobHolder getBlobHolder2preview(String xpath) {
185        if ((xpath == null) || ("default".equals(xpath))) {
186            return adaptedDoc.getAdapter(BlobHolder.class);
187        } else {
188            return new DocumentBlobHolder(adaptedDoc, xpath);
189        }
190    }
191
192    protected void setMimeType(BlobHolder result) {
193        for (Blob blob : result.getBlobs()) {
194            if ((blob.getMimeType() == null || blob.getMimeType().startsWith("application/octet-stream"))
195                    && blob.getFilename().endsWith("html")) {
196                String mimeTpye = getMimeType(blob);
197                blob.setMimeType(mimeTpye);
198            }
199        }
200    }
201
202    protected void setDigest(BlobHolder result) {
203        for (Blob blob : result.getBlobs()) {
204            if (blob.getDigest() == null) {
205                try (InputStream stream = blob.getStream()) {
206                    String digest = DigestUtils.md5Hex(stream);
207                    blob.setDigest(digest);
208                } catch (IOException e) {
209                    log.warn("Unable to compute digest of blob.", e);
210                }
211            }
212        }
213    }
214
215    public String getMimeType(File file) throws ConversionException {
216        try {
217            return Framework.getService(MimetypeRegistry.class).getMimetypeFromFile(file);
218        } catch (ConversionException e) {
219            throw new ConversionException("Could not get MimeTypeRegistry");
220        } catch (MimetypeNotFoundException | MimetypeDetectionException e) {
221            return "application/octet-stream";
222        }
223    }
224
225    /**
226     * @deprecated since 11.1. Use {@link Framework#getService(Class)} with {@link MimetypeRegistry} instead.
227     */
228    @Deprecated
229    public MimetypeRegistry getMimeTypeService() throws ConversionException {
230        if (mimeTypeService == null) {
231            mimeTypeService = Framework.getService(MimetypeRegistry.class);
232        }
233        return mimeTypeService;
234    }
235
236    @Override
237    public void cleanup() {
238
239    }
240
241    @Override
242    public boolean cachable() {
243        return true;
244    }
245
246    @Override
247    public boolean hasBlobToPreview() throws PreviewException {
248        String xpath = getDefaultPreviewFieldXPath();
249        Blob blob2Preview;
250        try {
251            blob2Preview = getBlob2preview(getBlobHolder2preview(xpath));
252        } catch (NothingToPreviewException e) {
253            return false;
254        }
255        String srcMT = getMimeType(xpath);
256        if ("application/zip".equals(srcMT)
257                && Framework.getService(ConfigurationService.class).isBooleanFalse(ALLOW_ZIP_PREVIEW)) {
258            return false;
259        }
260        return blob2Preview != null;
261    }
262
263}