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.List; 023 024import org.apache.commons.codec.digest.DigestUtils; 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 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.convert.api.ConversionException; 032import org.nuxeo.ecm.core.convert.api.ConversionService; 033import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException; 034import org.nuxeo.ecm.platform.mimetype.MimetypeNotFoundException; 035import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 036import org.nuxeo.ecm.platform.preview.adapter.MimeTypePreviewer; 037import org.nuxeo.ecm.platform.preview.adapter.PreviewAdapterManager; 038import org.nuxeo.ecm.platform.preview.api.NothingToPreviewException; 039import org.nuxeo.ecm.platform.preview.api.PreviewException; 040import org.nuxeo.runtime.api.Framework; 041import org.nuxeo.runtime.services.config.ConfigurationService; 042 043/** 044 * Base class for preview based on "on the fly" HTML transformers 045 * 046 * @author tiry 047 */ 048public class ConverterBasedHtmlPreviewAdapter extends AbstractHtmlPreviewAdapter { 049 050 private static final Log log = LogFactory.getLog(ConverterBasedHtmlPreviewAdapter.class); 051 052 protected String defaultFieldXPath; 053 054 protected MimetypeRegistry mimeTypeService; 055 056 /** 057 * @since 8.10 058 */ 059 protected static final String ALLOW_ZIP_PREVIEW = "nuxeo.preview.zip.enabled"; 060 061 public ConversionService getConversionService() { 062 return Framework.getService(ConversionService.class); 063 } 064 065 @Override 066 protected PreviewAdapterManager getPreviewManager() { 067 return Framework.getService(PreviewAdapterManager.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 = getMimeType(xpath); 112 if ("application/zip".equals(srcMT) 113 && !Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(ALLOW_ZIP_PREVIEW)) { 114 return false; 115 } 116 MimeTypePreviewer mtPreviewer = getPreviewManager().getPreviewer(srcMT); 117 return mtPreviewer != null || getConversionService().getConverterName(srcMT, "text/html") != null; 118 } 119 120 @Override 121 public List<Blob> getPreviewBlobs(String xpath) throws PreviewException { 122 BlobHolder blobHolder2preview = getBlobHolder2preview(xpath); 123 Blob blob2Preview = getBlob2preview(blobHolder2preview); 124 125 String srcMT = getMimeType(xpath); 126 log.debug("Source type for HTML preview =" + srcMT); 127 MimeTypePreviewer mtPreviewer = getPreviewManager().getPreviewer(srcMT); 128 if (mtPreviewer != null) { 129 return mtPreviewer.getPreview(blob2Preview, adaptedDoc); 130 } 131 132 String converterName = getConversionService().getConverterName(srcMT, "text/html"); 133 if (converterName == null) { 134 log.debug("No dedicated converter found, using generic"); 135 converterName = "any2html"; 136 } 137 138 BlobHolder result; 139 try { 140 result = getConversionService().convert(converterName, blobHolder2preview, null); 141 setMimeType(result); 142 setDigest(result); 143 return result.getBlobs(); 144 } catch (ConversionException e) { 145 throw new PreviewException(e.getMessage(), e); 146 } 147 } 148 149 /** 150 * @since 5.7.3 151 */ 152 private Blob getBlob2preview(BlobHolder blobHolder2preview) throws PreviewException { 153 Blob blob2Preview = blobHolder2preview.getBlob(); 154 if (blob2Preview == null) { 155 throw new NothingToPreviewException("Can not preview a document without blob"); 156 } else { 157 return blob2Preview; 158 } 159 } 160 161 /** 162 * Returns a blob holder suitable for a preview. 163 * 164 * @since 5.7.3 165 */ 166 private BlobHolder getBlobHolder2preview(String xpath) { 167 if ((xpath == null) || ("default".equals(xpath))) { 168 return adaptedDoc.getAdapter(BlobHolder.class); 169 } else { 170 return new DocumentBlobHolder(adaptedDoc, xpath); 171 } 172 } 173 174 protected void setMimeType(BlobHolder result) { 175 for (Blob blob : result.getBlobs()) { 176 if ((blob.getMimeType() == null || blob.getMimeType().startsWith("application/octet-stream")) 177 && blob.getFilename().endsWith("html")) { 178 String mimeTpye = getMimeType(blob); 179 blob.setMimeType(mimeTpye); 180 } 181 } 182 } 183 184 protected void setDigest(BlobHolder result) { 185 for (Blob blob : result.getBlobs()) { 186 if (blob.getDigest() == null) { 187 try (InputStream stream = blob.getStream()) { 188 String digest = DigestUtils.md5Hex(stream); 189 blob.setDigest(digest); 190 } catch (IOException e) { 191 log.warn("Unable to compute digest of blob.", e); 192 } 193 } 194 } 195 } 196 197 public String getMimeType(File file) throws ConversionException { 198 try { 199 return getMimeTypeService().getMimetypeFromFile(file); 200 } catch (ConversionException e) { 201 throw new ConversionException("Could not get MimeTypeRegistry"); 202 } catch (MimetypeNotFoundException e) { 203 return "application/octet-stream"; 204 } catch (MimetypeDetectionException e) { 205 return "application/octet-stream"; 206 } 207 } 208 209 public MimetypeRegistry getMimeTypeService() throws ConversionException { 210 if (mimeTypeService == null) { 211 mimeTypeService = Framework.getService(MimetypeRegistry.class); 212 } 213 return mimeTypeService; 214 } 215 216 @Override 217 public void cleanup() { 218 219 } 220 221 @Override 222 public boolean cachable() { 223 return true; 224 } 225 226 @Override 227 public boolean hasBlobToPreview() throws PreviewException { 228 String xpath = getDefaultPreviewFieldXPath(); 229 Blob blob2Preview; 230 try { 231 blob2Preview = getBlob2preview(getBlobHolder2preview(xpath)); 232 } catch (NothingToPreviewException e) { 233 return false; 234 } 235 String srcMT = getMimeType(xpath); 236 if ("application/zip".equals(srcMT) 237 && !Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(ALLOW_ZIP_PREVIEW)) { 238 return false; 239 } 240 return blob2Preview != null; 241 } 242 243}