| /* |
| * 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); |
| } |
| } |
| } |