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.client.jaxrs.spi;
020
021import static org.nuxeo.ecm.automation.client.Constants.CTYPE_AUTOMATION;
022import static org.nuxeo.ecm.automation.client.Constants.CTYPE_ENTITY;
023import static org.nuxeo.ecm.automation.client.Constants.CTYPE_MULTIPART_EMPTY;
024import static org.nuxeo.ecm.automation.client.Constants.CTYPE_MULTIPART_MIXED;
025
026import java.io.File;
027import java.io.FileInputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.UnsupportedEncodingException;
031import java.net.URLDecoder;
032import java.util.HashMap;
033import java.util.regex.Matcher;
034import java.util.regex.Pattern;
035
036import javax.mail.BodyPart;
037import javax.mail.MessagingException;
038import javax.mail.internet.MimeMultipart;
039import javax.ws.rs.core.Response;
040
041import org.nuxeo.ecm.automation.client.RemoteException;
042import org.nuxeo.ecm.automation.client.jaxrs.spi.marshallers.ExceptionMarshaller;
043import org.nuxeo.ecm.automation.client.jaxrs.util.IOUtils;
044import org.nuxeo.ecm.automation.client.jaxrs.util.InputStreamDataSource;
045import org.nuxeo.ecm.automation.client.model.Blob;
046import org.nuxeo.ecm.automation.client.model.Blobs;
047import org.nuxeo.ecm.automation.client.model.FileBlob;
048
049/**
050 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
051 */
052public class Request extends HashMap<String, String> {
053
054    public static final int GET = 0;
055
056    public static final int POST = 1;
057
058    private static final long serialVersionUID = 1L;
059
060    protected static Pattern RFC2231_ATTR_PATTERN = Pattern.compile(
061            ";?\\s*filename\\s*\\\\*.*\\*=([^']*)'([^']*)'\\s*([^;]+)\\s*", Pattern.CASE_INSENSITIVE);
062
063    protected static Pattern ATTR_PATTERN = Pattern.compile(";?\\s*filename\\s*=\\s*([^;]+)\\s*",
064            Pattern.CASE_INSENSITIVE);
065
066    protected final int method;
067
068    protected final String url;
069
070    protected final boolean isMultiPart;
071
072    protected Object entity;
073
074    public Request(int method, String url) {
075        this.method = method;
076        this.url = url;
077        isMultiPart = false;
078    }
079
080    public Request(int method, String url, MimeMultipart entity) {
081        this.method = method;
082        this.url = url;
083        this.entity = entity;
084        isMultiPart = true;
085    }
086
087    public Request(int method, String url, String entity) {
088        this.method = method;
089        this.url = url;
090        this.entity = entity;
091        isMultiPart = false;
092    }
093
094    public int getMethod() {
095        return method;
096    }
097
098    public String getUrl() {
099        return url;
100    }
101
102    public Object getEntity() {
103        return entity;
104    }
105
106    public final boolean isMultiPart() {
107        return isMultiPart;
108    }
109
110    public MimeMultipart asMultiPartEntity() {
111        return isMultiPart ? (MimeMultipart) entity : null;
112    }
113
114    public String asStringEntity() {
115        return isMultiPart ? null : (String) entity;
116    }
117
118    /**
119     * Must read the object from the server response and return it or throw a {@link RemoteException} if server sent an
120     * error.
121     */
122    public Object handleResult(int status, String ctype, String disp, InputStream stream)
123            throws RemoteException, IOException {
124        // Specific http status handling
125        if (status >= Response.Status.BAD_REQUEST.getStatusCode()) {
126            handleException(status, ctype, stream);
127        } else if (status == Response.Status.NO_CONTENT.getStatusCode() || stream == null) {
128            if (ctype != null && ctype.toLowerCase().startsWith(CTYPE_MULTIPART_EMPTY)) {
129                // empty entity and content type of nuxeo empty list
130                return new Blobs();
131            }
132            // no content
133            return null;
134        }
135        // Check content type
136        if (ctype == null) {
137            if (status != Response.Status.OK.getStatusCode()) {
138                // this may happen when login failed
139                throw new RemoteException(status, "ServerError", "Server Error", "");
140            }
141            // cannot handle responses with no content type
142            return null;
143        }
144        // Handle result
145        String lctype = ctype.toLowerCase();
146        if (lctype.startsWith(CTYPE_ENTITY)) {
147            return JsonMarshalling.readEntity(IOUtils.read(stream));
148        } else if (lctype.startsWith(CTYPE_AUTOMATION)) {
149            return JsonMarshalling.readRegistry(IOUtils.read(stream));
150        } else if (lctype.startsWith(CTYPE_MULTIPART_MIXED)) { // list of
151                                                               // blobs
152            return readBlobs(ctype, stream);
153        } else { // a blob?
154            String fname = null;
155            if (disp != null) {
156                fname = getFileName(disp);
157            }
158            return readBlob(ctype, fname, stream);
159        }
160    }
161
162    protected static Blobs readBlobs(String ctype, InputStream in) throws IOException {
163        Blobs files = new Blobs();
164        // save the stream to a temporary file
165        File file = IOUtils.copyToTempFile(in);
166        try (FileInputStream fin = new FileInputStream(file)) {
167            MimeMultipart mp = new MimeMultipart(new InputStreamDataSource(fin, ctype));
168            int size = mp.getCount();
169            for (int i = 0; i < size; i++) {
170                BodyPart part = mp.getBodyPart(i);
171                String fname = part.getFileName();
172                files.add(readBlob(part.getContentType(), fname, part.getInputStream()));
173            }
174        } catch (MessagingException e) {
175            throw new IOException(e);
176        } finally {
177            file.delete();
178        }
179        return files;
180    }
181
182    protected static Blob readBlob(String ctype, String fileName, InputStream in) throws IOException {
183        File file = IOUtils.copyToTempFile(in);
184        FileBlob blob = new FileBlob(file);
185        blob.setMimeType(ctype);
186        if (fileName != null) {
187            blob.setFileName(fileName);
188        }
189        return blob;
190    }
191
192    protected static String getFileName(String ctype) {
193        Matcher m = RFC2231_ATTR_PATTERN.matcher(ctype);
194        if (m.find()) {
195            try {
196                return URLDecoder.decode(m.group(3), m.group(1));
197            } catch (UnsupportedEncodingException e) {
198                throw new RuntimeException(e);
199            }
200        }
201        m = ATTR_PATTERN.matcher(ctype);
202        if (m.find()) {
203            return m.group(1);
204        }
205        return null;
206    }
207
208    protected void handleException(int status, String ctype, InputStream stream) throws RemoteException, IOException {
209        if (stream == null) {
210            throw new RemoteException(status, "ServerError", "Server Error", "");
211        } else if (CTYPE_ENTITY.equalsIgnoreCase(ctype)) {
212            String content = IOUtils.read(stream);
213            RemoteException e;
214            try {
215                e = ExceptionMarshaller.readException(content);
216            } catch (IOException t) {
217                throw new RemoteException(status, "ServerError", "Server Error", content);
218            }
219            throw e;
220        } else {
221            throw new RemoteException(status, "ServerError", "Server Error", IOUtils.read(stream));
222        }
223    }
224
225}