001/* 002 * (C) Copyright 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 * Thomas Roger 018 * 019 */ 020 021package org.nuxeo.ecm.platform.preview.restlet; 022 023import java.io.IOException; 024import java.io.Serializable; 025import java.io.UnsupportedEncodingException; 026import java.net.URI; 027import java.net.URLDecoder; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.List; 031import java.util.Map; 032 033import javax.servlet.http.HttpServletRequest; 034import javax.servlet.http.HttpServletResponse; 035 036import org.apache.commons.lang3.StringUtils; 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.nuxeo.ecm.core.api.Blob; 040import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 041import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder; 042import org.nuxeo.ecm.core.blob.BlobManager; 043import org.nuxeo.ecm.core.blob.BlobManager.UsageHint; 044import org.nuxeo.ecm.core.io.download.DownloadService; 045import org.nuxeo.ecm.platform.preview.adapter.base.ConverterBasedHtmlPreviewAdapter; 046import org.nuxeo.ecm.platform.preview.api.HtmlPreviewAdapter; 047import org.nuxeo.ecm.platform.preview.api.NothingToPreviewException; 048import org.nuxeo.ecm.platform.preview.api.PreviewException; 049import org.nuxeo.ecm.platform.ui.web.restAPI.BaseStatelessNuxeoRestlet; 050import org.nuxeo.runtime.api.Framework; 051import org.restlet.Request; 052import org.restlet.Response; 053import org.restlet.data.MediaType; 054import org.restlet.data.Status; 055 056/** 057 * Provides a REST API to retrieve the preview of a document. 058 * 059 * @author tiry 060 * @since 10.3 061 * @deprecated since 10.3 062 */ 063public class PreviewRestlet extends BaseStatelessNuxeoRestlet { 064 065 private static final Log log = LogFactory.getLog(PreviewRestlet.class); 066 067 public static final String PREVIEWURL_DEFAULTXPATH = "default"; 068 069 // cache duration in seconds 070 // protected static int MAX_CACHE_LIFE = 60 * 10; 071 072 // protected static final Map<String, PreviewCacheEntry> cachedAdapters = 073 // new ConcurrentHashMap<String, PreviewCacheEntry>(); 074 075 protected static final List<String> previewInProcessing = Collections.synchronizedList(new ArrayList<String>()); 076 077 @Override 078 public void doHandleStatelessRequest(Request req, Response res) { 079 logDeprecation(); 080 HttpServletRequest request = getHttpRequest(req); 081 HttpServletResponse response = getHttpResponse(res); 082 083 String repo = (String) req.getAttributes().get("repo"); 084 String docid = (String) req.getAttributes().get("docid"); 085 String xpath = (String) req.getAttributes().get("fieldPath"); 086 xpath = xpath.replace("-", "/"); 087 List<String> segments = req.getResourceRef().getSegments(); 088 StringBuilder sb = new StringBuilder(); 089 int pos = segments.indexOf("restAPI") + 5; 090 for (int i = pos; i < segments.size(); i++) { 091 sb.append(segments.get(i)); 092 sb.append("/"); 093 } 094 String subPath = sb.substring(0, sb.length() - 1); 095 096 try { 097 xpath = URLDecoder.decode(xpath, "UTF-8"); 098 subPath = URLDecoder.decode(subPath, "UTF-8"); 099 } catch (UnsupportedEncodingException e) { 100 log.error(e); 101 } 102 103 String blobPostProcessingParameter = getQueryParamValue(req, "blobPostProcessing", "false"); 104 boolean blobPostProcessing = Boolean.parseBoolean(blobPostProcessingParameter); 105 106 if (repo == null || repo.equals("*")) { 107 handleError(res, "you must specify a repository"); 108 return; 109 } 110 if (docid == null || repo.equals("*")) { 111 handleError(res, "you must specify a documentId"); 112 return; 113 } 114 115 boolean initOk = initRepositoryAndTargetDocument(res, repo, docid); 116 if (!initOk) { 117 return; 118 } 119 120 // if it's a managed blob try to use the embed uri for preview 121 Blob blobToPreview = getBlobToPreview(xpath); 122 BlobManager blobManager = Framework.getService(BlobManager.class); 123 try { 124 URI uri = blobManager.getURI(blobToPreview, UsageHint.EMBED, null); 125 if (uri != null) { 126 res.redirectSeeOther(uri.toString()); 127 return; 128 } 129 } catch (IOException e) { 130 handleError(res, e); 131 return; 132 } 133 134 List<Blob> previewBlobs = initCachedBlob(res, xpath, blobPostProcessing); 135 if (previewBlobs == null || previewBlobs.isEmpty()) { 136 // response was already handled by initCachedBlob 137 return; 138 } 139 140 // find blob 141 Blob blob = null; 142 if (StringUtils.isEmpty(subPath)) { 143 blob = previewBlobs.get(0); 144 blob.setMimeType("text/html"); 145 } else { 146 for (Blob b : previewBlobs) { 147 if (subPath.equals(b.getFilename())) { 148 blob = b; 149 break; 150 } 151 } 152 } 153 if (blob == null) { 154 res.setStatus(Status.CLIENT_ERROR_NOT_FOUND); 155 return; 156 } 157 158 response.setHeader("Cache-Control", "no-cache"); 159 response.setHeader("Pragma", "no-cache"); 160 161 String reason = "preview"; 162 final Blob fblob = blob; 163 if (xpath == null || "default".equals(xpath)) { 164 xpath = DownloadService.BLOBHOLDER_0; 165 } 166 Boolean inline = Boolean.TRUE; 167 Map<String, Serializable> extendedInfos = Collections.singletonMap("subPath", subPath); 168 DownloadService downloadService = Framework.getService(DownloadService.class); 169 try { 170 downloadService.downloadBlob(request, response, targetDocument, xpath, blob, blob.getFilename(), reason, 171 extendedInfos, inline, byteRange -> setEntityToBlobOutput(fblob, byteRange, res)); 172 } catch (IOException e) { 173 handleError(res, e); 174 } 175 } 176 177 /** 178 * @since 7.3 179 */ 180 private Blob getBlobToPreview(String xpath) { 181 BlobHolder bh; 182 if ((xpath == null) || ("default".equals(xpath))) { 183 bh = targetDocument.getAdapter(BlobHolder.class); 184 } else { 185 bh = new DocumentBlobHolder(targetDocument, xpath); 186 } 187 return bh.getBlob(); 188 } 189 190 private List<Blob> initCachedBlob(Response res, String xpath, boolean blobPostProcessing) { 191 192 HtmlPreviewAdapter preview = null; // getFromCache(targetDocument, 193 // xpath); 194 195 targetDocument.putContextData(ConverterBasedHtmlPreviewAdapter.OLD_PREVIEW_PROPERTY, true); 196 // if (preview == null) { 197 preview = targetDocument.getAdapter(HtmlPreviewAdapter.class); 198 // } 199 200 if (preview == null) { 201 handleNoPreview(res, xpath, null); 202 return null; 203 } 204 205 List<Blob> previewBlobs = null; 206 try { 207 if (xpath.equals(PREVIEWURL_DEFAULTXPATH)) { 208 previewBlobs = preview.getFilePreviewBlobs(blobPostProcessing); 209 } else { 210 previewBlobs = preview.getFilePreviewBlobs(xpath, blobPostProcessing); 211 } 212 /* 213 * if (preview.cachable()) { updateCache(targetDocument, preview, xpath); } 214 */ 215 } catch (PreviewException e) { 216 previewInProcessing.remove(targetDocument.getId()); 217 handleNoPreview(res, xpath, e); 218 return null; 219 } 220 221 if (previewBlobs == null || previewBlobs.size() == 0) { 222 handleNoPreview(res, xpath, null); 223 return null; 224 } 225 return previewBlobs; 226 } 227 228 protected void handleNoPreview(Response res, String xpath, Exception e) { 229 StringBuilder sb = new StringBuilder(); 230 231 sb.append("<html><body><center><h1>"); 232 if (e == null) { 233 sb.append("No preview is available for this document." + "</h1>"); 234 } else { 235 sb.append("Preview cannot be generated for this document." + "</h1>"); 236 sb.append("<pre>Technical issue:</pre>"); 237 sb.append("<pre>Blob path: "); 238 sb.append(xpath); 239 sb.append("</pre>"); 240 sb.append("<pre>"); 241 sb.append(e.toString()); 242 sb.append("</pre>"); 243 } 244 245 sb.append("</center></body></html>"); 246 if (e instanceof NothingToPreviewException) { 247 // Not an error, don't log 248 } else { 249 log.error("Could not build preview for missing blob at " + xpath, e); 250 } 251 252 res.setEntity(sb.toString(), MediaType.TEXT_HTML); 253 HttpServletResponse response = getHttpResponse(res); 254 255 response.setHeader("Content-Disposition", "inline"); 256 } 257 258}