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