001/*
002 * (C) Copyright 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 * Contributors:
017 *
018 *      Nelson Silva <nsilva@nuxeo.com>
019 */
020package org.nuxeo.ecm.restapi.server.jaxrs.adapters;
021
022import static javax.ws.rs.core.Response.Status.NOT_FOUND;
023
024import java.io.IOException;
025import java.net.URI;
026import java.util.Collections;
027import java.util.List;
028import java.util.Optional;
029
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpServletResponse;
032import javax.ws.rs.GET;
033import javax.ws.rs.Path;
034import javax.ws.rs.PathParam;
035import javax.ws.rs.Produces;
036import javax.ws.rs.QueryParam;
037import javax.ws.rs.core.Context;
038import javax.ws.rs.core.MediaType;
039import javax.ws.rs.core.Response;
040
041import org.nuxeo.ecm.core.api.Blob;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.NuxeoException;
044import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
045import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder;
046import org.nuxeo.ecm.core.blob.BlobManager;
047import org.nuxeo.ecm.core.io.download.DownloadService;
048import org.nuxeo.ecm.core.io.download.DownloadService.DownloadContext;
049import org.nuxeo.ecm.platform.preview.api.HtmlPreviewAdapter;
050import org.nuxeo.ecm.platform.preview.api.PreviewException;
051import org.nuxeo.ecm.platform.preview.helper.PreviewHelper;
052import org.nuxeo.ecm.restapi.server.jaxrs.blob.BlobObject;
053import org.nuxeo.ecm.webengine.model.Resource;
054import org.nuxeo.ecm.webengine.model.WebAdapter;
055import org.nuxeo.ecm.webengine.model.impl.DefaultAdapter;
056import org.nuxeo.runtime.api.Framework;
057
058/**
059 * @since 8.2
060 */
061@WebAdapter(name = PreviewAdapter.NAME, type = "previewAdapter")
062@Produces({ MediaType.APPLICATION_JSON })
063public class PreviewAdapter extends DefaultAdapter {
064
065    public static final String NAME = "preview";
066
067    @GET
068    public Object preview(@QueryParam("blobPostProcessing") boolean postProcessing, @Context HttpServletRequest request,
069            @Context HttpServletResponse response) {
070
071        DocumentBlobHolder bh = getBlobHolderToPreview();
072        if (bh == null) {
073            return Response.status(NOT_FOUND).build();
074        }
075
076        // if it's a managed blob try to use the embed uri for preview
077        BlobManager blobManager = Framework.getService(BlobManager.class);
078        try {
079            URI uri = blobManager.getURI(bh.getBlob(), BlobManager.UsageHint.EMBED, null);
080            if (uri != null) {
081                return Response.seeOther(uri).build();
082            }
083        } catch (IOException e) {
084            throw new NuxeoException(e);
085        }
086
087        List<Blob> previewBlobs = getPreviewBlobs(bh, postProcessing);
088        if (previewBlobs == null || previewBlobs.isEmpty()) {
089            return buildPreviewNotAvailable();
090        }
091
092        try {
093            Blob blob = previewBlobs.get(0);
094            DownloadContext context = DownloadContext.builder(request, response)
095                                                     .doc(bh.getDocument())
096                                                     .xpath(bh.getXpath())
097                                                     .blob(blob)
098                                                     .reason("preview")
099                                                     .inline(true)
100                                                     .build();
101            Framework.getService(DownloadService.class).downloadBlob(context);
102        } catch (IOException e) {
103            throw new NuxeoException(e);
104        }
105
106        return Response.ok().build();
107    }
108
109    protected Object buildPreviewNotAvailable() {
110        return Response.status(NOT_FOUND)
111                       .entity(getTemplate("preview/preview_not_available.ftl"))
112                       .type("text/html")
113                       .build();
114    }
115
116    @GET
117    @Path("{subPath}")
118    public Object subPath(@PathParam("subPath") String subPath,
119            @QueryParam("blobPostProcessing") boolean postProcessing, @Context HttpServletRequest request,
120            @Context HttpServletResponse response) {
121
122        DocumentBlobHolder bh = getBlobHolderToPreview();
123        if (bh == null) {
124            return Response.status(NOT_FOUND).build();
125        }
126
127        List<Blob> previewBlobs = getPreviewBlobs(bh, postProcessing);
128        if (previewBlobs == null || previewBlobs.isEmpty()) {
129            return buildPreviewNotAvailable();
130        }
131
132        // find blob
133        Optional<Blob> subBlob = previewBlobs.stream().filter(b -> subPath.equals(b.getFilename())).findFirst();
134
135        if (!subBlob.isPresent()) {
136            return buildPreviewNotAvailable();
137        }
138
139        try {
140            Blob blob = subBlob.get();
141            DownloadContext context = DownloadContext.builder(request, response)
142                                                     .doc(bh.getDocument())
143                                                     .xpath(bh.getXpath())
144                                                     .blob(blob)
145                                                     .reason("preview")
146                                                     .extendedInfos(Collections.singletonMap("subPath", subPath))
147                                                     .inline(true)
148                                                     .build();
149            Framework.getService(DownloadService.class).downloadBlob(context);
150        } catch (IOException e) {
151            throw new NuxeoException(e);
152        }
153
154        return Response.ok().build();
155    }
156
157    private List<Blob> getPreviewBlobs(DocumentBlobHolder bh, boolean blobPostProcessing) {
158        DocumentModel doc = bh.getDocument();
159        String xpath = bh.getXpath();
160        HtmlPreviewAdapter preview;
161
162        try {
163            if (isBlobTarget() && !isBlobHolder(doc, xpath)) {
164                preview = PreviewHelper.getBlobPreviewAdapter(doc);
165                return preview.getFilePreviewBlobs(xpath, blobPostProcessing);
166            }
167
168            preview = doc.getAdapter(HtmlPreviewAdapter.class);
169            if (preview == null) {
170                return List.of();
171            }
172
173            return preview.getFilePreviewBlobs(blobPostProcessing);
174        } catch (PreviewException e) {
175            return List.of();
176        }
177    }
178
179    private DocumentBlobHolder getBlobHolderToPreview() {
180        Resource target = getTarget();
181        if (isBlobTarget()) {
182            return ((BlobObject) target).getBlobHolder();
183        } else {
184            DocumentModel doc = target.getAdapter(DocumentModel.class);
185            return (DocumentBlobHolder) doc.getAdapter(BlobHolder.class);
186        }
187    }
188
189    private boolean isBlobTarget() {
190        return getTarget().isInstanceOf("blob");
191    }
192
193    private boolean isBlobHolder(DocumentModel doc, String xpath) {
194        DocumentBlobHolder bh = (DocumentBlobHolder) doc.getAdapter(BlobHolder.class);
195        return bh != null && bh.getXpath().equals(xpath);
196    }
197}