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