001/*
002 * (C) Copyright 2006-2008 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 *     bstefanescu
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.core.rest;
023
024import java.io.Serializable;
025import java.util.HashMap;
026import java.util.Map;
027
028import javax.ws.rs.DELETE;
029import javax.ws.rs.GET;
030import javax.ws.rs.POST;
031import javax.ws.rs.Path;
032import javax.ws.rs.core.Context;
033import javax.ws.rs.core.EntityTag;
034import javax.ws.rs.core.Request;
035import javax.ws.rs.core.Response;
036import javax.ws.rs.core.Response.ResponseBuilder;
037
038import org.nuxeo.ecm.core.api.Blob;
039import org.nuxeo.ecm.core.api.CoreSession;
040import org.nuxeo.ecm.core.api.DocumentModel;
041import org.nuxeo.ecm.core.api.NuxeoException;
042import org.nuxeo.ecm.core.api.PropertyException;
043import org.nuxeo.ecm.core.api.model.Property;
044import org.nuxeo.ecm.core.versioning.VersioningService;
045import org.nuxeo.ecm.platform.web.common.ServletHelper;
046import org.nuxeo.ecm.webengine.WebException;
047import org.nuxeo.ecm.webengine.forms.FormData;
048import org.nuxeo.ecm.webengine.model.WebAdapter;
049import org.nuxeo.ecm.webengine.model.exceptions.IllegalParameterException;
050import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException;
051import org.nuxeo.ecm.webengine.model.impl.DefaultAdapter;
052
053/**
054 * File Service - manages attachments to a document.
055 * <p>
056 * Accepts the following methods:
057 * <ul>
058 * <li>GET - get the attached file
059 * <li>POST - create an attachment
060 * </ul>
061 *
062 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
063 */
064@WebAdapter(name = "file", type = "FileService", targetType = "Document")
065public class FileService extends DefaultAdapter {
066
067    @GET
068    public Response doGet(@Context Request request) {
069        DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
070        FormData form = ctx.getForm();
071        String xpath = form.getString(FormData.PROPERTY);
072        if (xpath == null) {
073            if (doc.hasSchema("file")) {
074                xpath = "file:content";
075            } else {
076                throw new IllegalParameterException(
077                        "Missing request parameter named 'property' that specify the blob property xpath to fetch");
078            }
079        }
080        try {
081            Property p = doc.getProperty(xpath);
082            Blob blob = (Blob) p.getValue();
083            if (blob == null) {
084                throw new WebResourceNotFoundException("No attached file at " + xpath);
085            }
086
087            String fileName = blob.getFilename();
088            if (fileName == null) {
089                p = p.getParent();
090                if (p.isComplex()) { // special handling for file and files
091                    // schema
092                    try {
093                        fileName = (String) p.getValue("filename");
094                    } catch (PropertyException e) {
095                        fileName = "Unknown";
096                    }
097                }
098            }
099
100            String digest = blob.getDigest();
101            EntityTag etag = digest == null ? null : new EntityTag(digest);
102
103            if (etag != null) {
104                ResponseBuilder builder = request.evaluatePreconditions(etag);
105                if (builder != null) {
106                    return builder.build();
107                }
108            }
109
110            // TODO probably not needed as DownloadService already does it
111            String contentDisposition = ServletHelper.getRFC2231ContentDisposition(ctx.getRequest(), fileName);
112
113            // cached resource did change or no ETag -> serve updated content
114            ResponseBuilder builder = Response.ok(blob).header("Content-Disposition", contentDisposition).type(
115                    blob.getMimeType());
116            if (etag != null) {
117                builder.tag(etag);
118            }
119            return builder.build();
120
121        } catch (NuxeoException e) {
122            throw WebException.wrap("Failed to get the attached file", e);
123        }
124    }
125
126    @POST
127    public Response doPost() {
128        DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
129        FormData form = ctx.getForm();
130        form.fillDocument(doc);
131        String xpath = ctx.getForm().getString(FormData.PROPERTY);
132        if (xpath == null) {
133            if (doc.hasSchema("file")) {
134                xpath = "file:content";
135            } else if (doc.hasSchema("files")) {
136                xpath = "files:files";
137            } else {
138                throw new IllegalArgumentException("Missing request parameter named 'property' that specifies "
139                        + "the blob property xpath to fetch");
140            }
141        }
142        Blob blob = form.getFirstBlob();
143        if (blob == null) {
144            throw new IllegalArgumentException("Could not find any uploaded file");
145        }
146        try {
147            Property p = doc.getProperty(xpath);
148            if (p.isList()) { // add the file to the list
149                if ("files".equals(p.getSchema().getName())) { // treat the
150                    // files schema
151                    // separately
152                    Map<String, Serializable> map = new HashMap<String, Serializable>();
153                    map.put("filename", blob.getFilename());
154                    map.put("file", (Serializable) blob);
155                    p.addValue(map);
156                } else {
157                    p.addValue(blob);
158                }
159            } else {
160                if ("file".equals(p.getSchema().getName())) { // for
161                    // compatibility
162                    // with deprecated
163                    // filename
164                    p.getParent().get("filename").setValue(blob.getFilename());
165                }
166                p.setValue(blob);
167            }
168            // make snapshot
169            doc.putContextData(VersioningService.VERSIONING_OPTION, form.getVersioningOption());
170            CoreSession session = ctx.getCoreSession();
171            session.saveDocument(doc);
172            session.save();
173            return redirect(getTarget().getPath());
174        } catch (NuxeoException e) {
175            throw WebException.wrap("Failed to attach file", e);
176        }
177    }
178
179    @GET
180    @Path("delete")
181    public Response remove() {
182        return doDelete();
183    }
184
185    @DELETE
186    public Response doDelete() {
187        DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
188        FormData form = ctx.getForm();
189        String xpath = form.getString(FormData.PROPERTY);
190        if (xpath == null) {
191            if (doc.hasSchema("file")) {
192                xpath = "file:content";
193            } else {
194                throw new IllegalArgumentException("Missing request parameter named 'property' that specifies "
195                        + "the blob property xpath to fetch");
196            }
197        }
198        try {
199            doc.getProperty(xpath).remove();
200            CoreSession session = ctx.getCoreSession();
201            session.saveDocument(doc);
202            session.save();
203        } catch (NuxeoException e) {
204            throw WebException.wrap("Failed to delete attached file", e);
205        }
206        return redirect(getTarget().getPath());
207    }
208
209}