blob: 6d9f78627d7c8a9daaf081b9a19950f96a72c70b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.cocoon.forms.formmodel;
import java.util.Locale;
import java.util.StringTokenizer;
import org.apache.cocoon.forms.FormsConstants;
import org.apache.cocoon.forms.FormContext;
import org.apache.cocoon.forms.event.ValueChangedEvent;
import org.apache.cocoon.forms.event.ValueChangedListener;
import org.apache.cocoon.forms.event.ValueChangedListenerEnabled;
import org.apache.cocoon.forms.event.WidgetEvent;
import org.apache.cocoon.forms.event.WidgetEventMulticaster;
import org.apache.cocoon.forms.util.I18nMessage;
import org.apache.cocoon.forms.validation.ValidationError;
import org.apache.cocoon.forms.validation.ValidationErrorAware;
import org.apache.cocoon.servlet.multipart.Part;
import org.apache.cocoon.servlet.multipart.RejectedPart;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.cocoon.environment.Request;
import org.apache.commons.lang.ObjectUtils;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* A file-uploading Widget. This widget gives access via Cocoon Forms, to Cocoon's
* file upload functionality.
* <p>
* This widget accepts value-changed listeners, but the event raised does not hold
* the previous value, as uploads are heavyweight resources that must be released
* as soon as possible.
*
* @version $Id$
*/
public class Upload extends AbstractWidget
implements ValidationErrorAware, ValueChangedListenerEnabled {
private static final String UPLOAD_EL = "upload";
private static final String VALUE_EL = "value";
private static final String VALIDATION_MSG_EL = "validation-message";
private final UploadDefinition definition;
private Part part;
private ValidationError validationError;
private ValueChangedListener listener;
public Upload(UploadDefinition uploadDefinition) {
super(uploadDefinition);
this.definition = uploadDefinition;
this.listener = uploadDefinition.getValueChangedListener();
}
public WidgetDefinition getDefinition() {
return this.definition;
}
public UploadDefinition getUploadDefinition() {
return this.definition;
}
public Object getValue() {
return this.isValid() ? this.part : null;
}
public void setValue(Object object) {
if (object == this.part) {
return;
}
if ((object == null) || (object instanceof Part)) {
this.part = (Part) object;
} else {
throw new RuntimeException("The value of an upload widget must be of type " + Part.class + ".");
}
changed();
}
public void readFromRequest(FormContext formContext) {
if (!getCombinedState().isAcceptingInputs()) {
return;
}
Form form = getForm();
Request request = formContext.getRequest();
String fullId = getRequestParameterName();
Object obj = request.get(fullId);
if (fullId.equals(request.getParameter(Form.SUBMIT_ID_PARAMETER))) {
form.setSubmitWidget(this);
}
// If the request object is a Part, keep it
if (obj instanceof Part) {
Part requestPart = (Part)obj;
if (this.part != null) {
// Replace the current part
this.part.dispose();
}
// Keep the request part
requestPart.setDisposeWithRequest(false);
this.part = requestPart;
if (validateOversize()) {
// Clear any validation error
setValidationError(null);
}
changed();
// If it's not a part and not null, clear any existing value
// We also check if we're the submit widget, as a result of clicking the "..." button
} else if (obj != null || form.getSubmitWidget() == this){
// Clear the part, if any
if (this.part != null) {
this.part.dispose();
this.part = null;
}
setValidationError(null);
// Ensure we redisplay it
changed();
}
// And keep the current state if the parameter doesn't exist or is null
}
private void changed() {
if (this.hasValueChangedListeners() || this.getForm().hasFormHandler()) {
this.getForm().addWidgetEvent(new ValueChangedEvent(this, null, this.part));
}
getForm().addWidgetUpdate(this);
}
private boolean validateMimeType() {
String mimeTypes = this.definition.getMimeTypes();
if (mimeTypes != null) {
StringTokenizer tok = new StringTokenizer(mimeTypes, ", ");
String contentType = this.part.getMimeType();
while(tok.hasMoreTokens()) {
if (tok.nextToken().equals(contentType)) {
return true;
}
}
I18nMessage message = new I18nMessage("upload.invalid-type",
new String[] {contentType},
FormsConstants.I18N_CATALOGUE);
setValidationError(new ValidationError(message));
return false;
}
// No mime type restriction
return true;
}
/**
* Check if the part is oversized, and if yes sets the validation error accordingly
*/
private boolean validateOversize() {
if (!this.part.isRejected()) {
return true;
}
// Set a validation error indicating the sizes in kbytes (rounded)
RejectedPart rjp = (RejectedPart)this.part;
int size = (rjp.getContentLength() + 512) / 1024;
int maxSize = (rjp.getMaxContentLength() + 512) / 1024;
String[] i18nParams = new String[] { String.valueOf(size), String.valueOf(maxSize) };
I18nMessage i18nMessage = new I18nMessage("upload.rejected", i18nParams, FormsConstants.I18N_CATALOGUE);
setValidationError(new ValidationError(i18nMessage));
return false;
}
public boolean validate() {
if (!getCombinedState().isValidatingValues()) {
this.wasValid = true;
return true;
}
if (this.part == null) {
if (this.definition.isRequired()) {
I18nMessage i18nMessage = new I18nMessage("general.field-required", FormsConstants.I18N_CATALOGUE);
setValidationError(new ValidationError(i18nMessage));
}
} else if (validateOversize() && validateMimeType()) {
super.validate();
}
this.wasValid = this.validationError == null;
return this.wasValid;
}
/**
* Returns the validation error, if any. There will always be a validation error in case the
* {@link #validate()} method returned false.
*/
public ValidationError getValidationError() {
return this.validationError;
}
/**
* Set a validation error on this field. This allows fields to be externally marked as invalid by
* application logic.
*
* @param error the validation error
*/
public void setValidationError(ValidationError error) {
if(!ObjectUtils.equals(this.validationError, error)) {
this.validationError = error;
getForm().addWidgetUpdate(this);
}
}
/**
* Adds a ValueChangedListener to this widget instance. Listeners defined
* on the widget instance will be executed in addtion to any listeners
* that might have been defined in the widget definition.
*/
public void addValueChangedListener(ValueChangedListener listener) {
this.listener = WidgetEventMulticaster.add(this.listener, listener);
}
public void removeValueChangedListener(ValueChangedListener listener) {
this.listener = WidgetEventMulticaster.remove(this.listener, listener);
}
public boolean hasValueChangedListeners() {
return this.listener != null;
}
public void broadcastEvent(WidgetEvent event) {
if (event instanceof ValueChangedEvent) {
if (this.listener != null) {
this.listener.valueChanged((ValueChangedEvent)event);
}
} else {
// Other kinds of events
super.broadcastEvent(event);
}
}
/**
* @return "upload"
*/
public String getXMLElementName() {
return UPLOAD_EL;
}
/**
* Adds attributes @required, @mime-types
*/
public AttributesImpl getXMLElementAttributes() {
AttributesImpl attrs = super.getXMLElementAttributes();
attrs.addCDATAAttribute("id", getRequestParameterName());
attrs.addCDATAAttribute("required", String.valueOf(this.definition.isRequired()));
if (this.definition.getMimeTypes() != null) {
attrs.addCDATAAttribute("mime-types", this.definition.getMimeTypes());
}
return attrs;
}
public void generateItemSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException {
if (this.part != null) {
String name = (String)this.part.getHeaders().get("filename");
contentHandler.startElement(FormsConstants.INSTANCE_NS, VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALUE_EL, XMLUtils.EMPTY_ATTRIBUTES);
contentHandler.characters(name.toCharArray(), 0, name.length());
contentHandler.endElement(FormsConstants.INSTANCE_NS, VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALUE_EL);
}
// validation message element: only present if the value is not valid
if (this.validationError != null) {
contentHandler.startElement(FormsConstants.INSTANCE_NS, VALIDATION_MSG_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_MSG_EL, XMLUtils.EMPTY_ATTRIBUTES);
this.validationError.generateSaxFragment(contentHandler);
contentHandler.endElement(FormsConstants.INSTANCE_NS, VALIDATION_MSG_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_MSG_EL);
}
}
}