001: // Copyright 2005 Chibacon
002: /*
003: *
004: * Artistic License
005: *
006: * Preamble
007: *
008: * The intent of this document is to state the conditions under which a Package may be copied, such that
009: * the Copyright Holder maintains some semblance of artistic control over the development of the
010: * package, while giving the users of the package the right to use and distribute the Package in a
011: * more-or-less customary fashion, plus the right to make reasonable modifications.
012: *
013: * Definitions:
014: *
015: * "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives
016: * of that collection of files created through textual modification.
017: *
018: * "Standard Version" refers to such a Package if it has not been modified, or has been modified
019: * in accordance with the wishes of the Copyright Holder.
020: *
021: * "Copyright Holder" is whoever is named in the copyright or copyrights for the package.
022: *
023: * "You" is you, if you're thinking about copying or distributing this Package.
024: *
025: * "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication
026: * charges, time of people involved, and so on. (You will not be required to justify it to the
027: * Copyright Holder, but only to the computing community at large as a market that must bear the
028: * fee.)
029: *
030: * "Freely Available" means that no fee is charged for the item itself, though there may be fees
031: * involved in handling the item. It also means that recipients of the item may redistribute it under
032: * the same conditions they received it.
033: *
034: * 1. You may make and give away verbatim copies of the source form of the Standard Version of this
035: * Package without restriction, provided that you duplicate all of the original copyright notices and
036: * associated disclaimers.
037: *
038: * 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain
039: * or from the Copyright Holder. A Package modified in such a way shall still be considered the
040: * Standard Version.
041: *
042: * 3. You may otherwise modify your copy of this Package in any way, provided that you insert a
043: * prominent notice in each changed file stating how and when you changed that file, and provided that
044: * you do at least ONE of the following:
045: *
046: * a) place your modifications in the Public Domain or otherwise make them Freely
047: * Available, such as by posting said modifications to Usenet or an equivalent medium, or
048: * placing the modifications on a major archive site such as ftp.uu.net, or by allowing the
049: * Copyright Holder to include your modifications in the Standard Version of the Package.
050: *
051: * b) use the modified Package only within your corporation or organization.
052: *
053: * c) rename any non-standard executables so the names do not conflict with standard
054: * executables, which must also be provided, and provide a separate manual page for each
055: * non-standard executable that clearly documents how it differs from the Standard
056: * Version.
057: *
058: * d) make other distribution arrangements with the Copyright Holder.
059: *
060: * 4. You may distribute the programs of this Package in object code or executable form, provided that
061: * you do at least ONE of the following:
062: *
063: * a) distribute a Standard Version of the executables and library files, together with
064: * instructions (in the manual page or equivalent) on where to get the Standard Version.
065: *
066: * b) accompany the distribution with the machine-readable source of the Package with
067: * your modifications.
068: *
069: * c) accompany any non-standard executables with their corresponding Standard Version
070: * executables, giving the non-standard executables non-standard names, and clearly
071: * documenting the differences in manual pages (or equivalent), together with instructions
072: * on where to get the Standard Version.
073: *
074: * d) make other distribution arrangements with the Copyright Holder.
075: *
076: * 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge
077: * any fee you choose for support of this Package. You may not charge a fee for this Package itself.
078: * However, you may distribute this Package in aggregate with other (possibly commercial) programs as
079: * part of a larger (possibly commercial) software distribution provided that you do not advertise this
080: * Package as a product of your own.
081: *
082: * 6. The scripts and library files supplied as input to or produced as output from the programs of this
083: * Package do not automatically fall under the copyright of this Package, but belong to whomever
084: * generated them, and may be sold commercially, and may be aggregated with this Package.
085: *
086: * 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of
087: * this Package.
088: *
089: * 8. The name of the Copyright Holder may not be used to endorse or promote products derived from
090: * this software without specific prior written permission.
091: *
092: * 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
093: * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
094: * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
095: *
096: */
097: // Copyright 2005 Chibacon Liss�/Turner GbR
098: package mc.formgenerator.servlets;
099:
100: import mc.formgenerator.servlets.util.FormGeneratorConstant;
101: import org.apache.commons.fileupload.DiskFileUpload;
102: import org.apache.commons.fileupload.FileItem;
103: import org.apache.commons.fileupload.FileUpload;
104: import org.apache.commons.fileupload.FileUploadException;
105: import org.apache.log4j.Category;
106: import org.chiba.adapter.InteractionHandler;
107: import org.chiba.xml.xforms.ChibaBean;
108: import org.chiba.xml.xforms.config.Config;
109: import org.chiba.xml.xforms.events.XFormsEventFactory;
110: import org.chiba.xml.xforms.exception.XFormsException;
111: import org.chiba.xml.xforms.ui.Repeat;
112:
113: import javax.servlet.http.HttpServletRequest;
114: import java.io.File;
115: import java.io.UnsupportedEncodingException;
116: import java.util.*;
117:
118: /**
119: * Default implementation for handling http servlet requests.
120: *
121: * @author joern turner
122: * @version $Id$
123: */
124: public class HttpRequestHandler implements InteractionHandler {
125: private static final Category LOGGER = Category
126: .getInstance(HttpRequestHandler.class);
127: public static final String DATA_PREFIX_PROPERTY = "chiba.web.dataPrefix";
128: public static final String TRIGGER_PREFIX_PROPERTY = "chiba.web.triggerPrefix";
129: public static final String SELECTOR_PREFIX_PROPERTY = "chiba.web.selectorPrefix";
130: public static final String REMOVE_UPLOAD_PREFIX_PROPERTY = "chiba.web.removeUploadPrefix";
131: public static final String DATA_PREFIX_DEFAULT = "d_";
132: public static final String TRIGGER_PREFIX_DEFAULT = "t_";
133: public static final String SELECTOR_PREFIX_DEFAULT = "s_";
134: public static final String REMOVE_UPLOAD_PREFIX_DEFAULT = "ru_";
135:
136: private ChibaBean chibaBean;
137:
138: private String dataPrefix;
139: private String selectorPrefix;
140: private String triggerPrefix;
141: private String removeUploadPrefix;
142: private String uploadRoot;
143:
144: public HttpRequestHandler(ChibaBean chibaBean) {
145: this .chibaBean = chibaBean;
146: }
147:
148: /**
149: * executes this handler.
150: *
151: * @throws XFormsException
152: */
153: public void execute() throws XFormsException {
154: HttpServletRequest request = (HttpServletRequest) this .chibaBean
155: .getContext().get(ServletAdapter.HTTP_SERVLET_REQUEST);
156:
157: String contextRoot = request.getSession().getServletContext()
158: .getRealPath("");
159: if (contextRoot == null) {
160: contextRoot = request.getSession().getServletContext()
161: .getRealPath(".");
162: }
163:
164: String uploadDir = (String) this .chibaBean.getContext().get(
165: ServletAdapter.HTTP_UPLOAD_DIR);
166: this .uploadRoot = new File(contextRoot, uploadDir)
167: .getAbsolutePath();
168:
169: handleRequest(request);
170: }
171:
172: /**
173: * checks whether we have multipart or urlencoded request and processes it accordingly. After updating
174: * the data, a reacalculate, revalidate refresh sequence is fired and the found trigger is executed.
175: *
176: * @param request Servlet request
177: * @throws org.chiba.xml.xforms.exception.XFormsException
178: * todo: implement action block behaviour
179: */
180: protected void handleRequest(HttpServletRequest request)
181: throws XFormsException {
182: String trigger = null;
183:
184: // Check that we have a file upload request
185: boolean isMultipart = FileUpload.isMultipartContent(request);
186: if (LOGGER.isDebugEnabled()) {
187: LOGGER.debug("request isMultipart: " + isMultipart);
188: LOGGER.debug("base URI: " + this .chibaBean.getBaseURI());
189: LOGGER.debug("user agent: "
190: + request.getHeader("User-Agent"));
191: }
192:
193: if (isMultipart) {
194: trigger = processMultiPartRequest(request, trigger);
195: } else {
196: trigger = processUrlencodedRequest(request, trigger);
197: }
198:
199: // finally activate trigger if any
200: if (trigger != null) {
201: if (LOGGER.isDebugEnabled()) {
202: LOGGER.debug("trigger '" + trigger + "'");
203: }
204:
205: this .chibaBean.dispatch(trigger,
206: XFormsEventFactory.DOM_ACTIVATE);
207: }
208: }
209:
210: /**
211: * @param request Servlet request
212: * @param trigger Trigger control
213: * @return the calculated trigger
214: * @throws XFormsException If an error occurs
215: */
216: protected String processMultiPartRequest(
217: HttpServletRequest request, String trigger)
218: throws XFormsException {
219: DiskFileUpload upload = new DiskFileUpload();
220:
221: String encoding = request.getCharacterEncoding();
222: if (encoding == null) {
223: encoding = FormGeneratorConstant.CHARACTER_ENCODING;
224: }
225:
226: upload.setRepositoryPath(this .uploadRoot);
227:
228: if (LOGGER.isDebugEnabled()) {
229: LOGGER.debug("root dir for uploads: " + this .uploadRoot);
230: }
231:
232: List items;
233: try {
234: items = upload.parseRequest(request);
235: } catch (FileUploadException fue) {
236: throw new XFormsException(fue);
237: }
238:
239: Map formFields = new HashMap();
240: Iterator iter = items.iterator();
241: while (iter.hasNext()) {
242: FileItem item = (FileItem) iter.next();
243: String itemName = item.getName();
244: String fieldName = item.getFieldName();
245: String id = fieldName.substring(Config.getInstance()
246: .getProperty("chiba.web.dataPrefix").length());
247:
248: if (LOGGER.isDebugEnabled()) {
249: LOGGER.debug("Multipart item name is: " + itemName
250: + " and fieldname is: " + fieldName
251: + " and id is: " + id);
252: LOGGER.debug("Is formfield: " + item.isFormField());
253: LOGGER.debug("Content: " + item.getString());
254: }
255:
256: if (item.isFormField()) {
257:
258: // check for upload-remove action
259: if (fieldName.startsWith(getRemoveUploadPrefix())) {
260: id = fieldName.substring(getRemoveUploadPrefix()
261: .length());
262: // if data is null, file will be removed ...
263: // TODO: remove the file from the disk as well
264: chibaBean.updateControlValue(id, "", "", null);
265: continue;
266: }
267:
268: // It's a field name, it means that we got a non-file
269: // form field. Upload is not required. We must treat it as we
270: // do in processUrlencodedRequest()
271: processMultipartParam(formFields, fieldName, item,
272: encoding);
273: } else {
274:
275: String uniqueFilename = new File(
276: getUniqueParameterName("file"), new File(
277: itemName).getName()).getPath();
278:
279: File savedFile = new File(this .uploadRoot,
280: uniqueFilename);
281:
282: byte[] data = null;
283:
284: data = processMultiPartFile(item, id, savedFile,
285: encoding, data);
286:
287: // if data is null, file will be removed ...
288: // TODO: remove the file from the disk as well
289: chibaBean.updateControlValue(id, item.getContentType(),
290: itemName, data);
291: }
292:
293: // handle regular fields
294: if (formFields.size() > 0) {
295:
296: Iterator it = formFields.keySet().iterator();
297: while (it.hasNext()) {
298:
299: fieldName = (String) it.next();
300: String[] values = (String[]) formFields
301: .get(fieldName);
302:
303: // [1] handle data
304: handleData(fieldName, values);
305:
306: // [2] handle selector
307: handleSelector(fieldName, values[0]);
308:
309: // [3] handle trigger
310: trigger = handleTrigger(trigger, fieldName);
311: }
312: }
313: }
314:
315: return trigger;
316: }
317:
318: protected String processUrlencodedRequest(
319: HttpServletRequest request, String trigger)
320: throws XFormsException {
321: // iterate request parameters
322: Enumeration names = request.getParameterNames();
323: while (names.hasMoreElements()) {
324: String paramName = names.nextElement().toString();
325: String[] values = request.getParameterValues(paramName);
326:
327: if (LOGGER.isDebugEnabled()) {
328: LOGGER.debug(this + " parameter-name: " + paramName);
329: for (int i = 0; i < values.length; i++) {
330: LOGGER.debug(this + " value: " + values[i]);
331: }
332: }
333:
334: // [1] handle data
335: handleData(paramName, values);
336:
337: // [2] handle selector
338: handleSelector(paramName, values[0]);
339:
340: // [3] handle trigger
341: trigger = handleTrigger(trigger, paramName);
342: }
343: return trigger;
344: }
345:
346: /**
347: * @param name
348: * @throws XFormsException
349: */
350: protected void handleData(String name, String[] values)
351: throws XFormsException {
352: if (name.startsWith(getDataPrefix())) {
353: String id = name.substring(getDataPrefix().length());
354:
355: // assemble new control value
356: String newValue;
357:
358: if (values.length > 1) {
359: StringBuffer buffer = new StringBuffer(values[0]);
360:
361: for (int i = 1; i < values.length; i++) {
362: buffer.append(" ").append(values[i]);
363: }
364:
365: newValue = trim(buffer.toString());
366: } else {
367: newValue = trim(values[0]);
368: }
369:
370: this .chibaBean.updateControlValue(id, newValue);
371: }
372: }
373:
374: /**
375: * patch to handle linefeed duplication in textareas with some browsers.
376: *
377: * @param value the value where linebreaks will be trimmed
378: * @return returns a cleaned up version of the value
379: */
380:
381: protected String trim(String value) {
382: if (value != null && value.length() > 0) {
383: value = value.replaceAll("\r\n", "\r");
384: value = value.trim();
385: }
386: return value;
387: }
388:
389: /**
390: * @param name
391: * @throws XFormsException
392: */
393: protected void handleSelector(String name, String value)
394: throws XFormsException {
395: if (name.startsWith(getSelectorPrefix())) {
396: int separator = value.lastIndexOf(':');
397:
398: String id = value.substring(0, separator);
399: int index = Integer.valueOf(value.substring(separator + 1))
400: .intValue();
401:
402: Repeat repeat = (Repeat) this .chibaBean.lookup(id);
403: repeat.setIndex(index);
404: }
405: }
406:
407: protected String handleTrigger(String trigger, String name) {
408: if ((trigger == null) && name.startsWith(getTriggerPrefix())) {
409: String parameter = name;
410: int x = parameter.lastIndexOf(".x");
411: int y = parameter.lastIndexOf(".y");
412:
413: if (x > -1) {
414: parameter = parameter.substring(0, x);
415: }
416:
417: if (y > -1) {
418: parameter = parameter.substring(0, y);
419: }
420:
421: // keep trigger id
422: trigger = name.substring(getTriggerPrefix().length());
423: }
424: return trigger;
425: }
426:
427: private byte[] processMultiPartFile(FileItem item, String id,
428: File savedFile, String encoding, byte[] data)
429: throws XFormsException {
430: // some data uploaded ...
431: if (item.getSize() > 0) {
432:
433: if (chibaBean.storesExternalData(id)) {
434:
435: // store data to file and create URI
436: try {
437: savedFile.getParentFile().mkdir();
438: item.write(savedFile);
439: } catch (Exception e) {
440: throw new XFormsException(e);
441: }
442: // content is URI in this case
443: try {
444: data = savedFile.toURI().toString().getBytes(
445: encoding);
446: } catch (UnsupportedEncodingException e) {
447: throw new XFormsException(e);
448: }
449:
450: } else {
451: // content is the data
452: data = item.get();
453: }
454: }
455: return data;
456: }
457:
458: private void processMultipartParam(Map formFields,
459: String fieldName, FileItem item, String encoding)
460: throws XFormsException {
461: String values[] = (String[]) formFields.get(fieldName);
462: String formFieldValue = null;
463: try {
464: formFieldValue = item.getString(encoding);
465: } catch (UnsupportedEncodingException e) {
466: throw new XFormsException(e.getMessage(), e);
467: }
468:
469: if (values == null) {
470: formFields.put(fieldName, new String[] { formFieldValue });
471: } else {
472: // not very effective, but not many duplicate values
473: // expected either ...
474: String[] tmp = new String[values.length + 1];
475: System.arraycopy(values, 0, tmp, 0, values.length);
476: tmp[values.length] = formFieldValue;
477: formFields.put(fieldName, tmp);
478: }
479: }
480:
481: /**
482: * returns the prefix which is used to identify trigger parameters.
483: *
484: * @return the prefix which is used to identify trigger parameters
485: */
486: protected final String getTriggerPrefix() {
487: if (this .triggerPrefix == null) {
488: try {
489: this .triggerPrefix = Config.getInstance()
490: .getProperty(TRIGGER_PREFIX_PROPERTY,
491: TRIGGER_PREFIX_DEFAULT);
492: } catch (Exception e) {
493: this .triggerPrefix = TRIGGER_PREFIX_DEFAULT;
494: }
495: }
496:
497: return this .triggerPrefix;
498: }
499:
500: protected final String getDataPrefix() {
501: if (this .dataPrefix == null) {
502: try {
503: this .dataPrefix = Config.getInstance().getProperty(
504: DATA_PREFIX_PROPERTY, DATA_PREFIX_DEFAULT);
505: } catch (Exception e) {
506: this .dataPrefix = DATA_PREFIX_DEFAULT;
507: }
508: }
509:
510: return this .dataPrefix;
511: }
512:
513: protected final String getRemoveUploadPrefix() {
514: if (this .removeUploadPrefix == null) {
515: try {
516: this .removeUploadPrefix = Config.getInstance()
517: .getProperty(REMOVE_UPLOAD_PREFIX_PROPERTY,
518: REMOVE_UPLOAD_PREFIX_DEFAULT);
519: } catch (Exception e) {
520: this .removeUploadPrefix = REMOVE_UPLOAD_PREFIX_DEFAULT;
521: }
522: }
523:
524: return this .removeUploadPrefix;
525: }
526:
527: private String getUniqueParameterName(String prefix) {
528: return prefix
529: + Integer.toHexString((int) (Math.random() * 10000));
530: }
531:
532: /**
533: * returns the configured prefix which identifies 'selector' parameters. These are used to transport
534: * the state of repeat indices via http.
535: *
536: * @return the prefix for selector parameters from the configuration
537: */
538: public final String getSelectorPrefix() {
539: if (this .selectorPrefix == null) {
540: try {
541: this .selectorPrefix = Config.getInstance().getProperty(
542: SELECTOR_PREFIX_PROPERTY,
543: SELECTOR_PREFIX_DEFAULT);
544: } catch (Exception e) {
545: this .selectorPrefix = SELECTOR_PREFIX_DEFAULT;
546: }
547: }
548:
549: return this .selectorPrefix;
550: }
551:
552: /**
553: * Get the value of chibaBean.
554: *
555: * @return the value of chibaBean
556: */
557: public ChibaBean getChibaBean() {
558: return this .chibaBean;
559: }
560:
561: }
562:
563: // end of class
|