001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.ecm.automation.jaxrs.io.operations;
013
014import java.io.File;
015import java.io.IOException;
016import java.io.InputStream;
017import java.lang.annotation.Annotation;
018import java.lang.reflect.Type;
019import java.util.List;
020
021import javax.mail.BodyPart;
022import javax.mail.MessagingException;
023import javax.mail.internet.MimeMultipart;
024import javax.servlet.http.HttpServletRequest;
025import javax.ws.rs.Consumes;
026import javax.ws.rs.WebApplicationException;
027import javax.ws.rs.core.Context;
028import javax.ws.rs.core.MediaType;
029import javax.ws.rs.core.MultivaluedMap;
030import javax.ws.rs.ext.MessageBodyReader;
031import javax.ws.rs.ext.Provider;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.codehaus.jackson.JsonFactory;
036import org.codehaus.jackson.JsonParser;
037import org.nuxeo.common.utils.FileUtils;
038import org.nuxeo.ecm.automation.core.util.BlobList;
039import org.nuxeo.ecm.automation.jaxrs.io.InputStreamDataSource;
040import org.nuxeo.ecm.automation.jaxrs.io.SharedFileInputStream;
041import org.nuxeo.ecm.core.api.Blob;
042import org.nuxeo.ecm.core.api.Blobs;
043import org.nuxeo.ecm.core.api.CoreSession;
044import org.nuxeo.ecm.webengine.WebException;
045import org.nuxeo.ecm.webengine.jaxrs.context.RequestCleanupHandler;
046import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext;
047import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory;
048
049/**
050 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
051 */
052@Provider
053@Consumes("multipart/form-data")
054public class MultiPartFormRequestReader implements MessageBodyReader<ExecutionRequest> {
055
056    private static final Log log = LogFactory.getLog(MultiPartFormRequestReader.class);
057
058    @Context
059    protected HttpServletRequest request;
060
061    @Context
062    JsonFactory factory;
063
064    public CoreSession getCoreSession() {
065        return SessionFactory.getSession(request);
066    }
067
068    @Override
069    public boolean isReadable(Class<?> arg0, Type arg1, Annotation[] arg2, MediaType arg3) {
070        return ExecutionRequest.class.isAssignableFrom(arg0); // TODO check
071        // media type too
072    }
073
074    @Override
075    public ExecutionRequest readFrom(Class<ExecutionRequest> arg0, Type arg1, Annotation[] arg2, MediaType arg3,
076            MultivaluedMap<String, String> headers, InputStream in) throws IOException, WebApplicationException {
077        ExecutionRequest req = null;
078        try {
079            List<String> ctypes = headers.get("Content-Type");
080            String ctype = ctypes.get(0);
081            // we need to copy first the stream into a file otherwise it may
082            // happen that
083            // javax.mail fail to receive some parts - I am not sure why -
084            // perhaps the stream is no more available when javax.mail need it?
085            File tmp = File.createTempFile("nx-automation-mp-upload-", ".tmp");
086            FileUtils.copyToFile(in, tmp);
087            // get the input from the saved file
088            in = new SharedFileInputStream(tmp);
089            try {
090                MimeMultipart mp = new MimeMultipart(new InputStreamDataSource(in, ctype));
091                BodyPart part = mp.getBodyPart(0); // use content ids
092                InputStream pin = part.getInputStream();
093                JsonParser jp = factory.createJsonParser(pin);
094                req = JsonRequestReader.readRequest(jp, headers, getCoreSession());
095                int cnt = mp.getCount();
096                if (cnt == 2) { // a blob
097                    req.setInput(readBlob(request, mp.getBodyPart(1)));
098                } else if (cnt > 2) { // a blob list
099                    BlobList blobs = new BlobList();
100                    for (int i = 1; i < cnt; i++) {
101                        blobs.add(readBlob(request, mp.getBodyPart(i)));
102                    }
103                    req.setInput(blobs);
104                } else {
105                    log.error("Not all parts received.");
106                    for (int i = 0; i < cnt; i++) {
107                        log.error("Received parts: " + mp.getBodyPart(i).getHeader("Content-ID")[0] + " -> "
108                                + mp.getBodyPart(i).getContentType());
109                    }
110                    throw WebException.newException(new IllegalStateException("Received only " + cnt
111                            + " part in a multipart request"));
112                }
113            } finally {
114                try {
115                    in.close();
116                } catch (IOException e) {
117                    // do nothing
118                }
119                tmp.delete();
120            }
121        } catch (MessagingException | IOException e) {
122            throw WebException.newException("Failed to parse multipart request", e);
123        }
124        return req;
125    }
126
127    public static Blob readBlob(HttpServletRequest request, BodyPart part) throws MessagingException, IOException {
128        String ctype = part.getContentType();
129        String fname = part.getFileName();
130        InputStream pin = part.getInputStream();
131        final File tmp = File.createTempFile("nx-automation-upload-", ".tmp");
132        FileUtils.copyToFile(pin, tmp);
133        Blob blob = Blobs.createBlob(tmp, ctype, null, fname);
134        RequestContext.getActiveContext(request).addRequestCleanupHandler(new RequestCleanupHandler() {
135            @Override
136            public void cleanup(HttpServletRequest req) {
137                tmp.delete();
138            }
139        });
140        return blob;
141    }
142
143}