org.chiba.web.servlet._HttpRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.chiba.web.servlet._HttpRequestHandler.java

Source

// Copyright 2001-2007 ChibaXForms GmbH
/*
 *
 *    Artistic License
 *
 *    Preamble
 *
 *    The intent of this document is to state the conditions under which a Package may be copied, such that
 *    the Copyright Holder maintains some semblance of artistic control over the development of the
 *    package, while giving the users of the package the right to use and distribute the Package in a
 *    more-or-less customary fashion, plus the right to make reasonable modifications.
 *
 *    Definitions:
 *
 *    "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives
 *    of that collection of files created through textual modification.
 *
 *    "Standard Version" refers to such a Package if it has not been modified, or has been modified
 *    in accordance with the wishes of the Copyright Holder.
 *
 *    "Copyright Holder" is whoever is named in the copyright or copyrights for the package.
 *
 *    "You" is you, if you're thinking about copying or distributing this Package.
 *
 *    "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication
 *    charges, time of people involved, and so on. (You will not be required to justify it to the
 *    Copyright Holder, but only to the computing community at large as a market that must bear the
 *    fee.)
 *
 *    "Freely Available" means that no fee is charged for the item itself, though there may be fees
 *    involved in handling the item. It also means that recipients of the item may redistribute it under
 *    the same conditions they received it.
 *
 *    1. You may make and give away verbatim copies of the source form of the Standard Version of this
 *    Package without restriction, provided that you duplicate all of the original copyright notices and
 *    associated disclaimers.
 *
 *    2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain
 *    or from the Copyright Holder. A Package modified in such a way shall still be considered the
 *    Standard Version.
 *
 *    3. You may otherwise modify your copy of this Package in any way, provided that you insert a
 *    prominent notice in each changed file stating how and when you changed that file, and provided that
 *    you do at least ONE of the following:
 *
 *        a) place your modifications in the Public Domain or otherwise make them Freely
 *        Available, such as by posting said modifications to Usenet or an equivalent medium, or
 *        placing the modifications on a major archive site such as ftp.uu.net, or by allowing the
 *        Copyright Holder to include your modifications in the Standard Version of the Package.
 *
 *        b) use the modified Package only within your corporation or organization.
 *
 *        c) rename any non-standard executables so the names do not conflict with standard
 *        executables, which must also be provided, and provide a separate manual page for each
 *        non-standard executable that clearly documents how it differs from the Standard
 *        Version.
 *
 *        d) make other distribution arrangements with the Copyright Holder.
 *
 *    4. You may distribute the programs of this Package in object code or executable form, provided that
 *    you do at least ONE of the following:
 *
 *        a) distribute a Standard Version of the executables and library files, together with
 *        instructions (in the manual page or equivalent) on where to get the Standard Version.
 *
 *        b) accompany the distribution with the machine-readable source of the Package with
 *        your modifications.
 *
 *        c) accompany any non-standard executables with their corresponding Standard Version
 *        executables, giving the non-standard executables non-standard names, and clearly
 *        documenting the differences in manual pages (or equivalent), together with instructions
 *        on where to get the Standard Version.
 *
 *        d) make other distribution arrangements with the Copyright Holder.
 *
 *    5. You may charge a reasonable copying fee for any distribution of this Package. You may charge
 *    any fee you choose for support of this Package. You may not charge a fee for this Package itself.
 *    However, you may distribute this Package in aggregate with other (possibly commercial) programs as
 *    part of a larger (possibly commercial) software distribution provided that you do not advertise this
 *    Package as a product of your own.
 *
 *    6. The scripts and library files supplied as input to or produced as output from the programs of this
 *    Package do not automatically fall under the copyright of this Package, but belong to whomever
 *    generated them, and may be sold commercially, and may be aggregated with this Package.
 *
 *    7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of
 *    this Package.
 *
 *    8. The name of the Copyright Holder may not be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 *    9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 *    WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 *    MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 */
// Copyright 2005 Chibacon Liss /Turner GbR
package org.chiba.web.servlet;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.chiba.web.session.XFormsSession;
import org.chiba.web.upload.MonitoredDiskFileItemFactory;
import org.chiba.web.upload.UploadInfo;
import org.chiba.web.upload.UploadListener;
import org.chiba.xml.events.DOMEventNames;
import org.chiba.xml.xforms.ChibaBean;
import org.chiba.xml.xforms.config.Config;
import org.chiba.xml.xforms.exception.XFormsException;

/**
 * Default implementation for handling HTTP requests.
 *
 * @author Ulrich Nicolas Lissé
 * @version $Id: _HttpRequestHandler.java,v 1.2 2008/04/24 20:59:22 laddi Exp $
 */
public class _HttpRequestHandler {
    private static final Logger LOGGER = Logger.getLogger(_HttpRequestHandler.class.getName());

    // todo: ioc
    public static final String DATA_PREFIX_PROPERTY = "chiba.web.dataPrefix";
    public static final String TRIGGER_PREFIX_PROPERTY = "chiba.web.triggerPrefix";
    public static final String SELECTOR_PREFIX_PROPERTY = "chiba.web.selectorPrefix";
    public static final String REMOVE_UPLOAD_PREFIX_PROPERTY = "chiba.web.removeUploadPrefix";
    public static final String DAYTIMEDURATION_PREFIX_PROPERTY = "chiba.web.dayTimeDurationPrefix";
    public static final String DATETIME_PREFIX_PROPERTY = "chiba.web.dateTimePrefix";
    public static final String DATA_PREFIX_DEFAULT = "d_";
    public static final String TRIGGER_PREFIX_DEFAULT = "t_";
    public static final String SELECTOR_PREFIX_DEFAULT = "s_";
    public static final String REMOVE_UPLOAD_PREFIX_DEFAULT = "ru_";
    public static final String DAYTIMEDURATION_PREFIX_DEFAULT = "dtd_";
    public static final String DATETIME_PREFIX_DEFAULT = "dt_";
    //    test directory we have to change
    //   public static final String UPLOAD_FILE = "webapp/files/";

    // todo: remove
    private String removeUploadPrefix;

    protected ChibaBean chibaBean;
    private String uploadRoot;//IWMainApplication.getIWMainApplication(FacesContext.getCurrentInstance()).getApplicationRealPath()+"/idegaweb/org.chiba.web.bundle/uploads/";;
    private String sessionKey;
    private String dataPrefix;
    private String selectorPrefix;
    private String triggerPrefix;
    private String dayTimeDurationPrefix;
    private String dateTimePrefix;

    //temporary storage for composite controls
    private HashMap dayTimeDurationValues = new HashMap();
    private HashMap dateTimeValues = new HashMap();
    //protected 

    public _HttpRequestHandler(ChibaBean chibaBean) {
        this.chibaBean = chibaBean;
    }

    public void setUploadRoot(String uploadRoot) {
        this.uploadRoot = uploadRoot;
    }

    public void setSessionKey(String sessionKey) {
        this.sessionKey = sessionKey;
    }

    /**
     * Handles a HTTP request.
     * <p/>
     * After parsing the request will processed in following steps:
     * <ol>
     * <li>Upload controls are updated if any.</li>
     * <li>All other controls are updated if any changes arrive with the request.</li>
     * <li>Repeat indices are updated if any.</li>
     * <li>Triggers are activated if any.</li>
     * </ol>
     * <p/>
     * <b>Note:</b> In case the request is <code>multipart/form-data</code>-encoded,
     * it will be processed with <code>org.apache.commons.fileupload.FileUpload</code>
     * which appears to <i>consume</i> all request parameters.
     *
     * @param request a HTTP request.
     * @throws XFormsException if any error occurred during request processing.
     */
    public void handleRequest(HttpServletRequest request) throws XFormsException {
        LOGGER.info("handle request: " + request.getRequestURI());

        Map[] parameters;
        try {
            parameters = parseRequest(request);
        } catch (Exception e) {
            throw new XFormsException("could not parse request", e);
        }

        // todo: implement action block behaviour ?
        if (parameters[0] != null) {
            processUploadParameters(parameters[0], request);
        }
        if (parameters[1] != null) {
            processControlParameters(parameters[1]);
        }
        if (parameters[2] != null) {
            processRepeatParameters(parameters[2]);
        }
        if (parameters[3] != null) {
            processTriggerParameters(parameters[3]);
        }
    }

    public void handleUpload(HttpServletRequest request) throws XFormsException {
        LOGGER.info("handle request: " + request.getRequestURI());

        Map[] parameters;
        try {
            parameters = parseRequest(request);
        } catch (Exception e) {
            throw new XFormsException("could not parse request", e);
        }
        if (parameters[0] != null) {
            processUploadParameters(parameters[0], request);
        }
    }

    /**
     * Parses a HTTP request. Returns an array containing maps for upload
     * controls, other controls, repeat indices, and trigger. The individual
     * maps may be null in case no corresponding parameters appear in the
     * request.
     *
     * @param request a HTTP request.
     * @return an array of maps containing the parsed request parameters.
     * @throws FileUploadException if an error occurred during file upload.
     * @throws UnsupportedEncodingException if an error occurred during
     * parameter value decoding.
     */
    protected Map[] parseRequest(HttpServletRequest request)
            throws FileUploadException, UnsupportedEncodingException {
        Map[] parameters = new Map[4];

        if (FileUploadBase.isMultipartContent(new ServletRequestContext(request))) {
            UploadListener uploadListener = new UploadListener(request, this.sessionKey);
            DiskFileItemFactory factory = new MonitoredDiskFileItemFactory(uploadListener);
            factory.setRepository(new File(this.uploadRoot));
            ServletFileUpload upload = new ServletFileUpload(factory);

            String encoding = request.getCharacterEncoding();
            if (encoding == null) {
                encoding = "UTF-8";
            }

            Iterator iterator = upload.parseRequest(request).iterator();
            FileItem item;
            while (iterator.hasNext()) {
                item = (FileItem) iterator.next();

                if (item.isFormField()) {
                    LOGGER.info("request param: " + item.getFieldName() + " - value='" + item.getString() + "'");
                } else {
                    LOGGER.info("file in request: " + item.getName());
                }

                parseMultiPartParameter(item, encoding, parameters);
            }
        } else {
            Enumeration enumeration = request.getParameterNames();
            String name;
            String[] values;
            while (enumeration.hasMoreElements()) {
                name = (String) enumeration.nextElement();
                values = request.getParameterValues(name);

                parseURLEncodedParameter(name, values, parameters);
            }
        }

        return parameters;
    }

    /**
     * Parses a <code>application/x-www-form-urlencoded</code>-encoded request
     * parameter and stores it in the parameter map.
     *
     * @param name the paremeter name.
     * @param values the paremeter value(s).
     * @param parameters the parameters map.
     */
    protected void parseURLEncodedParameter(String name, String[] values, Map[] parameters) {
        if (name.startsWith(getDataPrefix()) || name.startsWith(getDayTimeDurationPrefix())
                || name.startsWith(getDateTimePrefix())) {
            StringBuffer buffer = new StringBuffer(values[0]);
            for (int index = 1; index < values.length; index++) {
                buffer.append(" ").append(values[index]);
            }

            parameters[1] = parseControlParameter(name, buffer.toString().trim(), parameters[1]);
        } else if (name.startsWith(getSelectorPrefix())) {
            parameters[2] = parseRepeatParameter(name, values[0], parameters[2]);
        } else if (name.startsWith(getTriggerPrefix())) {
            parameters[3] = parseTriggerParameter(name, values[0], parameters[3]);
        }
    }

    /**
     * Parses a <code>multipart/form-data</code>-encoded request parameter and
     * stores it in the parameter map.
     *
     * @param item the uploaded file item.
     * @param encoding the parameter encoding.
     * @param parameters the parameters map.
     * @throws UnsupportedEncodingException if an error occurred during
     * parameter value decoding.
     */
    protected void parseMultiPartParameter(FileItem item, String encoding, Map[] parameters)
            throws UnsupportedEncodingException {
        String name = item.getFieldName();
        if (name.startsWith(getDataPrefix()) || name.startsWith(getDayTimeDurationPrefix())
                || name.startsWith(getDateTimePrefix())) {
            if (item.isFormField()) {
                parameters[1] = parseControlParameter(name, item.getString(encoding), parameters[1]);
            } else {
                parameters[0] = parseUploadParameter(name, item, parameters[0]);
            }
        } else if (name.startsWith(getSelectorPrefix())) {
            parameters[2] = parseRepeatParameter(name, item.getString(encoding), parameters[2]);
        } else if (name.startsWith(getTriggerPrefix())) {
            parameters[3] = parseTriggerParameter(name, item.getString(encoding), parameters[3]);
        }
    }

    protected Map parseUploadParameter(String name, FileItem item, Map uploads) {
        if (uploads == null) {
            uploads = new HashMap();
        }
        String id = name.substring(getDataPrefix().length());
        uploads.put(id, item);
        return uploads;
    }

    protected Map parseControlParameter(String name, String value, Map controls) {
        if (controls == null) {
            controls = new HashMap();
        }

        String id = null;

        if (name.startsWith(getDayTimeDurationPrefix())) {
            //xdt:dayTimeDuration
            id = name.substring(getDayTimeDurationPrefix().length());
            String part = id.substring(0, id.indexOf('_'));
            id = id.substring(part.length() + 1);

            DayTimeDurationValue dtdValue = (DayTimeDurationValue) dayTimeDurationValues.get(id);

            if (dtdValue == null)
                dtdValue = new DayTimeDurationValue();

            if (part.equals("days")) {
                dtdValue.setDays(value);
            } else if (part.equals("hours")) {
                dtdValue.setHours(value);
            } else if (part.equals("minutes")) {
                dtdValue.setMinutes(value);
            } else if (part.equals("seconds")) {
                dtdValue.setSeconds(value);
            }

            if (dtdValue.isComplete()) {
                value = dtdValue.toString();
                dayTimeDurationValues.remove(id);
            } else {
                dayTimeDurationValues.put(id, dtdValue);
                return controls;
            }
        }

        else if (name.startsWith(getDateTimePrefix())) {
            //xs:date or xs:dateTime bound control
            id = name.substring(getDateTimePrefix().length());
            String part = id.substring(0, id.indexOf('_'));
            id = id.substring(part.length() + 1);

            DateTimeValue dtValue = (DateTimeValue) dateTimeValues.get(id);

            if (dtValue == null)
                dtValue = new DateTimeValue();

            if (part.equals("year")) {
                dtValue.setYear(value);
            } else if (part.equals("month")) {
                dtValue.setMonth(value);
            } else if (part.equals("day")) {
                dtValue.setDay(value);
            } else if (part.equals("hour")) {
                dtValue.setHour(value);
            } else if (part.equals("minute")) {
                dtValue.setMinute(value);
            } else if (part.equals("second")) {
                dtValue.setSecond(value);
            } else if (part.equals("timezone")) {
                dtValue.setTimeZone(value);
            }

            if (dtValue.isComplete()) {
                value = dtValue.toString();
                dateTimeValues.remove(id);
            } else {
                dateTimeValues.put(id, dtValue);
                return controls;
            }
        } else {
            //other bound control
            id = name.substring(getDataPrefix().length());
        }

        //get existing list of values
        String list = (String) controls.get(id);
        if (list == null) {
            //set the value
            list = value;
        } else {
            //add the value to the list
            list = list.concat(" ").concat(value).trim();
        }

        //store the controls updated value
        controls.put(id, list);

        return controls;
    }

    protected Map parseRepeatParameter(String name, String value, Map repeats) {
        if (repeats == null) {
            repeats = new HashMap();
        }

        int separator = value.lastIndexOf(':');
        String id = value.substring(0, separator);
        String index = value.substring(separator + 1);

        repeats.put(id, index);
        return repeats;
    }

    protected Map parseTriggerParameter(String name, String value, Map trigger) {
        if (trigger == null) {
            trigger = new HashMap();
        }

        String id = name.substring(getTriggerPrefix().length());
        int x = id.lastIndexOf(".x");
        if (x > -1) {
            id = id.substring(0, x);
        }
        int y = id.lastIndexOf(".y");
        if (y > -1) {
            id = id.substring(0, y);
        }

        trigger.put(id, DOMEventNames.ACTIVATE);
        return trigger;
    }

    protected void processUploadParameters(Map uploads, HttpServletRequest request) throws XFormsException {
        LOGGER.info("updating " + uploads.keySet().size() + " uploads(s)");

        try {
            // update repeat indices

            Iterator iterator = uploads.keySet().iterator();
            String id;
            FileItem item;
            byte[] data;
            while (iterator.hasNext()) {
                id = (String) iterator.next();
                item = (FileItem) uploads.get(id);

                if (item.getSize() > 0) {
                    if (this.chibaBean.hasControlType(id, "anyURI")) {

                        String localPath = new StringBuffer().append('/').append(id).toString();
                        File localFile = new File(uploadRoot + this.sessionKey, localPath);

                        localFile.getParentFile().mkdirs();
                        item.write(localFile);
                        // todo: externalize file handling and uri generation
                        data = localFile.toURI().toString().getBytes("UTF-8");
                    } else {
                        data = item.get();
                    }

                    this.chibaBean.updateControlValue(id, item.getContentType(), item.getName(), data);

                    // After the value has been set and the RRR took place, create new UploadInfo with status set to 'done'
                    request.getSession().setAttribute(XFormsSession.ADAPTER_PREFIX + sessionKey + "-uploadInfo",
                            new UploadInfo(1, 0, 0, 0, "done"));
                } else {
                    LOGGER.info("ignoring empty upload " + id);
                    // todo: removal ?
                }

                item.delete();
            }

        } catch (Exception e) {
            throw new XFormsException(e);
        }
    }

    protected void processControlParameters(Map controls) throws XFormsException {
        // first filter out all unchanged controls ...
        Iterator iterator = controls.keySet().iterator();
        String id;
        String value;
        int unchanged = 0;
        while (iterator.hasNext()) {
            id = (String) iterator.next();
            value = (String) controls.get(id);

            if (!this.chibaBean.hasControlChanged(id, value)) {
                controls.put(id, null);
                unchanged++;
            }
        }

        int all = controls.keySet().size();
        int changed = all - unchanged;
        if (changed > 0) {
            LOGGER.info("updating " + changed + " of " + all + " control(s)");
        }

        // ... then update changed controls to avoid side-effects
        iterator = controls.keySet().iterator();
        while (iterator.hasNext()) {
            id = (String) iterator.next();
            value = (String) controls.get(id);

            if (value != null) {
                this.chibaBean.updateControlValue(id, value);
            }
        }
    }

    protected void processRepeatParameters(Map repeats) throws XFormsException {
        LOGGER.info("updating " + repeats.keySet().size() + " repeat(s)");

        // update repeat indices
        Iterator iterator = repeats.keySet().iterator();
        String id;
        int index;
        while (iterator.hasNext()) {
            id = (String) iterator.next();
            index = Integer.parseInt((String) repeats.get(id));

            // todo: change detection ?
            this.chibaBean.updateRepeatIndex(id, index);
        }
    }

    protected void processTriggerParameters(Map trigger) throws XFormsException {
        LOGGER.info("activating " + trigger.keySet().size() + " trigger");

        // update repeat indices
        Iterator iterator = trigger.keySet().iterator();
        String id;
        String event;
        while (iterator.hasNext()) {
            id = (String) iterator.next();
            event = (String) trigger.get(id);

            this.chibaBean.dispatch(id, event);
        }
    }

    // todo: remove and introduce setters (ioc)
    protected final String getTriggerPrefix() {
        if (this.triggerPrefix == null) {
            try {
                this.triggerPrefix = Config.getInstance().getProperty(TRIGGER_PREFIX_PROPERTY,
                        TRIGGER_PREFIX_DEFAULT);
            } catch (Exception e) {
                this.triggerPrefix = TRIGGER_PREFIX_DEFAULT;
            }
        }

        return this.triggerPrefix;
    }

    protected final String getDataPrefix() {
        if (this.dataPrefix == null) {
            try {
                this.dataPrefix = Config.getInstance().getProperty(DATA_PREFIX_PROPERTY, DATA_PREFIX_DEFAULT);
            } catch (Exception e) {
                this.dataPrefix = DATA_PREFIX_DEFAULT;
            }
        }

        return this.dataPrefix;
    }

    protected final String getRemoveUploadPrefix() {
        if (this.removeUploadPrefix == null) {
            try {
                this.removeUploadPrefix = Config.getInstance().getProperty(REMOVE_UPLOAD_PREFIX_PROPERTY,
                        REMOVE_UPLOAD_PREFIX_DEFAULT);
            } catch (Exception e) {
                this.removeUploadPrefix = REMOVE_UPLOAD_PREFIX_DEFAULT;
            }
        }

        return this.removeUploadPrefix;
    }

    protected final String getSelectorPrefix() {
        if (this.selectorPrefix == null) {
            try {
                this.selectorPrefix = Config.getInstance().getProperty(SELECTOR_PREFIX_PROPERTY,
                        SELECTOR_PREFIX_DEFAULT);
            } catch (Exception e) {
                this.selectorPrefix = SELECTOR_PREFIX_DEFAULT;
            }
        }

        return this.selectorPrefix;
    }

    protected final String getDayTimeDurationPrefix() {
        if (this.dayTimeDurationPrefix == null) {
            try {
                this.dayTimeDurationPrefix = Config.getInstance().getProperty(DAYTIMEDURATION_PREFIX_PROPERTY,
                        DAYTIMEDURATION_PREFIX_DEFAULT);
            } catch (Exception e) {
                this.dayTimeDurationPrefix = DAYTIMEDURATION_PREFIX_DEFAULT;
            }
        }

        return this.dayTimeDurationPrefix;
    }

    protected final String getDateTimePrefix() {
        if (this.dateTimePrefix == null) {
            try {
                this.dateTimePrefix = Config.getInstance().getProperty(DATETIME_PREFIX_PROPERTY,
                        DATETIME_PREFIX_DEFAULT);
            } catch (Exception e) {
                this.dateTimePrefix = DATETIME_PREFIX_DEFAULT;
            }
        }

        return this.dateTimePrefix;
    }

    protected class DayTimeDurationValue {
        /**
         * xdt:dayTimeDuration looks like P1DT2H30M00S
         * 
         * P = indicates a period
         * D = prefixed by the number of days
         * T = indicates the time portion
         * H = prefixed by the number of hours
         * M = prefixed by the number of minutes
         * S = prefixed by the number of seconds
         */

        private String days = null;
        private String hours = null;
        private String minutes = null;
        private String seconds = null;

        public void setDays(String days) {
            if (days.length() == 1) {
                this.days = '0' + days;
            }
            if (days.length() == 0 || days.length() == 2) {
                this.days = days;
            }
        }

        public void setHours(String hours) {
            if (hours.length() == 1) {
                this.hours = '0' + hours;
            }
            if (hours.length() == 0 || hours.length() == 2) {
                this.hours = hours;
            }
        }

        public void setMinutes(String minutes) {
            if (minutes.length() == 1) {
                this.minutes = '0' + minutes;
            }
            if (minutes.length() == 0 || minutes.length() == 2) {
                this.minutes = minutes;
            }
        }

        public void setSeconds(String seconds) {
            if (seconds.length() == 1) {
                this.seconds = '0' + seconds;
            }
            if (seconds.length() == 0 || seconds.length() == 2) {
                this.seconds = seconds;
            }
        }

        public boolean isComplete() {
            return (days != null && hours != null && minutes != null && seconds != null);
        }

        @Override
        public String toString() {
            if (!isComplete())
                return new String();

            //must be a days component
            String dayTimeDuration = new String("P");

            //is there a days component
            if (days.length() > 0)
                dayTimeDuration += days + 'D';

            //is there a time component
            if (hours.length() > 0 || minutes.length() > 0 || seconds.length() > 0)
                dayTimeDuration += 'T';

            //is there a hours component
            if (hours.length() > 0)
                dayTimeDuration += hours + 'H';

            //is there a minutes component
            if (minutes.length() > 0)
                dayTimeDuration += minutes + 'M';

            //is there a seconds component
            if (seconds.length() > 0)
                dayTimeDuration += seconds + 'S';

            return dayTimeDuration;
        }
    }

    protected class DateTimeValue {
        /**
         * xs:dateTime looks like 2006-09-19T10:56:00.00+1:00 or YYYY-MM-DDTHH:mm:ss.ms+tz
         * 
         * YYYY = the year, string index 0 to 3
         * MM = the month, string index 5 to 6
         * DD = the day, string index 8 to 9
         * HH = the hour, string index 11 to 12
         * mm = the minute, string index 14 to 15
         * ss = the second, string index 17 to 18
         * ms = the milliseconds
         * tz = the timezone if any
         */

        private String year = new String();
        private String month = new String();
        private String day = new String();
        private String hour = new String();
        private String minute = new String();
        private String second = new String();
        private String millisecond = "000";
        private String timezone = new String();

        public void setYear(String year) {
            if (year.length() == 2) {
                this.year = "20" + year;
            }
            if (year.length() == 4) {
                this.year = year;
            }
        }

        public void setMonth(String month) {
            if (month.length() == 1) {
                this.month = '0' + month;
            }
            if (month.length() == 2) {
                this.month = month;
            }
        }

        public void setDay(String day) {
            if (day.length() == 1) {
                this.day = '0' + day;
            }
            if (day.length() == 2) {
                this.day = day;
            }
        }

        public void setHour(String hour) {
            if (hour.length() == 1) {
                this.hour = '0' + hour;
            }
            if (hour.length() == 2) {
                this.hour = hour;
            }
        }

        public void setMinute(String minute) {
            if (minute.length() == 1) {
                this.minute = '0' + minute;
            }
            if (minute.length() == 2) {
                this.minute = minute;
            }
        }

        public void setSecond(String second) {
            if (second.length() == 1) {
                this.second = '0' + second;
            }
            if (second.length() == 2) {
                this.second = second;
            }
        }

        public void setTimeZone(String timezone) {
            if (timezone.length() == 5) {
                this.timezone = timezone.substring(0, 1) + '0' + timezone.substring(1);
            }
            if (timezone.length() == 6) {
                this.timezone = timezone;
            }
        }

        private boolean isCompleteDate() {
            return (year.length() == 4 && month.length() == 2 && day.length() == 2);
        }

        private boolean isCompleteDateTime() {
            return isCompleteDate() && (hour.length() == 2 && minute.length() == 2 && second.length() == 2
                    && millisecond.length() == 3 && timezone.length() == 6);
        }

        public boolean isComplete() {
            return (isCompleteDate() || isCompleteDateTime());
        }

        private String toDateString() {
            return new String(year + "-" + month + "-" + day);
        }

        private String toDateTimeString() {
            return new String(
                    toDateString() + "T" + hour + ":" + minute + ":" + second + "." + millisecond + timezone);
        }

        @Override
        public String toString() {
            if (isCompleteDateTime())
                return toDateTimeString();

            if (isCompleteDate())
                return toDateString();

            return new String();
        }
    }

}

// end of class