001/*
002 * (C) Copyright 2013 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 *     dmetzler
018 */
019package org.nuxeo.ecm.restapi.server.jaxrs.blob;
020
021import static org.nuxeo.ecm.core.io.download.DownloadService.BLOBHOLDER_PREFIX;
022
023import java.io.Serializable;
024
025import javax.servlet.http.HttpServletRequest;
026import javax.ws.rs.DELETE;
027import javax.ws.rs.GET;
028import javax.ws.rs.PUT;
029import javax.ws.rs.core.Context;
030import javax.ws.rs.core.EntityTag;
031import javax.ws.rs.core.Request;
032import javax.ws.rs.core.Response;
033
034import org.apache.commons.lang.StringUtils;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.CoreSession;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.PropertyException;
039import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
040import org.nuxeo.ecm.core.versioning.VersioningService;
041import org.nuxeo.ecm.platform.web.common.ServletHelper;
042import org.nuxeo.ecm.webengine.WebException;
043import org.nuxeo.ecm.webengine.forms.FormData;
044import org.nuxeo.ecm.webengine.model.WebObject;
045import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException;
046import org.nuxeo.ecm.webengine.model.impl.DefaultObject;
047
048/**
049 * @since 5.8
050 */
051@WebObject(type = "blob")
052public class BlobObject extends DefaultObject {
053
054    private String fieldPath;
055
056    private DocumentModel doc;
057
058    private BlobHolder bh;
059
060    @Override
061    protected void initialize(Object... args) {
062        super.initialize(args);
063        if (args.length == 2) {
064            fieldPath = (String) args[0];
065            doc = (DocumentModel) args[1];
066
067            if (fieldPath == null) {
068                if (bh == null && doc.hasSchema("file")) {
069                    fieldPath = "file:content";
070                } else {
071                    throw new IllegalArgumentException("No xpath specified and document does not have 'file' schema");
072                }
073            } else {
074                if (fieldPath.startsWith(BLOBHOLDER_PREFIX)) {
075                    bh = doc.getAdapter(BlobHolder.class);
076                    if (bh != null) {
077                        fieldPath = fieldPath.replace(BLOBHOLDER_PREFIX, "");
078                    } else {
079                        throw new WebResourceNotFoundException("No BlobHolder found");
080                    }
081                }
082            }
083        }
084    }
085
086    @Override
087    public <A> A getAdapter(Class<A> adapter) {
088        if (adapter.isAssignableFrom(Blob.class)) {
089            return adapter.cast(getBlob());
090        }
091        return super.getAdapter(adapter);
092    }
093
094    protected Blob getBlob() {
095        if (bh != null) {
096            if (StringUtils.isBlank(fieldPath) || fieldPath.equals("0")) {
097                return bh.getBlob();
098            } else {
099                int index = Integer.parseInt(fieldPath);
100                return bh.getBlobs().get(index);
101            }
102        }
103        return (Blob) doc.getPropertyValue(fieldPath);
104    }
105
106    public BlobHolder getBlobHolder() {
107        return bh;
108    }
109
110    /**
111     * @since 8.2
112     */
113    public DocumentModel getDocument() {
114        return doc;
115    }
116
117    /**
118     * @since 8.2
119     */
120    public String getXpath() {
121        return fieldPath;
122    }
123
124    @GET
125    public Object doGet(@Context Request request) {
126        Blob blob = getBlob();
127        if (blob == null) {
128            throw new WebResourceNotFoundException("No attached file at " + fieldPath);
129        }
130        return blob;
131    }
132
133    /**
134     * @deprecated since 7.3. Now returns directly the Blob and use default {@code BlobWriter}.
135     */
136    @Deprecated
137    public static Response buildResponseFromBlob(Request request, HttpServletRequest httpServletRequest, Blob blob,
138            String filename) {
139        if (filename == null) {
140            filename = blob.getFilename();
141        }
142
143        String digest = blob.getDigest();
144        EntityTag etag = digest == null ? null : new EntityTag(digest);
145        if (etag != null) {
146            Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
147            if (builder != null) {
148                return builder.build();
149            }
150        }
151        String contentDisposition = ServletHelper.getRFC2231ContentDisposition(httpServletRequest, filename);
152        // cached resource did change or no ETag -> serve updated content
153        Response.ResponseBuilder builder = Response.ok(blob).header("Content-Disposition", contentDisposition).type(
154                blob.getMimeType());
155        if (etag != null) {
156            builder.tag(etag);
157        }
158        return builder.build();
159    }
160
161    @DELETE
162    public Response doDelete() {
163        if (bh != null) {
164            throw new IllegalArgumentException("Cannot modify a Blob using a BlobHolder");
165        }
166        try {
167            doc.getProperty(fieldPath).remove();
168        } catch (PropertyException e) {
169            throw WebException.wrap("Failed to delete attached file into property: " + fieldPath, e);
170        }
171        CoreSession session = ctx.getCoreSession();
172        session.saveDocument(doc);
173            session.save();
174        return Response.noContent().build();
175    }
176
177    @PUT
178    public Response doPut() {
179        FormData form = ctx.getForm();
180        Blob blob = form.getFirstBlob();
181        if (blob == null) {
182            throw new IllegalArgumentException("Could not find any uploaded file");
183        }
184        if (bh != null) {
185            throw new IllegalArgumentException("Cannot modify a Blob using a BlobHolder");
186        }
187        try {
188            doc.setPropertyValue(fieldPath, (Serializable) blob);
189        } catch (PropertyException e) {
190            throw WebException.wrap("Failed to attach file", e);
191        }
192        // make snapshot
193        doc.putContextData(VersioningService.VERSIONING_OPTION, form.getVersioningOption());
194        CoreSession session = ctx.getCoreSession();
195        session.saveDocument(doc);
196        session.save();
197        return Response.ok("blob updated").build();
198    }
199
200}