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}