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