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.ImagePreviewer; 038import org.nuxeo.ecm.platform.preview.adapter.MarkdownPreviewer; 039import org.nuxeo.ecm.platform.preview.adapter.MimeTypePreviewer; 040import org.nuxeo.ecm.platform.preview.adapter.OfficePreviewer; 041import org.nuxeo.ecm.platform.preview.adapter.PdfPreviewer; 042import org.nuxeo.ecm.platform.preview.adapter.PlainImagePreviewer; 043import org.nuxeo.ecm.platform.preview.adapter.PreviewAdapterManager; 044import org.nuxeo.ecm.platform.preview.api.NothingToPreviewException; 045import org.nuxeo.ecm.platform.preview.api.PreviewException; 046import org.nuxeo.runtime.api.Framework; 047import org.nuxeo.runtime.services.config.ConfigurationService; 048 049/** 050 * Base class for preview based on "on the fly" HTML transformers 051 * 052 * @author tiry 053 */ 054public class ConverterBasedHtmlPreviewAdapter extends AbstractHtmlPreviewAdapter { 055 056 private static final Logger log = LogManager.getLogger(ConverterBasedHtmlPreviewAdapter.class); 057 058 /** 059 * @since 10.3 060 * @deprecated since 10.3 061 */ 062 public static final String OLD_PREVIEW_PROPERTY = "nuxeo.old.jsf.preview"; 063 064 /** 065 * @since 10.3 066 * @deprecated since 10.3 067 */ 068 public static final String TEXT_ANNOTATIONS_PROPERTY = "nuxeo.text.annotations"; 069 070 protected String defaultFieldXPath; 071 072 protected MimetypeRegistry mimeTypeService; 073 074 /** 075 * @since 8.10 076 */ 077 protected static final String ALLOW_ZIP_PREVIEW = "nuxeo.preview.zip.enabled"; 078 079 public ConversionService getConversionService() { 080 return Framework.getService(ConversionService.class); 081 } 082 083 @Override 084 protected PreviewAdapterManager getPreviewManager() { 085 return Framework.getService(PreviewAdapterManager.class); 086 } 087 088 protected static String getMimeType(Blob blob) { 089 if (blob == null) { 090 return null; 091 } 092 093 String srcMT = blob.getMimeType(); 094 if (srcMT == null || srcMT.startsWith("application/octet-stream")) { 095 // call MT Service 096 try { 097 MimetypeRegistry mtr = Framework.getService(MimetypeRegistry.class); 098 srcMT = mtr.getMimetypeFromFilenameAndBlobWithDefault(blob.getFilename(), blob, 099 "application/octet-stream"); 100 log.debug("mime type service returned " + srcMT); 101 } catch (MimetypeDetectionException e) { 102 log.warn("error while calling Mimetype service", e); 103 } 104 } 105 return srcMT; 106 } 107 108 protected String getMimeType(String xpath) { 109 BlobHolder blobHolder2preview = getBlobHolder2preview(xpath); 110 Blob blob = getBlob2preview(blobHolder2preview); 111 return getMimeType(blob); 112 } 113 114 protected String getDefaultPreviewFieldXPath() { 115 return defaultFieldXPath; 116 } 117 118 public void setDefaultPreviewFieldXPath(String xPath) { 119 defaultFieldXPath = xPath; 120 } 121 122 @Override 123 public List<Blob> getPreviewBlobs() throws PreviewException { 124 return getPreviewBlobs(getDefaultPreviewFieldXPath()); 125 } 126 127 @Override 128 public boolean hasPreview(String xpath) { 129 String srcMT; 130 try { 131 srcMT = getMimeType(xpath); 132 } catch (NothingToPreviewException e) { 133 return false; 134 } 135 if ("application/zip".equals(srcMT) 136 && !Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(ALLOW_ZIP_PREVIEW)) { 137 return false; 138 } 139 MimeTypePreviewer mtPreviewer = getPreviewManager().getPreviewer(srcMT); 140 return mtPreviewer != null || getConversionService().getConverterName(srcMT, "text/html") != null; 141 } 142 143 @Override 144 public List<Blob> getPreviewBlobs(String xpath) throws PreviewException { 145 BlobHolder blobHolder2preview = getBlobHolder2preview(xpath); 146 Blob blob2Preview; 147 try { 148 blob2Preview = getBlob2preview(blobHolder2preview); 149 } catch (NothingToPreviewException e) { 150 return Collections.emptyList(); 151 } 152 153 String srcMT = getMimeType(xpath); 154 log.debug("Source type for HTML preview =" + srcMT); 155 MimeTypePreviewer mtPreviewer = getPreviewManager().getPreviewer(srcMT); 156 if (mtPreviewer != null) { 157 List<Blob> result = getPreviewFromMimeTypePreviewer(mtPreviewer, blob2Preview); 158 if (result != null) { 159 return result; 160 } 161 } 162 163 String converterName = getConversionService().getConverterName(srcMT, "text/html"); 164 if (converterName == null) { 165 log.debug("No dedicated converter found, using generic"); 166 converterName = "any2html"; 167 } 168 169 BlobHolder result; 170 try { 171 result = getConversionService().convert(converterName, blobHolder2preview, null); 172 setMimeType(result); 173 setDigest(result); 174 return result.getBlobs(); 175 } catch (ConversionException e) { 176 throw new PreviewException(e.getMessage(), e); 177 } 178 } 179 180 /** 181 * Backward compatibility method to trigger the right previewers if 'nuxeo.old.jsf.preview' is set. 182 * <p> 183 * This allows old HTML preview to be used, to make annotations available. 184 * <p> 185 * To be removed with JSF UI. 186 * 187 * @since 10.3 188 * @deprecated since 10.3 189 */ 190 protected List<Blob> getPreviewFromMimeTypePreviewer(MimeTypePreviewer mtPreviewer, Blob blob2Preview) { 191 // this context data comes from the PreviewRestlet 192 boolean oldPreview = Boolean.TRUE.equals(adaptedDoc.getContextData(OLD_PREVIEW_PROPERTY)); 193 if (!oldPreview) { 194 return mtPreviewer.getPreview(blob2Preview, adaptedDoc); 195 } 196 197 // when old preview is enabled 198 // - replace ImagePreviewer with PlainImagePreviewer 199 // - do nothing for "office" previewers if the text annotations are enabled to trigger the old preview behavior, 200 // otherwise keep the current preview behavior 201 if (mtPreviewer instanceof ImagePreviewer) { 202 return new PlainImagePreviewer().getPreview(blob2Preview, adaptedDoc); 203 } 204 205 ConfigurationService cs = Framework.getService(ConfigurationService.class); 206 if (cs.isBooleanPropertyTrue(TEXT_ANNOTATIONS_PROPERTY) && (mtPreviewer instanceof PdfPreviewer 207 || mtPreviewer instanceof MarkdownPreviewer || mtPreviewer instanceof OfficePreviewer)) { 208 return null; 209 } 210 return mtPreviewer.getPreview(blob2Preview, adaptedDoc); 211 } 212 213 /** 214 * @since 5.7.3 215 */ 216 private Blob getBlob2preview(BlobHolder blobHolder2preview) throws PreviewException { 217 Blob blob2Preview; 218 try { 219 blob2Preview = blobHolder2preview.getBlob(); 220 } catch (PropertyNotFoundException e) { 221 blob2Preview = null; 222 } 223 if (blob2Preview == null) { 224 throw new NothingToPreviewException("Can not preview a document without blob"); 225 } else { 226 return blob2Preview; 227 } 228 } 229 230 /** 231 * Returns a blob holder suitable for a preview. 232 * 233 * @since 5.7.3 234 */ 235 private BlobHolder getBlobHolder2preview(String xpath) { 236 if ((xpath == null) || ("default".equals(xpath))) { 237 return adaptedDoc.getAdapter(BlobHolder.class); 238 } else { 239 return new DocumentBlobHolder(adaptedDoc, xpath); 240 } 241 } 242 243 protected void setMimeType(BlobHolder result) { 244 for (Blob blob : result.getBlobs()) { 245 if ((blob.getMimeType() == null || blob.getMimeType().startsWith("application/octet-stream")) 246 && blob.getFilename().endsWith("html")) { 247 String mimeTpye = getMimeType(blob); 248 blob.setMimeType(mimeTpye); 249 } 250 } 251 } 252 253 protected void setDigest(BlobHolder result) { 254 for (Blob blob : result.getBlobs()) { 255 if (blob.getDigest() == null) { 256 try (InputStream stream = blob.getStream()) { 257 String digest = DigestUtils.md5Hex(stream); 258 blob.setDigest(digest); 259 } catch (IOException e) { 260 log.warn("Unable to compute digest of blob.", e); 261 } 262 } 263 } 264 } 265 266 public String getMimeType(File file) throws ConversionException { 267 try { 268 return getMimeTypeService().getMimetypeFromFile(file); 269 } catch (ConversionException e) { 270 throw new ConversionException("Could not get MimeTypeRegistry"); 271 } catch (MimetypeNotFoundException | MimetypeDetectionException e) { 272 return "application/octet-stream"; 273 } 274 } 275 276 public MimetypeRegistry getMimeTypeService() throws ConversionException { 277 if (mimeTypeService == null) { 278 mimeTypeService = Framework.getService(MimetypeRegistry.class); 279 } 280 return mimeTypeService; 281 } 282 283 @Override 284 public void cleanup() { 285 286 } 287 288 @Override 289 public boolean cachable() { 290 return true; 291 } 292 293 @Override 294 public boolean hasBlobToPreview() throws PreviewException { 295 String xpath = getDefaultPreviewFieldXPath(); 296 Blob blob2Preview; 297 try { 298 blob2Preview = getBlob2preview(getBlobHolder2preview(xpath)); 299 } catch (NothingToPreviewException e) { 300 return false; 301 } 302 String srcMT = getMimeType(xpath); 303 if ("application/zip".equals(srcMT) 304 && !Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(ALLOW_ZIP_PREVIEW)) { 305 return false; 306 } 307 return blob2Preview != null; 308 } 309 310}