001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.formmodel;
018:
019: import java.util.Locale;
020: import java.util.StringTokenizer;
021:
022: import org.apache.cocoon.forms.FormsConstants;
023: import org.apache.cocoon.forms.FormContext;
024: import org.apache.cocoon.forms.event.ValueChangedEvent;
025: import org.apache.cocoon.forms.event.ValueChangedListener;
026: import org.apache.cocoon.forms.event.ValueChangedListenerEnabled;
027: import org.apache.cocoon.forms.event.WidgetEvent;
028: import org.apache.cocoon.forms.event.WidgetEventMulticaster;
029: import org.apache.cocoon.forms.util.I18nMessage;
030: import org.apache.cocoon.forms.validation.ValidationError;
031: import org.apache.cocoon.forms.validation.ValidationErrorAware;
032:
033: import org.apache.cocoon.servlet.multipart.Part;
034: import org.apache.cocoon.servlet.multipart.RejectedPart;
035:
036: import org.apache.cocoon.xml.AttributesImpl;
037: import org.apache.cocoon.xml.XMLUtils;
038: import org.apache.cocoon.environment.Request;
039:
040: import org.apache.commons.lang.ObjectUtils;
041:
042: import org.xml.sax.ContentHandler;
043: import org.xml.sax.SAXException;
044:
045: /**
046: * A file-uploading Widget. This widget gives access via Cocoon Forms, to Cocoon's
047: * file upload functionality.
048: * <p>
049: * This widget accepts value-changed listeners, but the event raised does not hold
050: * the previous value, as uploads are heavyweight resources that must be released
051: * as soon as possible.
052: *
053: * @version $Id: Upload.java 462520 2006-10-10 19:39:14Z vgritsenko $
054: */
055: public class Upload extends AbstractWidget implements
056: ValidationErrorAware, ValueChangedListenerEnabled {
057:
058: private static final String UPLOAD_EL = "upload";
059: private static final String VALUE_EL = "value";
060: private static final String VALIDATION_MSG_EL = "validation-message";
061:
062: private final UploadDefinition definition;
063: private Part part;
064: private ValidationError validationError;
065: private ValueChangedListener listener;
066:
067: public Upload(UploadDefinition uploadDefinition) {
068: super (uploadDefinition);
069: this .definition = uploadDefinition;
070: this .listener = uploadDefinition.getValueChangedListener();
071: }
072:
073: public WidgetDefinition getDefinition() {
074: return this .definition;
075: }
076:
077: public UploadDefinition getUploadDefinition() {
078: return this .definition;
079: }
080:
081: public Object getValue() {
082: return this .isValid() ? this .part : null;
083: }
084:
085: public void setValue(Object object) {
086: if (object == this .part) {
087: return;
088: }
089:
090: if ((object == null) || (object instanceof Part)) {
091: this .part = (Part) object;
092: } else {
093: throw new RuntimeException(
094: "The value of an upload widget must be of type "
095: + Part.class + ".");
096: }
097:
098: changed();
099: }
100:
101: public void readFromRequest(FormContext formContext) {
102: if (!getCombinedState().isAcceptingInputs()) {
103: return;
104: }
105:
106: Form form = getForm();
107: Request request = formContext.getRequest();
108: String fullId = getRequestParameterName();
109:
110: Object obj = request.get(fullId);
111:
112: if (fullId.equals(request
113: .getParameter(Form.SUBMIT_ID_PARAMETER))) {
114: form.setSubmitWidget(this );
115: }
116:
117: // If the request object is a Part, keep it
118: if (obj instanceof Part) {
119: Part requestPart = (Part) obj;
120: if (this .part != null) {
121: // Replace the current part
122: this .part.dispose();
123: }
124:
125: // Keep the request part
126: requestPart.setDisposeWithRequest(false);
127: this .part = requestPart;
128: if (validateOversize()) {
129: // Clear any validation error
130: setValidationError(null);
131: }
132: changed();
133:
134: // If it's not a part and not null, clear any existing value
135: // We also check if we're the submit widget, as a result of clicking the "..." button
136: } else if (obj != null || form.getSubmitWidget() == this ) {
137: // Clear the part, if any
138: if (this .part != null) {
139: this .part.dispose();
140: this .part = null;
141: }
142: setValidationError(null);
143: // Ensure we redisplay it
144: changed();
145: }
146:
147: // And keep the current state if the parameter doesn't exist or is null
148: }
149:
150: private void changed() {
151: if (this .hasValueChangedListeners()
152: || this .getForm().hasFormHandler()) {
153: this .getForm().addWidgetEvent(
154: new ValueChangedEvent(this , null, this .part));
155: }
156: getForm().addWidgetUpdate(this );
157: }
158:
159: private boolean validateMimeType() {
160: String mimeTypes = this .definition.getMimeTypes();
161: if (mimeTypes != null) {
162: StringTokenizer tok = new StringTokenizer(mimeTypes, ", ");
163: String contentType = this .part.getMimeType();
164: while (tok.hasMoreTokens()) {
165: if (tok.nextToken().equals(contentType)) {
166: return true;
167: }
168: }
169: I18nMessage message = new I18nMessage(
170: "upload.invalid-type",
171: new String[] { contentType },
172: FormsConstants.I18N_CATALOGUE);
173: setValidationError(new ValidationError(message));
174: return false;
175: }
176:
177: // No mime type restriction
178: return true;
179: }
180:
181: /**
182: * Check if the part is oversized, and if yes sets the validation error accordingly
183: */
184: private boolean validateOversize() {
185: if (!this .part.isRejected()) {
186: return true;
187: }
188:
189: // Set a validation error indicating the sizes in kbytes (rounded)
190: RejectedPart rjp = (RejectedPart) this .part;
191: int size = (rjp.getContentLength() + 512) / 1024;
192: int maxSize = (rjp.getMaxContentLength() + 512) / 1024;
193: String[] i18nParams = new String[] { String.valueOf(size),
194: String.valueOf(maxSize) };
195: I18nMessage i18nMessage = new I18nMessage("upload.rejected",
196: i18nParams, FormsConstants.I18N_CATALOGUE);
197: setValidationError(new ValidationError(i18nMessage));
198: return false;
199: }
200:
201: public boolean validate() {
202: if (!getCombinedState().isValidatingValues()) {
203: this .wasValid = true;
204: return true;
205: }
206:
207: if (this .part == null) {
208: if (this .definition.isRequired()) {
209: I18nMessage i18nMessage = new I18nMessage(
210: "general.field-required",
211: FormsConstants.I18N_CATALOGUE);
212: setValidationError(new ValidationError(i18nMessage));
213: }
214: } else if (validateOversize() && validateMimeType()) {
215: super .validate();
216: }
217:
218: this .wasValid = this .validationError == null;
219: return this .wasValid;
220: }
221:
222: /**
223: * Returns the validation error, if any. There will always be a validation error in case the
224: * {@link #validate()} method returned false.
225: */
226: public ValidationError getValidationError() {
227: return this .validationError;
228: }
229:
230: /**
231: * Set a validation error on this field. This allows fields to be externally marked as invalid by
232: * application logic.
233: *
234: * @param error the validation error
235: */
236: public void setValidationError(ValidationError error) {
237: if (!ObjectUtils.equals(this .validationError, error)) {
238: this .validationError = error;
239: getForm().addWidgetUpdate(this );
240: }
241: }
242:
243: /**
244: * Adds a ValueChangedListener to this widget instance. Listeners defined
245: * on the widget instance will be executed in addtion to any listeners
246: * that might have been defined in the widget definition.
247: */
248: public void addValueChangedListener(ValueChangedListener listener) {
249: this .listener = WidgetEventMulticaster.add(this .listener,
250: listener);
251: }
252:
253: public void removeValueChangedListener(ValueChangedListener listener) {
254: this .listener = WidgetEventMulticaster.remove(this .listener,
255: listener);
256: }
257:
258: public boolean hasValueChangedListeners() {
259: return this .listener != null;
260: }
261:
262: public void broadcastEvent(WidgetEvent event) {
263: if (event instanceof ValueChangedEvent) {
264: if (this .listener != null) {
265: this .listener.valueChanged((ValueChangedEvent) event);
266: }
267: } else {
268: // Other kinds of events
269: super .broadcastEvent(event);
270: }
271: }
272:
273: /**
274: * @return "upload"
275: */
276: public String getXMLElementName() {
277: return UPLOAD_EL;
278: }
279:
280: /**
281: * Adds attributes @required, @mime-types
282: */
283: public AttributesImpl getXMLElementAttributes() {
284: AttributesImpl attrs = super .getXMLElementAttributes();
285: attrs.addCDATAAttribute("id", getRequestParameterName());
286: attrs.addCDATAAttribute("required", String
287: .valueOf(this .definition.isRequired()));
288: if (this .definition.getMimeTypes() != null) {
289: attrs.addCDATAAttribute("mime-types", this .definition
290: .getMimeTypes());
291: }
292: return attrs;
293: }
294:
295: public void generateItemSaxFragment(ContentHandler contentHandler,
296: Locale locale) throws SAXException {
297: if (this .part != null) {
298: String name = (String) this .part.getHeaders().get(
299: "filename");
300: contentHandler.startElement(FormsConstants.INSTANCE_NS,
301: VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON
302: + VALUE_EL, XMLUtils.EMPTY_ATTRIBUTES);
303: contentHandler.characters(name.toCharArray(), 0, name
304: .length());
305: contentHandler.endElement(FormsConstants.INSTANCE_NS,
306: VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON
307: + VALUE_EL);
308: }
309:
310: // validation message element: only present if the value is not valid
311: if (this.validationError != null) {
312: contentHandler.startElement(FormsConstants.INSTANCE_NS,
313: VALIDATION_MSG_EL,
314: FormsConstants.INSTANCE_PREFIX_COLON
315: + VALIDATION_MSG_EL,
316: XMLUtils.EMPTY_ATTRIBUTES);
317: this.validationError.generateSaxFragment(contentHandler);
318: contentHandler.endElement(FormsConstants.INSTANCE_NS,
319: VALIDATION_MSG_EL,
320: FormsConstants.INSTANCE_PREFIX_COLON
321: + VALIDATION_MSG_EL);
322: }
323: }
324: }
|