/*
* (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu
*
* $Id$
*/
package org.nuxeo.ecm.webengine.forms;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.apache.commons.lang.StringUtils;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.Blobs;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.PropertyException;
import org.nuxeo.ecm.core.api.VersioningOption;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
import org.nuxeo.ecm.core.schema.types.ListType;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.webengine.WebException;
import org.nuxeo.ecm.webengine.forms.validation.Form;
import org.nuxeo.ecm.webengine.forms.validation.FormManager;
import org.nuxeo.ecm.webengine.forms.validation.ValidationException;
import org.nuxeo.ecm.webengine.servlet.WebConst;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class FormData implements FormInstance {
public static final String PROPERTY = "property";
public static final String TITLE = "dc:title";
public static final String DOCTYPE = "doctype";
public static final String VERSIONING = "versioning";
public static final String MAJOR = "major";
public static final String MINOR = "minor";
protected static ServletFileUpload fu = new ServletFileUpload(new DiskFileItemFactory());
protected final HttpServletRequest request;
protected boolean isMultipart = false;
protected RequestContext ctx;
// Multipart items cache
protected Map<String, List<FileItem>> items;
// parameter map cache - used in Multipart forms to convert to
// ServletRequest#getParameterMap
// format
// protected Map<String, String[]> parameterMap;
public FormData(HttpServletRequest request) {
this.request = request;
isMultipart = getIsMultipartContent();
if (isMultipart) {
ctx = new ServletRequestContext(request);
}
}
protected String getString(FileItem item) {
try {
String enc = request.getCharacterEncoding();
if (enc != null) {
return item.getString(request.getCharacterEncoding());
} else {
return item.getString();
}
} catch (UnsupportedEncodingException e) {
return item.getString();
}
}
protected boolean getIsMultipartContent() {
String method = request.getMethod().toLowerCase();
if (!"post".equals(method) && !"put".equals(method)) {
return false;
}
String contentType = request.getContentType();
if (contentType == null) {
return false;
}
return contentType.toLowerCase().startsWith(WebConst.MULTIPART);
}
public boolean isMultipartContent() {
return isMultipart;
}
@SuppressWarnings("unchecked")
public Map<String, String[]> getFormFields() {
if (isMultipart) {
return getMultiPartFormFields();
} else {
return request.getParameterMap();
}
}
public Map<String, String[]> getMultiPartFormFields() {
Map<String, List<FileItem>> items = getMultiPartItems();
Map<String, String[]> result = new HashMap<String, String[]>();
for (Map.Entry<String, List<FileItem>> entry : items.entrySet()) {
List<FileItem> list = entry.getValue();
String[] ar = new String[list.size()];
for (int i = 0; i < ar.length; i++) {
ar[i] = getString(list.get(i));
}
result.put(entry.getKey(), ar);
}
return result;
}
@SuppressWarnings("unchecked")
public Map<String, List<FileItem>> getMultiPartItems() {
if (items == null) {
if (!isMultipart) {
throw new IllegalStateException("Not in a multi part form request");
}
try {
items = new HashMap<String, List<FileItem>>();
ServletRequestContext ctx = new ServletRequestContext(request);
List<FileItem> fileItems = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(ctx);
for (FileItem item : fileItems) {
String key = item.getFieldName();
List<FileItem> list = items.get(key);
if (list == null) {
list = new ArrayList<FileItem>();
items.put(key, list);
}
list.add(item);
}
} catch (FileUploadException e) {
throw WebException.wrap("Failed to get uploaded files", e);
}
}
return items;
}
@SuppressWarnings("unchecked")
public Collection<String> getKeys() {
if (isMultipart) {
return getMultiPartItems().keySet();
} else {
return ((Map<String, String[]>) request.getParameterMap()).keySet();
}
}
public Blob getBlob(String key) {
FileItem item = getFileItem(key);
return item == null ? null : getBlob(item);
}
public Blob[] getBlobs(String key) {
List<FileItem> list = getFileItems(key);
Blob[] ar = null;
if (list != null) {
ar = new Blob[list.size()];
for (int i = 0, len = list.size(); i < len; i++) {
ar[i] = getBlob(list.get(i));
}
}
return ar;
}
/**
* XXX TODO implement it
*/
public Map<String, Blob[]> getBlobFields() {
throw new UnsupportedOperationException("Not yet implemented");
}
public Blob getFirstBlob() {
Map<String, List<FileItem>> items = getMultiPartItems();
for (List<FileItem> list : items.values()) {
for (FileItem item : list) {
if (!item.isFormField()) {
return getBlob(item);
}
}
}
return null;
}
protected Blob getBlob(FileItem item) {
try {
InputStream in;
if (item.isInMemory()) {
in = new ByteArrayInputStream(item.get());
} else {
in = item.getInputStream();
}
String ctype = item.getContentType();
Blob blob = Blobs.createBlob(in, ctype == null ? "application/octet-stream" : ctype);
blob.setFilename(item.getName());
in.close();
return blob;
} catch (IOException e) {
throw WebException.wrap("Failed to get blob data", e);
}
}
public final FileItem getFileItem(String key) {
Map<String, List<FileItem>> items = getMultiPartItems();
List<FileItem> list = items.get(key);
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
}
public final List<FileItem> getFileItems(String key) {
return getMultiPartItems().get(key);
}
public String getMultiPartFormProperty(String key) {
FileItem item = getFileItem(key);
return item == null ? null : getString(item);
}
public String[] getMultiPartFormListProperty(String key) {
List<FileItem> list = getFileItems(key);
String[] ar = null;
if (list != null) {
ar = new String[list.size()];
for (int i = 0, len = list.size(); i < len; i++) {
ar[i] = getString(list.get(i));
}
}
return ar;
}
/**
* @param key
* @return an array of strings or an array of blobs
*/
public Object[] getMultiPartFormItems(String key) {
return getMultiPartFormItems(getFileItems(key));
}
public Object[] getMultiPartFormItems(List<FileItem> list) {
Object[] ar = null;
if (list != null) {
if (list.isEmpty()) {
return null;
}
FileItem item0 = list.get(0);
if (item0.isFormField()) {
ar = new String[list.size()];
ar[0] = getString(item0);
for (int i = 1, len = list.size(); i < len; i++) {
ar[i] = getString(list.get(i));
}
} else {
List<Blob> blobs = new ArrayList<Blob>();
for (FileItem item : list) {
if (!StringUtils.isBlank(item.getName())) {
blobs.add(getBlob(item));
}
}
ar = blobs.toArray(new Blob[blobs.size()]);
}
}
return ar;
}
public final Object getFileItemValue(FileItem item) {
if (item.isFormField()) {
return getString(item);
} else {
return getBlob(item);
}
}
public String getFormProperty(String key) {
String[] value = request.getParameterValues(key);
if (value != null && value.length > 0) {
return value[0];
}
return null;
}
public String[] getFormListProperty(String key) {
return request.getParameterValues(key);
}
public String getString(String key) {
if (isMultipart) {
return getMultiPartFormProperty(key);
} else {
return getFormProperty(key);
}
}
public String[] getList(String key) {
if (isMultipart) {
return getMultiPartFormListProperty(key);
} else {
return getFormListProperty(key);
}
}
public Object[] get(String key) {
if (isMultipart) {
return getMultiPartFormItems(key);
} else {
return getFormListProperty(key);
}
}
public void fillDocument(DocumentModel doc) {
try {
if (isMultipart) {
fillDocumentFromMultiPartForm(doc);
} else {
fillDocumentFromForm(doc);
}
} catch (PropertyException e) {
throw WebException.wrap("Failed to fill document properties from request properties", e);
}
}
@SuppressWarnings("unchecked")
public void fillDocumentFromForm(DocumentModel doc) throws PropertyException {
Map<String, String[]> map = request.getParameterMap();
for (Map.Entry<String, String[]> entry : map.entrySet()) {
String key = entry.getKey();
if (key.indexOf(':') > -1) { // an XPATH property
Property p;
try {
p = doc.getProperty(key);
} catch (PropertyException e) {
continue; // not a valid property
}
String[] ar = entry.getValue();
fillDocumentProperty(p, key, ar);
}
}
}
public void fillDocumentFromMultiPartForm(DocumentModel doc) throws PropertyException {
Map<String, List<FileItem>> map = getMultiPartItems();
for (Map.Entry<String, List<FileItem>> entry : map.entrySet()) {
String key = entry.getKey();
if (key.indexOf(':') > -1) { // an XPATH property
Property p;
try {
p = doc.getProperty(key);
} catch (PropertyException e) {
continue; // not a valid property
}
List<FileItem> list = entry.getValue();
if (list.isEmpty()) {
fillDocumentProperty(p, key, null);
} else {
Object[] ar = getMultiPartFormItems(list);
fillDocumentProperty(p, key, ar);
}
}
}
}
static void fillDocumentProperty(Property p, String key, Object[] ar) throws PropertyException {
if (ar == null || ar.length == 0) {
p.remove();
} else if (p.isScalar()) {
p.setValue(ar[0]);
} else if (p.isList()) {
if (!p.isContainer()) { // an array
p.setValue(ar);
} else {
Type elType = ((ListType) p.getType()).getFieldType();
if (elType.isSimpleType()) {
p.setValue(ar);
} else if ("content".equals(elType.getName())) {
// list of blobs
List<Blob> blobs = new ArrayList<Blob>();
if (ar.getClass().getComponentType() == String.class) { // transform
// strings
// to
// blobs
for (Object obj : ar) {
blobs.add(Blobs.createBlob(obj.toString()));
}
} else {
for (Object obj : ar) {
blobs.add((Blob) obj);
}
}
p.setValue(blobs);
} else {
// complex properties will be ignored
// throw new
// WebException("Cannot create complex lists properties from HTML forms");
}
}
} else if (p.isComplex()) {
if (p.getClass() == BlobProperty.class) {
// should be a file upload
Blob blob = null;
if (ar[0].getClass() == String.class) {
blob = Blobs.createBlob(ar[0].toString());
} else {
blob = (Blob) ar[0];
}
p.setValue(blob);
} else {
// complex properties will be ignored
// throw new WebException(
// "Cannot set complex properties from HTML forms. You need to set each sub-scalar property explicitely");
}
}
}
public VersioningOption getVersioningOption() {
String val = getString(VERSIONING);
if (val != null) {
return val.equals(MAJOR) ? VersioningOption.MAJOR : val.equals(MINOR) ? VersioningOption.MINOR : null;
}
return null;
}
public String getDocumentType() {
return getString(DOCTYPE);
}
public String getDocumentTitle() {
return getString(TITLE);
}
public <T extends Form> T validate(Class<T> type) throws ValidationException {
T proxy = FormManager.newProxy(type);
try {
proxy.load(this, proxy);
return proxy;
} catch (ValidationException e) {
e.setForm(proxy);
throw e;
}
}
}