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