001/* 002 * (C) Copyright 2006-2008 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 * 019 * $Id$ 020 */ 021 022package org.nuxeo.ecm.webengine.forms; 023 024import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 025import static org.apache.commons.lang3.StringUtils.defaultIfEmpty; 026 027import java.io.File; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.UnsupportedEncodingException; 031import java.nio.file.Files; 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037 038import javax.servlet.http.HttpServletRequest; 039 040import org.apache.commons.fileupload.FileItem; 041import org.apache.commons.fileupload.FileUploadException; 042import org.apache.commons.fileupload.RequestContext; 043import org.apache.commons.fileupload.disk.DiskFileItem; 044import org.apache.commons.fileupload.disk.DiskFileItemFactory; 045import org.apache.commons.fileupload.servlet.ServletFileUpload; 046import org.apache.commons.fileupload.servlet.ServletRequestContext; 047import org.apache.commons.lang3.StringUtils; 048import org.nuxeo.ecm.core.api.Blob; 049import org.nuxeo.ecm.core.api.Blobs; 050import org.nuxeo.ecm.core.api.DocumentModel; 051import org.nuxeo.ecm.core.api.NuxeoException; 052import org.nuxeo.ecm.core.api.PropertyException; 053import org.nuxeo.ecm.core.api.VersioningOption; 054import org.nuxeo.ecm.core.api.model.Property; 055import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 056import org.nuxeo.ecm.core.schema.types.ListType; 057import org.nuxeo.ecm.core.schema.types.Type; 058import org.nuxeo.ecm.webengine.forms.validation.Form; 059import org.nuxeo.ecm.webengine.forms.validation.FormManager; 060import org.nuxeo.ecm.webengine.forms.validation.ValidationException; 061import org.nuxeo.ecm.webengine.servlet.WebConst; 062 063/** 064 * @author <a href="mailto:[email protected]">Bogdan Stefanescu</a> 065 */ 066public class FormData implements FormInstance { 067 068 public static final String PROPERTY = "property"; 069 070 public static final String TITLE = "dc:title"; 071 072 public static final String DOCTYPE = "doctype"; 073 074 public static final String VERSIONING = "versioning"; 075 076 public static final String MAJOR = "major"; 077 078 public static final String MINOR = "minor"; 079 080 protected static ServletFileUpload fu = new ServletFileUpload(new DiskFileItemFactory()); 081 082 protected final HttpServletRequest request; 083 084 protected boolean isMultipart = false; 085 086 protected RequestContext ctx; 087 088 // Multipart items cache 089 protected Map<String, List<FileItem>> items; 090 091 // parameter map cache - used in Multipart forms to convert to 092 // ServletRequest#getParameterMap 093 // format 094 // protected Map<String, String[]> parameterMap; 095 096 public FormData(HttpServletRequest request) { 097 this.request = request; 098 isMultipart = getIsMultipartContent(); 099 if (isMultipart) { 100 ctx = new ServletRequestContext(request); 101 } 102 } 103 104 protected String getString(FileItem item) { 105 try { 106 String enc = request.getCharacterEncoding(); 107 if (enc != null) { 108 return item.getString(request.getCharacterEncoding()); 109 } else { 110 return item.getString(); 111 } 112 } catch (UnsupportedEncodingException e) { 113 return item.getString(); 114 } 115 } 116 117 protected boolean getIsMultipartContent() { 118 String method = request.getMethod().toLowerCase(); 119 if (!"post".equals(method) && !"put".equals(method)) { 120 return false; 121 } 122 String contentType = request.getContentType(); 123 if (contentType == null) { 124 return false; 125 } 126 return contentType.toLowerCase().startsWith(WebConst.MULTIPART); 127 } 128 129 public boolean isMultipartContent() { 130 return isMultipart; 131 } 132 133 @SuppressWarnings("unchecked") 134 public Map<String, String[]> getFormFields() { 135 if (isMultipart) { 136 return getMultiPartFormFields(); 137 } else { 138 return request.getParameterMap(); 139 } 140 } 141 142 public Map<String, String[]> getMultiPartFormFields() { 143 Map<String, List<FileItem>> items = getMultiPartItems(); 144 Map<String, String[]> result = new HashMap<String, String[]>(); 145 for (Map.Entry<String, List<FileItem>> entry : items.entrySet()) { 146 List<FileItem> list = entry.getValue(); 147 String[] ar = new String[list.size()]; 148 for (int i = 0; i < ar.length; i++) { 149 ar[i] = getString(list.get(i)); 150 } 151 result.put(entry.getKey(), ar); 152 } 153 return result; 154 } 155 156 @SuppressWarnings("unchecked") 157 public Map<String, List<FileItem>> getMultiPartItems() { 158 if (items == null) { 159 if (!isMultipart) { 160 throw new IllegalStateException("Not in a multi part form request"); 161 } 162 try { 163 items = new HashMap<String, List<FileItem>>(); 164 ServletRequestContext ctx = new ServletRequestContext(request); 165 List<FileItem> fileItems = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(ctx); 166 for (FileItem item : fileItems) { 167 String key = item.getFieldName(); 168 List<FileItem> list = items.get(key); 169 if (list == null) { 170 list = new ArrayList<FileItem>(); 171 items.put(key, list); 172 } 173 list.add(item); 174 } 175 } catch (FileUploadException e) { 176 throw new NuxeoException("Failed to get uploaded files", e); 177 } 178 } 179 return items; 180 } 181 182 @SuppressWarnings("unchecked") 183 public Collection<String> getKeys() { 184 if (isMultipart) { 185 return getMultiPartItems().keySet(); 186 } else { 187 return ((Map<String, String[]>) request.getParameterMap()).keySet(); 188 } 189 } 190 191 public Blob getBlob(String key) { 192 FileItem item = getFileItem(key); 193 return item == null ? null : getBlob(item); 194 } 195 196 public Blob[] getBlobs(String key) { 197 List<FileItem> list = getFileItems(key); 198 Blob[] ar = null; 199 if (list != null) { 200 ar = new Blob[list.size()]; 201 for (int i = 0, len = list.size(); i < len; i++) { 202 ar[i] = getBlob(list.get(i)); 203 } 204 } 205 return ar; 206 } 207 208 /** 209 * XXX TODO implement it 210 */ 211 public Map<String, Blob[]> getBlobFields() { 212 throw new UnsupportedOperationException("Not yet implemented"); 213 } 214 215 public Blob getFirstBlob() { 216 Map<String, List<FileItem>> items = getMultiPartItems(); 217 for (List<FileItem> list : items.values()) { 218 for (FileItem item : list) { 219 if (!item.isFormField()) { 220 return getBlob(item); 221 } 222 } 223 } 224 return null; 225 } 226 227 protected Blob getBlob(FileItem item) { 228 try { 229 Blob blob; 230 if (item.isInMemory()) { 231 blob = Blobs.createBlob(item.get()); 232 } else { 233 File file; 234 if (item instanceof DiskFileItem // 235 && (file = ((DiskFileItem) item).getStoreLocation()) != null) { 236 // move the file to a temporary blob we own 237 blob = Blobs.createBlobWithExtension(null); 238 Files.move(file.toPath(), blob.getFile().toPath(), REPLACE_EXISTING); 239 } else { 240 // if we couldn't get to the file, use the InputStream 241 try (InputStream in = item.getInputStream()) { 242 blob = Blobs.createBlob(in); 243 } 244 } 245 } 246 blob.setMimeType(defaultIfEmpty(item.getContentType(), "application/octet-stream")); 247 blob.setFilename(item.getName()); 248 return blob; 249 } catch (IOException e) { 250 throw new NuxeoException("Failed to get blob data", e); 251 } 252 } 253 254 public final FileItem getFileItem(String key) { 255 Map<String, List<FileItem>> items = getMultiPartItems(); 256 List<FileItem> list = items.get(key); 257 if (list != null && !list.isEmpty()) { 258 return list.get(0); 259 } 260 return null; 261 } 262 263 public final List<FileItem> getFileItems(String key) { 264 return getMultiPartItems().get(key); 265 } 266 267 public String getMultiPartFormProperty(String key) { 268 FileItem item = getFileItem(key); 269 return item == null ? null : getString(item); 270 } 271 272 public String[] getMultiPartFormListProperty(String key) { 273 List<FileItem> list = getFileItems(key); 274 String[] ar = null; 275 if (list != null) { 276 ar = new String[list.size()]; 277 for (int i = 0, len = list.size(); i < len; i++) { 278 ar[i] = getString(list.get(i)); 279 } 280 } 281 return ar; 282 } 283 284 /** 285 * @param key 286 * @return an array of strings or an array of blobs 287 */ 288 public Object[] getMultiPartFormItems(String key) { 289 return getMultiPartFormItems(getFileItems(key)); 290 } 291 292 public Object[] getMultiPartFormItems(List<FileItem> list) { 293 Object[] ar = null; 294 if (list != null) { 295 if (list.isEmpty()) { 296 return null; 297 } 298 FileItem item0 = list.get(0); 299 if (item0.isFormField()) { 300 ar = new String[list.size()]; 301 ar[0] = getString(item0); 302 for (int i = 1, len = list.size(); i < len; i++) { 303 ar[i] = getString(list.get(i)); 304 } 305 } else { 306 List<Blob> blobs = new ArrayList<Blob>(); 307 for (FileItem item : list) { 308 if (!StringUtils.isBlank(item.getName())) { 309 blobs.add(getBlob(item)); 310 } 311 } 312 ar = blobs.toArray(new Blob[blobs.size()]); 313 } 314 } 315 return ar; 316 } 317 318 public final Object getFileItemValue(FileItem item) { 319 if (item.isFormField()) { 320 return getString(item); 321 } else { 322 return getBlob(item); 323 } 324 } 325 326 public String getFormProperty(String key) { 327 String[] value = request.getParameterValues(key); 328 if (value != null && value.length > 0) { 329 return value[0]; 330 } 331 return null; 332 } 333 334 public String[] getFormListProperty(String key) { 335 return request.getParameterValues(key); 336 } 337 338 public String getString(String key) { 339 if (isMultipart) { 340 return getMultiPartFormProperty(key); 341 } else { 342 return getFormProperty(key); 343 } 344 } 345 346 public String[] getList(String key) { 347 if (isMultipart) { 348 return getMultiPartFormListProperty(key); 349 } else { 350 return getFormListProperty(key); 351 } 352 } 353 354 public Object[] get(String key) { 355 if (isMultipart) { 356 return getMultiPartFormItems(key); 357 } else { 358 return getFormListProperty(key); 359 } 360 } 361 362 public void fillDocument(DocumentModel doc) { 363 try { 364 if (isMultipart) { 365 fillDocumentFromMultiPartForm(doc); 366 } else { 367 fillDocumentFromForm(doc); 368 } 369 } catch (PropertyException e) { 370 e.addInfo("Failed to fill document properties from request properties"); 371 throw e; 372 } 373 } 374 375 @SuppressWarnings("unchecked") 376 public void fillDocumentFromForm(DocumentModel doc) throws PropertyException { 377 Map<String, String[]> map = request.getParameterMap(); 378 for (Map.Entry<String, String[]> entry : map.entrySet()) { 379 String key = entry.getKey(); 380 if (key.indexOf(':') > -1) { // an XPATH property 381 Property p; 382 try { 383 p = doc.getProperty(key); 384 } catch (PropertyException e) { 385 continue; // not a valid property 386 } 387 String[] ar = entry.getValue(); 388 fillDocumentProperty(p, key, ar); 389 } 390 } 391 } 392 393 public void fillDocumentFromMultiPartForm(DocumentModel doc) throws PropertyException { 394 Map<String, List<FileItem>> map = getMultiPartItems(); 395 for (Map.Entry<String, List<FileItem>> entry : map.entrySet()) { 396 String key = entry.getKey(); 397 if (key.indexOf(':') > -1) { // an XPATH property 398 Property p; 399 try { 400 p = doc.getProperty(key); 401 } catch (PropertyException e) { 402 continue; // not a valid property 403 } 404 List<FileItem> list = entry.getValue(); 405 if (list.isEmpty()) { 406 fillDocumentProperty(p, key, null); 407 } else { 408 Object[] ar = getMultiPartFormItems(list); 409 fillDocumentProperty(p, key, ar); 410 } 411 } 412 } 413 } 414 415 static void fillDocumentProperty(Property p, String key, Object[] ar) throws PropertyException { 416 if (ar == null || ar.length == 0) { 417 p.remove(); 418 } else if (p.isScalar()) { 419 p.setValue(ar[0]); 420 } else if (p.isList()) { 421 if (!p.isContainer()) { // an array 422 p.setValue(ar); 423 } else { 424 Type elType = ((ListType) p.getType()).getFieldType(); 425 if (elType.isSimpleType()) { 426 p.setValue(ar); 427 } else if ("content".equals(elType.getName())) { 428 // list of blobs 429 List<Blob> blobs = new ArrayList<Blob>(); 430 if (ar.getClass().getComponentType() == String.class) { // transform 431 // strings 432 // to 433 // blobs 434 for (Object obj : ar) { 435 blobs.add(Blobs.createBlob(obj.toString())); 436 } 437 } else { 438 for (Object obj : ar) { 439 blobs.add((Blob) obj); 440 } 441 } 442 p.setValue(blobs); 443 } else { 444 // complex properties will be ignored 445 // throw new 446 // WebException("Cannot create complex lists properties from HTML forms"); 447 } 448 } 449 } else if (p.isComplex()) { 450 if (p.getClass() == BlobProperty.class) { 451 // should be a file upload 452 Blob blob = null; 453 if (ar[0].getClass() == String.class) { 454 blob = Blobs.createBlob(ar[0].toString()); 455 } else { 456 blob = (Blob) ar[0]; 457 } 458 p.setValue(blob); 459 } else { 460 // complex properties will be ignored 461 // throw new WebException( 462 // "Cannot set complex properties from HTML forms. You need to set each sub-scalar property 463 // explicitely"); 464 } 465 } 466 } 467 468 public VersioningOption getVersioningOption() { 469 String val = getString(VERSIONING); 470 if (val != null) { 471 return val.equals(MAJOR) ? VersioningOption.MAJOR : val.equals(MINOR) ? VersioningOption.MINOR : null; 472 } 473 return null; 474 } 475 476 public String getDocumentType() { 477 return getString(DOCTYPE); 478 } 479 480 public String getDocumentTitle() { 481 return getString(TITLE); 482 } 483 484 public <T extends Form> T validate(Class<T> type) throws ValidationException { 485 T proxy = FormManager.newProxy(type); 486 try { 487 proxy.load(this, proxy); 488 return proxy; 489 } catch (ValidationException e) { 490 e.setForm(proxy); 491 throw e; 492 } 493 } 494 495}