com.bluexml.xforms.controller.mapping.MappingToolCommon.java Source code

Java tutorial

Introduction

Here is the source code for com.bluexml.xforms.controller.mapping.MappingToolCommon.java

Source

/*
Copyright (C) 2007-2011  BlueXML - www.bluexml.com
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/

package com.bluexml.xforms.controller.mapping;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.bluexml.side.form.utils.DOMUtil;
import com.bluexml.xforms.actions.EnumAction;
import com.bluexml.xforms.controller.alfresco.AlfrescoController;
import com.bluexml.xforms.controller.alfresco.AlfrescoTransaction;
import com.bluexml.xforms.controller.beans.FileUploadInfoBean;
import com.bluexml.xforms.controller.beans.PersistFormResultBean;
import com.bluexml.xforms.controller.binding.ActionFieldType;
import com.bluexml.xforms.controller.binding.AspectType;
import com.bluexml.xforms.controller.binding.AssociationType;
import com.bluexml.xforms.controller.binding.AttributeType;
import com.bluexml.xforms.controller.binding.Batch;
import com.bluexml.xforms.controller.binding.CanisterType;
import com.bluexml.xforms.controller.binding.ClassType;
import com.bluexml.xforms.controller.binding.EnumType;
import com.bluexml.xforms.controller.binding.FieldType;
import com.bluexml.xforms.controller.binding.FileFieldType;
import com.bluexml.xforms.controller.binding.FormFieldType;
import com.bluexml.xforms.controller.binding.FormType;
import com.bluexml.xforms.controller.binding.GenericAttribute;
import com.bluexml.xforms.controller.binding.GenericClass;
import com.bluexml.xforms.controller.binding.Mapping;
import com.bluexml.xforms.controller.binding.ModelChoiceType;
import com.bluexml.xforms.controller.binding.ObjectFactory;
import com.bluexml.xforms.controller.binding.SearchFormType;
import com.bluexml.xforms.controller.binding.ValueType;
import com.bluexml.xforms.controller.binding.WorkflowTaskType;
import com.bluexml.xforms.controller.navigation.FormTypeEnum;
import com.bluexml.xforms.messages.MsgId;
import com.bluexml.xforms.messages.MsgPool;

/**
 * The Class MappingToolCommon.
 */
public class MappingToolCommon {

    private static final String TRACE_LOGGER_CATEGORY = "com.bluexml.xforms.controller.mapping.trace";

    /**
     * The separator used by the webscript for concatenating pieces of info in
     * the same string
     */
    protected final String WEBSCRIPT_SEPARATOR = "{::}";

    protected final int WEBSCRIPT_SEPARATOR_LENGTH = WEBSCRIPT_SEPARATOR.length();

    /** The document builder. */
    protected static DocumentBuilder documentBuilder;

    /** The document transformer. */
    protected static Transformer documentTransformer;

    /** The alfresco object factory. */
    protected static ObjectFactory alfrescoObjectFactory = new ObjectFactory();

    /** The logger. */
    protected static Log logger = LogFactory.getLog(MappingToolCommon.class);

    /** Trace logging detector. DO NO OUTPUT LOGS USING THIS. */
    protected static Log loggertrace = LogFactory.getLog(TRACE_LOGGER_CATEGORY);

    /** The number types. */
    protected static List<String> numberTypes = Arrays.asList("byte", "double", "float", "int", "long", "short");

    /** The mapping. */
    protected Mapping mapping;

    /** The controller. */
    protected AlfrescoController controller;

    /** The alfresco unmarshaller. */
    private static Unmarshaller alfrescoUnmarshaller;

    /** The alfresco marshaller. */
    private static Marshaller alfrescoMarshaller;
    static {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance("com.bluexml.xforms.controller.binding");
            alfrescoMarshaller = jaxbContext.createMarshaller();
            alfrescoMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            alfrescoMarshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
            alfrescoUnmarshaller = jaxbContext.createUnmarshaller();

            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            documentTransformer = TransformerFactory.newInstance().newTransformer();
            documentTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Marshal.
     * 
     * @param alfrescoClass
     *            the alfresco class
     * @param os
     *            the os
     * @throws JAXBException
     *             the JAXB exception
     */
    protected static synchronized void marshal(GenericClass alfrescoClass, OutputStream os) throws JAXBException {
        alfrescoMarshaller.marshal(alfrescoObjectFactory.createClass(alfrescoClass), os);
    }

    private static synchronized void marshal(Batch batch, ByteArrayOutputStream os) throws JAXBException {
        alfrescoMarshaller.marshal(batch, os);
    }

    /**
     * Marshal.
     * 
     * @param alfrescoClass
     *            the alfresco class
     * @return the string
     * @throws ServletException
     *             the alfresco controller exception
     */
    @SuppressWarnings("unused")
    @Deprecated
    private static String marshal(GenericClass alfrescoClass) throws ServletException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            marshal(alfrescoClass, os);
        } catch (JAXBException e) {
            throw new ServletException(e);
        }
        String datas = "";
        try {
            datas = os.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new ServletException(e);
        }
        return datas;
    }

    /**
     * Marshalls the batch operations into a string.
     * 
     * @param batch
     * @return
     * @throws ServletException
     */
    public static String marshal(Batch batch) throws ServletException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            marshal(batch, os);
        } catch (JAXBException e) {
            throw new ServletException(e);
        }
        String datas = "";
        try {
            datas = os.toString("UTF-8"); // #1295
        } catch (UnsupportedEncodingException e) {
            String message = "UTF-8 encoding is unsupported.";
            logger.error(message, e);
            throw new ServletException(message);
        }
        return datas;
    }

    /**
     * Unmarshal.
     * 
     * @param alfrescoNode
     *            the alfresco node
     * @return the com.bluexml.xforms.controller.alfresco.binding. class
     * @throws JAXBException
     *             the JAXB exception
     */
    public static synchronized GenericClass unmarshal(Document alfrescoNode) throws JAXBException {
        JAXBElement<GenericClass> alfrescoClass = alfrescoUnmarshaller.unmarshal(alfrescoNode, GenericClass.class);
        return alfrescoClass.getValue();
    }

    public static String packageConcate(String parent, String name) {
        if (parent.equals("")) {
            return name;
        }
        return parent + "." + name;
    }

    class TransformInfoBean {
        public String result;
        public int positionWhereStopped; // set only in the Date transform. -1 otherwise

        public TransformInfoBean(String value, int position) {
            this.result = value;
            this.positionWhereStopped = position;
        }
    }

    /**
     * Instantiates a new mapping tool common.
     * 
     * @param mapping
     *            the mapping
     * @param controller
     *            the controller
     */
    public MappingToolCommon(Mapping mapping, AlfrescoController controller) {
        super();
        this.mapping = mapping;
        this.controller = controller;
    }

    /**
     * Class type to string.
     * 
     * @param classType
     *            the class type
     * @return the string
     */
    protected String classTypeToString(ClassType classType) {
        return packageConcate(classType.getPackage(), classType.getName());
    }

    /**
     * Gets the aspect type.
     * 
     * @param aspectDecl
     *            the aspect decl
     * @return the aspect type
     */
    public AspectType getAspectType(AspectType aspectDecl) {
        List<AspectType> aspects = mapping.getAspect();
        for (AspectType aspectType : aspects) {
            if (aspectDecl.getPackage().equals(aspectType.getPackage())
                    && aspectDecl.getName().equals(aspectType.getName())) {
                return aspectType;
            }
        }
        System.err.println("Aspect Not found :" + aspectDecl);
        return null;
    }

    public EnumType getEnumType(String type) {
        List<EnumType> enums = mapping.getEnum();
        for (EnumType enumType : enums) {
            if (type.equals(enumTypeToString(enumType))) {
                return enumType;
            }
        }
        return null;
    }

    private String enumTypeToString(EnumType enumType) {
        return packageConcate(enumType.getPackage(), enumType.getName());
    }

    /**
     * Gets the class type.
     * 
     * @param type
     *            the type
     * @return the class type
     */
    protected ClassType getClassType(ClassType type) {
        List<ClassType> clazz = mapping.getClazz();
        for (ClassType classType : clazz) {
            if (type.getPackage().equals(classType.getPackage()) && type.getName().equals(classType.getName())) {
                return classType;
            }
        }
        return null;
    }

    /**
     * Gets the class type.
     * 
     * @param type
     *            the type, under the form package + "." + name
     * @return the class type
     */
    public ClassType getClassType(String type) {
        List<ClassType> clazz = mapping.getClazz();
        for (ClassType classType : clazz) {
            String refType = classTypeToString(classType);
            if (type.equals(refType)) {
                return classType;
            }
        }
        return null;
    }

    /**
     * Gets the complete name of a class type.
     * 
     * @param classType
     *            the type
     * @return the complete name of the type, under the form package + "." +
     *         name
     */
    public String getClassTypeCompleteName(ClassType classType) {
        return classTypeToString(classType);
    }

    /**
     * Gets the id of a form that supports the given data type.
     * 
     * @param dataType
     *            a node type as returned by Alfresco
     * @return the class type, under the form [package].[class name]
     */
    public String getDefaultFormForDatatype(String dataType) {
        List<ClassType> clazz = mapping.getClazz();
        for (ClassType classType : clazz) {
            if (classType.getAlfrescoName().equals(dataType)) {
                return classType.getPackage() + "." + classType.getName();
            }
        }
        return null;
    }

    /**
     * Gets the mapping entry for a model form with the specified type (search,
     * form or workflow).
     * 
     * @param formName
     * @param formTypeEnum
     * @return the mapping entry
     */
    private CanisterType getCanisterType(String formName, FormTypeEnum formTypeEnum) {
        CanisterType formType = null;
        if (formTypeEnum.equals(FormTypeEnum.FORM)) {
            formType = getFormType(formName);
        }
        if (formTypeEnum.equals(FormTypeEnum.SEARCH)) {
            formType = getSearchFormType(formName);
        }
        if (formTypeEnum.equals(FormTypeEnum.WKFLW)) {
            formType = getWorkflowTaskType(formName, true);
        }
        return formType;
    }

    /**
     * Gets the form type that matches the given name.
     * 
     * @param formName
     *            the form name
     * @return the form type
     */
    public FormType getFormType(String formName) {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof FormType) {
                FormType formType = (FormType) element.getValue();
                if (formType.getName().equals(formName)) {
                    return formType;
                }
            }
        }
        return null;
    }

    /**
     * Gets the name of the form type that supports the given data type (i.e.
     * whose real class's
     * name matches the data type).
     * 
     * @param formName
     *            the form name
     * @return the form type
     */
    public String getCustomFormForDatatype(String dataType) {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof FormType) {
                FormType formType = (FormType) element.getValue();
                if (formType.getRealClass().getAlfrescoName().equals(dataType)) {
                    return formType.getName();
                }
            }
        }
        return null;
    }

    /**
     * Gets the search form type that matches the given name.
     * 
     * @param formName
     *            the form name
     * @return the search form type
     */
    public SearchFormType getSearchFormType(String formName) {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof SearchFormType) {
                SearchFormType formType = (SearchFormType) element.getValue();
                if (formType.getName().equals(formName)) {
                    return formType;
                }
            }
        }
        return null;
    }

    /**
     * Gets (from the mapping) the WorkflowTaskType object that matches the
     * name.
     * 
     * @param refName
     *            the name to test against, either a task id (e.g.
     *            "wfbxwfTest:Start") or a form
     *            name (e.g. "wfTest_Start")
     * @param byId
     *            if true, the task id from mapping.xml is tested. Otherwise,
     *            the form name is
     *            tested.
     * @return the form type that matches
     */
    public WorkflowTaskType getWorkflowTaskType(String refName, boolean byId) {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        String testValue;
        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof WorkflowTaskType) {
                WorkflowTaskType taskType = (WorkflowTaskType) element.getValue();
                if (byId) {
                    testValue = taskType.getTaskId();
                } else {
                    testValue = taskType.getName();
                }
                if (testValue.equals(refName)) {
                    return taskType;
                } else {
                    // test
                    logger.trace("MappingToolCommon.getWorkflowTaskType() : no match : RefName = " + refName
                            + ", testValue = " + testValue);
                }
            }
        }
        return null;
    }

    /**
     * Gets (from the mapping) the WorkflowTaskType object that contains the
     * field whose
     * 'uniqueName' matches the given field name.
     * 
     * @param fieldName
     * @return
     */
    public WorkflowTaskType getWorkflowTaskTypeWithField(String fieldName) {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof WorkflowTaskType) {
                WorkflowTaskType taskType = (WorkflowTaskType) element.getValue();
                List<FormFieldType> fields = taskType.getField();
                for (FormFieldType field : fields) {
                    if (StringUtils.equals(field.getUniqueName(), fieldName)) {
                        return taskType;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Gets (from the mapping) the name of the form that is the start task for
     * the given definition
     * name.<br/>
     * e.g. "wfTest" returns "wfTest_Start" knowing the start task is named
     * "Start".
     * 
     * @param namespacePrefix
     *            the name to test against
     * @return the name of the start task
     */
    public String getWorkflowStartTaskFormName(String namespacePrefix) {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof WorkflowTaskType) {
                WorkflowTaskType taskType = (WorkflowTaskType) element.getValue();
                if (isStartTask(taskType)) {
                    String formName = taskType.getName();
                    if (formName.startsWith(namespacePrefix + "_")) {
                        return formName;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Gets the Alfresco name of the form field whose 'uniqueName' matches the
     * given field name in
     * the task form.
     * 
     * @param formName
     *            the id of the workflow form
     * @param fieldName
     *            the field name
     * @return the Alfresco name of the form field
     */
    public String getWorkflowFieldAlfrescoName(String formName, String fieldName) {
        List<FormFieldType> fields = null;
        WorkflowTaskType taskType = getWorkflowTaskType(formName, false);
        fields = taskType.getField();

        if (fields != null) {
            for (FormFieldType formFieldType : fields) {
                if (formFieldType.getUniqueName().equals(fieldName)) {
                    return formFieldType.getAlfrescoName();
                }
            }
        }
        return null;
    }

    /**
     * Gets the class type q name.
     * 
     * @param qNameType
     *            the q name type
     * @return the class type q name
     */
    protected ClassType getClassTypeAlfrescoName(String qNameType) {
        List<ClassType> clazz = mapping.getClazz();
        for (ClassType classType : clazz) {
            if (qNameType.equals(classType.getAlfrescoName())) {
                return classType;
            }
        }
        return null;
    }

    /**
     * Gets the parent class types.
     * 
     * @param classType
     *            the class type
     * @return the parent class types
     */
    protected List<ClassType> getParentClassTypes(ClassType classType) {
        List<ClassType> result = new ArrayList<ClassType>();
        recursiveGetParentClassTypes(classType, result);
        return result;
    }

    /**
     * Gets the id.
     * 
     * @param node
     *            the node
     * @return the id
     */
    public String getId(Node node) {
        Element idElement = DOMUtil.getChild(node, MsgId.INT_INSTANCE_SIDEID.getText());
        String id = null;
        if (idElement != null) {
            id = StringUtils.trimToNull(idElement.getTextContent());

        }
        return id;
    }

    protected void addId(Document xformsDocument, Node element, String alfrescoId) {
        if (alfrescoId != null) {
            Element idElement = xformsDocument.createElement(MsgId.INT_INSTANCE_SIDEID.getText());
            idElement.setTextContent(alfrescoId);
            element.appendChild(idElement);
        }
    }

    /**
     * Gets the label.
     * 
     * @param node
     *            the node
     * @return the label
     */
    public String getLabel(Node node) {
        Element idElement = DOMUtil.getChild(node, MsgId.INT_INSTANCE_SIDELABEL.getText());
        String id = null;
        if (idElement != null) {
            id = StringUtils.trimToNull(idElement.getTextContent());

        }
        return id;
    }

    /**
     * Log xml.
     * 
     * @param node
     *            the node
     * @param messages
     *            the messages
     */
    public void logXML(Node node, String... messages) {
        if (logger.isDebugEnabled()) {
            for (String message : messages) {
                logger.debug(message);
            }
            if (node != null) {
                Source xmlSource = new DOMSource(node);
                StringWriter sw = new StringWriter();
                Result outputTarget = new StreamResult(sw);
                try {
                    documentTransformer.transform(xmlSource, outputTarget);
                } catch (TransformerException e) {
                    logger.error(e);
                }
                logger.debug(sw.toString());
            }
        }
    }

    /**
     * Recursive get parent class types.
     * 
     * @param classType
     *            the class type
     * @param result
     *            the result
     */
    protected void recursiveGetParentClassTypes(ClassType classType, List<ClassType> result) {
        ClassType realClassType = getClassType(classType);
        if (!result.contains(realClassType)) {
            result.add(realClassType);
            ClassType parentClass = realClassType.getParentClass();
            if (parentClass != null) {
                recursiveGetParentClassTypes(parentClass, result);
            }
        }
    }

    /**
     * Safe map get.
     * 
     * @param map
     *            the map
     * @param key
     *            the key
     * @return the string
     */
    protected String safeMapGet(Map<String, String> map, String key) {
        if ((map != null) && (key != null)) {
            return map.get(key);
        }
        return null;
    }

    /**
     * Gets a submap, i.e. the map of entries whose keys start with the given
     * prefix.
     * 
     * @param map
     *            the initial map
     * @param start
     *            the key prefix
     * @return the map< string, string>
     */
    protected Map<String, String> subMap(Map<String, String> map, String start) {
        Map<String, String> result = new TreeMap<String, String>();
        if (map != null) {
            Set<Entry<String, String>> entrySet = map.entrySet();
            for (Entry<String, String> entry : entrySet) {
                String key = entry.getKey();
                if (key.startsWith(start + ".")) {
                    result.put(key.substring(start.length() + 1), entry.getValue());
                }
            }
        }
        return result;
    }

    /**
     * Convert attribute values returned by Alfresco to their XForms equivalent.
     * NOTE:
     * <b>enumerations have a specific processing.</b> Used when building a form
     * from an existing
     * repository object.
     * 
     * @param textContent
     *            the text content
     * @param xformsAttribute
     *            the xforms attribute
     * @param initParams
     * @return the string
     */
    protected String convertAlfrescoAttributeToXforms(String textContent, String xformsAttribute,
            String staticEnumType, Map<String, String> initParams) {
        if (xformsAttribute.equalsIgnoreCase("DateTime")) {
            return DateTimeConverter.convert_AlfrescoToXForms_DateTime(textContent);
        }
        if (xformsAttribute.equalsIgnoreCase("Date")) {
            String localTimeZone = createXFormsInitialValue("Time", null, null, null).substring(12);
            return DateTimeConverter.convert_AlfrescoToXForms_Date(textContent, localTimeZone);
        }
        if (xformsAttribute.equalsIgnoreCase("Time")) {
            return DateTimeConverter.convert_AlfrescoToXForms_Time(textContent);
        }
        if (xformsAttribute.equalsIgnoreCase("double") || xformsAttribute.equalsIgnoreCase("float")) {
            return textContent.replace(',', '.');
        }

        String realTextContent = textContent;
        if (StringUtils.trimToNull(staticEnumType) != null) {
            realTextContent = StringUtils
                    .trimToEmpty(EnumAction.getEnumKey(staticEnumType, textContent, initParams));
        }
        return realTextContent;
    }

    protected String convertAlfrescoAttributeToXforms(List<ValueType> value, String type, String staticEnumType,
            Map<String, String> initParams) {
        StringBuffer result = new StringBuffer();
        boolean first = true;
        for (ValueType valueType : value) {
            if (!first) {
                result.append(" ");
            }
            result.append(convertAlfrescoAttributeToXforms(valueType.getValue(), type, staticEnumType, initParams));
            first = false;
        }
        return result.toString();
    }

    /**
     * Convert xforms attribute to alfresco.
     * 
     * @param textContent
     *            the text content
     * @param type
     *            the type
     * @param isMassTagging
     * @return the string
     */
    protected String convertXformsAttributeToAlfresco(String textContent, String type, String staticEnumType,
            Map<String, String> initParams, boolean isMassTagging) {
        if (isMassTagging && StringUtils.trimToNull(textContent) == null) {
            return null;
        }
        if (type.equalsIgnoreCase("DateTime")) {
            return DateTimeConverter.convert_XFormsToAlfresco_DateTime(textContent);
        }
        if (type.equalsIgnoreCase("Date")) {
            return DateTimeConverter.convert_XFormsToAlfresco_Date(textContent);
        }
        if (type.equalsIgnoreCase("Time")) {
            return DateTimeConverter.convert_XFormsToAlfresco_Time(textContent);
        }
        if (type.equalsIgnoreCase("double") || type.equalsIgnoreCase("float")) {
            return textContent.replace(',', '.');
        }
        if (StringUtils.trimToNull(staticEnumType) != null) {
            String res = StringUtils.trimToEmpty(EnumAction.getEnumValue(staticEnumType, textContent, initParams));
            if (res == null) {
                if (logger.isErrorEnabled()) {
                    logger.error("The value '" + textContent + "' is not a valid for enumeration type '"
                            + staticEnumType + "'; will be overriden");
                }
                res = "1"; // inspired by #941: always initialize enums
            }
            return res;
        }
        return textContent;
    }

    /**
     * Converts the values of a field (with the 'multiple' property set to true)
     * for transmission to
     * the data layer.
     * 
     * @param attribute
     *            the storage for the converted values
     * @param root
     *            the node from the form instance that stores the values
     * @param type
     *            the data type of the values to convert
     * @param staticEnumType
     *            the enumeration type, <code>null</code> if no enumeration
     *            applies
     * @param isMassTagging
     * @return <code>true</code> if at least one value was found and converted
     */
    protected boolean convertXformsMultipleAttributeToAlfresco(GenericAttribute attribute, String textContent,
            Element root, String type, String staticEnumType, Map<String, String> initParams,
            boolean isMassTagging) {

        boolean result = false;
        List<String> rawValuesList = new ArrayList<String>();
        if (staticEnumType == null) {
            // ** #1420
            List<Element> itemsList = DOMUtil.getChildren(root, MsgId.INT_INSTANCE_INPUT_MULT_ITEM.getText());
            if (itemsList.size() == 0) { // should never happen if the instance is correctly served
                return false;
            }
            for (Element anItem : itemsList) {
                String rawValue = StringUtils.trimToNull(anItem.getTextContent());
                if (rawValue != null) {
                    rawValuesList.add(rawValue);
                }
            }
            // ** #1420
        } else {
            String[] values = textContent.split(" ");
            rawValuesList.addAll(Arrays.asList(values));
        }
        for (String rawValue : rawValuesList) {
            String value = convertXformsAttributeToAlfresco(rawValue, type, staticEnumType, initParams,
                    isMassTagging);
            if (StringUtils.trimToNull(value) != null) {
                result = true;
                ValueType valueType = alfrescoObjectFactory.createValueType();
                valueType.setValue(value);
                attribute.getValue().add(valueType);
            }
        }
        return result;
    }

    /**
     * Return an initial value for a form field, depending on the data type.<br/>
     * For instance, <b>boolean</b> is initialized to <b>false</b>, <b>Date</b>
     * is initialized to
     * the system date, etc. If the field is a static enumeration, returns the
     * key that corresponds
     * to the initial value.
     * 
     * @param attributeType
     *            the attribute type
     * @param candidateValue
     *            the default value, if any, that will be returned. May be
     *            <b>null</b>
     * @param enumType
     *            the name of the static enumeration the candidate value comes
     *            from
     * @param initParams
     * @return the value, as a string
     */
    protected String createXFormsInitialValue(String attributeType, String candidateValue, String enumType,
            Map<String, String> initParams) {
        if (attributeType.equalsIgnoreCase("DateTime")) {
            return DateTimeConverter.convert_AlfrescoToXForms_DateTime(new Date().getTime());
        }
        if (attributeType.equalsIgnoreCase("Date")) {
            return DateTimeConverter.convert_AlfrescoToXForms_Date(new Date().getTime());
        }
        if (attributeType.equalsIgnoreCase("Time")) {
            return DateTimeConverter.convert_AlfrescoToXForms_Time(new Date().getTime());
        }

        if (attributeType.equalsIgnoreCase("boolean")) {
            return "false";
        }
        if (numberTypes.indexOf(attributeType) != -1) {
            return "0";
        }
        String resValue = candidateValue;
        if (StringUtils.trimToNull(enumType) != null) {
            resValue = StringUtils.trimToNull(EnumAction.getEnumKey(enumType, candidateValue, initParams));
            if (resValue == null) { // fix for #941: always initialize enums
                resValue = "1";
            }
        }
        return (resValue == null) ? "" : resValue;
    }

    /**
     * Creates an item for inputs that support multiple values, setting the item
     * value.
     * 
     * @param formInstance
     * @param value
     * @throws DOMException
     * @return the DOM node for the item
     */
    protected Element getInputMultipleItemWithValue(Document formInstance, String value) throws DOMException {
        Element item = formInstance.createElement(MsgId.INT_INSTANCE_INPUT_MULT_ITEM.getText());
        Element valueElt = formInstance.createElement(MsgId.INT_INSTANCE_INPUT_MULT_VALUE.getText());
        valueElt.setTextContent(value);

        item.appendChild(valueElt);
        return item;
    }

    /*
     * HELPER FUNCTIONS DUE TO MOVING SOME INFO FROM ELEMENTS TO ATTRIBUTES
     */

    /*
     * ActionFieldType
     */
    protected boolean isInWorkflow(ActionFieldType actionType) {
        if (actionType.isInWorkflow() == null) {
            return false;
        }
        return actionType.isInWorkflow();
    }

    /*
     * AssociationType
     */
    protected boolean isInline(AssociationType associationType) {
        if (associationType.isInline() == null) {
            return false;
        }
        return associationType.isInline();
    }

    protected boolean isMultiple(AssociationType associationType) {
        if (associationType.isMultiple() == null) {
            return false;
        }
        return associationType.isMultiple();
    }

    /*
     * AttributeType
     */
    protected String getDefault(AttributeType attributeType) {
        try {
            return attributeType.getDefault();
        } catch (Exception e) {
            return null;
        }
    }

    protected String getFieldSize(AttributeType attributeType) {
        try {
            return attributeType.getFieldSize();
        } catch (Exception e) {
            return null;
        }
    }

    protected boolean isDynamicEnum(AttributeType attributeType) {
        if (attributeType.isDynamicEnum() == null) {
            return false;
        }
        return attributeType.isDynamicEnum();
    }

    protected boolean isMultiple(AttributeType attributeType) {
        if (attributeType.isMultiple() == null) {
            return false;
        }
        return attributeType.isMultiple();
    }

    protected boolean isReadOnly(AttributeType attributeType) {
        if (attributeType.isReadOnly() == null) {
            return false;
        }
        return attributeType.isReadOnly();
    }

    /*
     * ClassType
     */
    public boolean isRendered(ClassType classType) { // public for MappingAgent
        if (classType.isRendered() == null) {
            return false;
        }
        return classType.isRendered();
    }

    /*
     * EnumType
     */
    public boolean isDynamic(EnumType enumType) { // public for MappingAgent
        if (enumType.isDynamic() == null) {
            return false;
        }
        return enumType.isDynamic();
    }

    /*
     * FieldType
     */
    protected boolean isMandatory(FieldType fieldType) {
        if (fieldType.isMandatory() == null) {
            return false;
        }
        return fieldType.isMandatory();
    }

    /**
     * Gets the value of the extension key for a given field.
     * 
     * @param fieldType
     *            the mapping description of the field
     * @param keyStr
     *            the key to look for using a case-insensitive comparison
     * @return the value
     */
    private String getXtension(FieldType fieldType, String keyStr) {
        String xtension = fieldType.getXtension();
        if (xtension == null) {
            return null;
        }
        return getXtension(xtension, keyStr);
    }

    /**
     * Gets the value of the extension key for a given field.
     * 
     * @param form
     *            the mapping description of a form. Non-<code>null</code>.
     * @param keyStr
     *            the key to look for using a case-insensitive comparison. Non-
     *            <code>null</code>.
     * @return the value of the key
     */
    private String getXtension(CanisterType form, String keyStr) {
        String xtension = form.getXtension();
        if (xtension == null) {
            return null;
        }
        return getXtension(xtension, keyStr);
    }

    /**
     * Gets the value of a key from the value of an Xtension attribute. The
     * format of the xtension
     * string is not checked for correction.
     * 
     * @param xtension
     * @param keyStr
     *            the key, case-insensitive
     * @return
     */
    private String getXtension(String xtension, String keyStr) {
        String[] splitted = StringUtils.split(xtension, ',');
        for (String info : splitted) {
            int pos = info.indexOf('=');
            if (pos != -1) {
                String key = info.substring(0, pos);
                if (key.equalsIgnoreCase(keyStr)) {
                    String value = info.substring(pos + 1);
                    return value;
                }
            }
        }
        return null;
    }

    protected String getXtensionFormat(FieldType fieldType) {
        return getXtension(fieldType, MsgId.MODEL_XTENSION_FORMAT.getText());
    }

    protected String getXtensionIdentifier(FieldType fieldType) {
        return getXtension(fieldType, MsgId.MODEL_XTENSION_IDENTIFIER.getText());
    }

    protected String getXtensionDataType(FieldType fieldType) {
        return getXtension(fieldType, MsgId.MODEL_XTENSION_DATATYPE.getText());
    }

    protected String getXtensionLabelLength(FieldType fieldType) {
        return getXtension(fieldType, MsgId.MODEL_XTENSION_LABEL_LENGTH.getText());
    }

    /*
     * FileFieldType
     */
    protected boolean isInRepository(FileFieldType fieldType) {
        if (fieldType.isInRepository() == null) {
            return false;
        }
        return fieldType.isInRepository();
    }

    /*
     * FormFieldType
     */
    protected String getDefault(FormFieldType formFieldType) {
        return formFieldType.getDefault();
    }

    protected boolean isMultiple(FormFieldType formFieldType) {
        if (formFieldType.isMultiple() == null) {
            return false;
        }
        return formFieldType.isMultiple();
    }

    protected boolean isReadOnly(FormFieldType formFieldType) {
        if (formFieldType.isReadOnly() == null) {
            return false;
        }
        return formFieldType.isReadOnly();
    }

    protected boolean isSearchEnum(FormFieldType formFieldType) {
        if (formFieldType.isSearchEnum() == null) {
            return false;
        }
        return formFieldType.isSearchEnum();
    }

    protected boolean isSelectionCapable(FormFieldType formFieldType) {
        if (formFieldType.isSelectionCapable() == null) {
            return false;
        }
        return formFieldType.isSelectionCapable();
    }

    /*
     * FormType
     */
    protected boolean isContentEnabled(FormType formType) {
        if (formType.isContentEnabled() == null) {
            return false;
        }
        return formType.isContentEnabled();
    }

    /*
     * ModelChoiceType
     */
    public String getFieldSize(ModelChoiceType modelChoiceType) { // public for MappingAgent
        try {
            return modelChoiceType.getFieldSize();
        } catch (Exception e) {
            return null;
        }
    }

    protected boolean isExtendedWidget(ModelChoiceType modelChoiceType) {
        if (modelChoiceType.isExtendedWidget() == null) {
            return false;
        }
        return modelChoiceType.isExtendedWidget();
    }

    protected boolean isInline(ModelChoiceType modelChoiceType) {
        if (modelChoiceType.isInline() == null) {
            return false;
        }
        return modelChoiceType.isInline();
    }

    protected boolean isOrdered(ModelChoiceType modelChoiceType) {
        if (modelChoiceType.isOrdered() == null) {
            return false;
        }
        return modelChoiceType.isOrdered();
    }

    /*
     * WorkflowTaskType
     */
    protected boolean isStartTask(WorkflowTaskType workflowTaskType) {
        if (workflowTaskType.isStartTask() == null) {
            return false;
        }
        return workflowTaskType.isStartTask();
    }

    /*
     * 
     */

    /**
     * Tells whether an AttributeType refers to a file field with upload to file
     * system.
     * 
     * @param xformsAttribute
     *            the object to test
     * @return the status
     */
    public boolean isFileContent(AttributeType xformsAttribute) {
        return (xformsAttribute.getName().endsWith("content"));
    }

    /**
     * Tells whether an AttributeType refers to a file field with upload to the
     * repository.
     * 
     * @param xformsAttribute
     *            the object to test
     * @return the status
     */
    public boolean isRepositoryContent(AttributeType xformsAttribute) {
        return (xformsAttribute.getName().endsWith("repositoryContent"));
    }

    /**
     * Tells whether an AttributeType refers to a file upload field.
     * 
     * @param xformsAttribute
     *            the object to test
     * @return the status
     */
    public boolean isFileField(AttributeType xformsAttribute) {
        return (isRepositoryContent(xformsAttribute) || isFileContent(xformsAttribute));
    }

    /**
     * Tells whether the workflow form whose name is given supports a start
     * task.
     * 
     * @param wkFormName
     * @return false if either the workflow form does not exist or it is not a
     *         start task.
     */
    public boolean isStartTaskForm(String wkFormName) {
        WorkflowTaskType workflowTaskType = getWorkflowTaskType(wkFormName, false);
        if (workflowTaskType == null) {
            return false;
        }
        return isStartTask(workflowTaskType);
    }

    /**
     * Gets the id from a node information string as formatted by
     * {@link XFormsWork.wfGetCurrentTasks}.
     * 
     * @param nodeInfoString
     * @return the id.
     */
    protected String getIdFromObjectInfo(String nodeInfoString) {
        int pos = nodeInfoString.indexOf(WEBSCRIPT_SEPARATOR);
        String result = nodeInfoString.substring(0, pos);
        return result;
    }

    /**
     * Gets the label from a node information string as formatted by
     * {@link XFormsWork.wfGetCurrentTasks}.
     * 
     * @param nodeInfoString
     * @return the label.
     */
    protected String getLabelFromObjectInfo(String nodeInfoString) {
        int pos = nodeInfoString.indexOf(WEBSCRIPT_SEPARATOR);
        int posEnd = nodeInfoString.indexOf(WEBSCRIPT_SEPARATOR, pos + WEBSCRIPT_SEPARATOR_LENGTH);
        String result = nodeInfoString.substring(pos + WEBSCRIPT_SEPARATOR_LENGTH, posEnd);
        return result;
    }

    /**
     * Gets the QName from a node information string as formatted by
     * {@link XFormsWork.wfGetCurrentTasks}.
     * 
     * @param nodeInfoString
     * @return the qname.
     */
    protected String getQNameFromObjectInfo(String nodeInfoString) {
        int pos = nodeInfoString.lastIndexOf(WEBSCRIPT_SEPARATOR);
        String result = nodeInfoString.substring(pos + WEBSCRIPT_SEPARATOR_LENGTH);
        return result;
    }

    /**
     * Gets the time from date time.
     * 
     * @param value
     *            the value
     * @return the time from date time
     */
    protected String getTimeFromDateTime(String value) {
        Calendar date = DatatypeConverter.parseDateTime(value);
        return DateTimeConverter.convert_AlfrescoToXForms_Time(date.getTimeInMillis());
    }

    /**
     * Gets the date from date time.
     * 
     * @param value
     *            the value
     * @return the date from date time
     */
    protected String getDateFromDateTime(String value) {
        Calendar date = DatatypeConverter.parseDateTime(value);
        return DateTimeConverter.convert_AlfrescoToXForms_Date(date.getTimeInMillis());
    }

    /**
     * Gets the date time from date and time.
     * 
     * @param date
     *            the date
     * @param time
     *            the time
     * @return the date time from date and time
     */
    protected String getDateTimeFromDateAndTime(String date, String time) {
        DateTime rdate = new DateTime(DatatypeConverter.parseDate(date));
        DateTime rtime = new DateTime(DatatypeConverter.parseTime(time));
        long millis = rdate.getMillis() - rdate.getMillisOfDay() + rtime.getMillisOfDay();
        return DateTimeConverter.convert_XFormsToAlfresco_DateTime(millis);
    }

    /**
     * Gets, from the given instance, the element whose tag name matches the
     * given form name.
     * 
     * @param formName
     *            the id of the form
     * @param formNode
     *            the XForms instance
     * @return
     */
    protected Element getRootElement(String formName, Node formNode) {
        Element element = null;
        if (logger.isDebugEnabled()) {
            DOMUtil.logXML(formNode, true, ">> looking for root element for form: " + formName);
        }
        if (formNode instanceof Document) {
            Element docElt = ((Document) formNode).getDocumentElement();
            element = DOMUtil.getChild(docElt, formName);
            if (element == null) {
                // we may be dealing with a workflow form
                element = DOMUtil.getFirstElement(docElt); // behavior when no workflows existed
                element = DOMUtil.getChild(element, formName);
            }
        }
        if (formNode instanceof Element) {
            element = (Element) formNode;
        }
        return element;
    }

    /**
     * Process save.
     * 
     * @param login
     *            the login
     * @param targetNode
     *            the target node
     * @return the string
     */
    protected String processSave(AlfrescoTransaction transaction, Element targetNode,
            Map<String, String> initParams) {
        // this item has to be updated or saved
        String targetId = null;
        try {
            PersistFormResultBean result = controller.persistClass(transaction, targetNode, false, initParams);
            targetId = result.getResultStr();
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
        return targetId;
    }

    /**
     * Xforms id to alfresco.
     * 
     * @param children
     *            the children
     * @return the string
     */
    protected String xformsIdToAlfresco(List<Element> children) {
        String result = null;
        Element idElt = DOMUtil.getOneElementByTagName(children, MsgId.INT_INSTANCE_SIDEID.getText());
        if (idElt != null) {
            result = controller.patchDataId(StringUtils.trimToNull(idElt.getTextContent()));
        }
        return result;
    }

    /**
     * Gets the list of content beans for the specified destination.
     * 
     * @param transaction
     *            the login
     * @param alfClass
     *            the alf class
     * @param uploadDestination
     *            the identification of the upload store
     * @param suffix
     *            the suffix that, when found in attribute names, denotes an
     *            upload field/attribute
     * @return null if no repository content file name was detected
     */
    public List<FileUploadInfoBean> getFileUploadBeans(AlfrescoTransaction transaction, GenericClass alfClass,
            String uploadDestination, String suffix) {
        List<FileUploadInfoBean> list = new ArrayList<FileUploadInfoBean>();
        List<GenericAttribute> attributes = alfClass.getAttributes().getAttribute();

        for (GenericAttribute attribute : attributes) {
            String qualifiedName = attribute.getQualifiedName();
            if (qualifiedName != null) {
                if (qualifiedName.endsWith(suffix)
                        || StringUtils.equals(attribute.getUploadTo(), uploadDestination)) {
                    String path = attribute.getValue().get(0).getValue();
                    String name = attribute.getValue().get(1).getValue();
                    String type = attribute.getValue().get(2).getValue();

                    list.add(new FileUploadInfoBean(path, name, type, attribute,
                            controller.getParamUploadRepoAppendSuffix(transaction.getInitParams())));
                }
            }
        }

        return list;
    }

    /**
     * Sets the value of an attribute to the reference, which should be a file
     * name for a filesystem
     * upload, or a reference in the format "workspace://SpacesStore/..." in
     * case of repository
     * content.<br/>
     * Clearing is needed before setting the value! Or the attr will be taken as
     * multiple-valued
     * since the GenericAttribute has file name, path, MIME...
     * 
     * @param attr
     * @param fileName
     *            the file name
     */
    public void setFileUploadFileName(String fileRef, GenericAttribute attr) {
        GenericAttribute contentAttribute = attr;
        if (contentAttribute != null) {
            contentAttribute.getValue().clear();

            ValueType valueName = alfrescoObjectFactory.createValueType();
            valueName.setValue(fileRef);
            contentAttribute.getValue().add(valueName);
        }
    }

    /**
     * Gets the value of the MsgId.MODEL_XTENSION_FAILURE_PAGE for a workflow
     * form.
     * 
     * @param formName
     */
    public String getXtensionFailurePage(String formName) {
        String result = null;
        WorkflowTaskType formType = getWorkflowTaskType(formName, true);
        if (formType != null) {
            result = getXtension(formType, MsgId.MODEL_XTENSION_FAILURE_PAGE.getText());
        }
        return result;
    }

    /**
     * Gets the value of the MsgId.MODEL_XTENSION_SUCCESS_PAGE for a workflow
     * form.
     * 
     * @param formName
     */
    public String getXtensionSuccessPage(String formName) {
        String result = null;
        WorkflowTaskType formType = getWorkflowTaskType(formName, true);
        if (formType != null) {
            result = getXtension(formType, MsgId.MODEL_XTENSION_SUCCESS_PAGE.getText());
        }
        return result;
    }

    /**
     * Gets the value of the MsgId.MODEL_XTENSION_NEXT_PAGE_CANCEL for a form of
     * a given type.
     * 
     * @param formName
     * @param formTypeEnum
     */
    public String getXtensionNextPageCancel(String formName, FormTypeEnum formTypeEnum) {
        String result = null;
        CanisterType formType = getCanisterType(formName, formTypeEnum);
        if (formType != null) {
            result = getXtension(formType, MsgId.MODEL_XTENSION_NEXT_PAGE_CANCEL.getText());
        }
        return result;
    }

    /**
     * Gets the value of the MsgId.MODEL_XTENSION_NEXT_PAGE_DELETE for a form of
     * a given type.
     * 
     * @param formName
     * @param formTypeEnum
     */
    public String getXtensionNextPageDelete(String formName, FormTypeEnum formTypeEnum) {
        String result = null;
        CanisterType formType = getCanisterType(formName, formTypeEnum);
        if (formType != null) {
            result = getXtension(formType, MsgId.MODEL_XTENSION_NEXT_PAGE_DELETE.getText());
        }
        return result;
    }

    /**
     * Gets the value of the MsgId.MODEL_XTENSION_NEXT_PAGE_SUBMIT for a form of
     * a given type.
     * 
     * @param formName
     * @param formTypeEnum
     */
    public String getXtensionNextPageSubmit(String formName, FormTypeEnum formTypeEnum) {
        String result = null;
        CanisterType formType = getCanisterType(formName, formTypeEnum);
        if (formType != null) {
            result = getXtension(formType, MsgId.MODEL_XTENSION_NEXT_PAGE_SUBMIT.getText());
        }
        return result;
    }

    /**
     * Gets the value of the MsgId.MODEL_XTENSION_SKIP_ADDITIONAL_INFO for a
     * form of a given type.
     * 
     * @param formName
     * @param formTypeEnum
     */
    public boolean getXtensionSkipAdditionalInfo(String formName, FormTypeEnum formTypeEnum) {
        boolean result = false;
        CanisterType formType = getCanisterType(formName, formTypeEnum);
        if (formType != null) {
            String value = getXtension(formType, MsgId.MODEL_XTENSION_SKIP_ADDITIONAL_INFO.getText());
            result = StringUtils.equals(value, "false");
        }
        return result;
    }

    //
    //
    // SUPPORT FOR READ ONLY DATES AND TIMES
    //

    /**
     * Tells whether the data type and read only status provided indicate a
     * special processing.
     * 
     * @param type
     * @param isReadOnly
     * @param isServletRequest
     * @return
     */
    protected static boolean isAmendable(String type, boolean isReadOnly, boolean isServletRequest) {
        return isReadOnly && (isServletRequest == false)
                && (type.equalsIgnoreCase(MsgId.INT_TYPE_XSD_DATE.getText())
                        || type.equalsIgnoreCase(MsgId.INT_TYPE_XSD_DATETIME.getText())
                        || type.equalsIgnoreCase(MsgId.INT_TYPE_XSD_TIME.getText()));
    }

    /**
     * Formats the time sent to the XForms instances for "time" or "dateTime"
     * values.
     * 
     * @param timeValue
     *            , e.g. 12:34:56.789+01:00
     * @return the time, formatted as specified in the messages properties, or
     *         (if nothing is
     *         specified) the time in 24-hour format (e.g. 12:34:56)
     */
    protected String transformTimeValueForDisplay(String timeValue) {
        String defaultFormat = timeValue.substring(0, 8);
        String hh = timeValue.substring(0, 2);
        String mm = timeValue.substring(3, 5);
        String ss = timeValue.substring(6, 8);
        String milli = timeValue.substring(9, 12);

        String formatted = MsgPool.testMsg(MsgId.MSG_FORMAT_TIME_OUTPUT, hh, mm, ss, milli);
        if (StringUtils.trimToNull(formatted) == null) {
            return defaultFormat;
        }

        return formatted;
    }

    /**
     * @param dateValue
     *            in YYYY-MM-DD format
     * @return the date, formatted as specified.
     */
    protected String transformDateValueForDisplay(String dateValue) {
        String defaultFormat = dateValue.substring(0, 10);
        String yyyy = dateValue.substring(0, 4);
        String yy = dateValue.substring(2, 4);
        String mm = dateValue.substring(5, 7);
        String dd = dateValue.substring(8, 10);

        String formatted = MsgPool.testMsg(MsgId.MSG_FORMAT_DATE_OUTPUT, yyyy, yy, mm, dd);
        if (StringUtils.trimToNull(formatted) == null) {
            return defaultFormat;
        }

        return formatted;
    }

    protected String extractTimeFromDateTimeModified(String inputTextContent) {
        return inputTextContent.substring(inputTextContent.indexOf('T') + 1);
    }

    protected String extractDateFromDateTimeModified(String inputTextContent) {
        return inputTextContent.substring(0, inputTextContent.indexOf('T'));
    }

    protected String getReadOnlyDateOrTimeModifiedValue(String type, String initialValue) {
        String modifiedValue = null;

        if (type.equalsIgnoreCase("date")) {
            modifiedValue = transformDateValueForSaving(initialValue);
        } else if (type.equalsIgnoreCase("time")) {
            modifiedValue = transformTimeValueForSaving(initialValue);
        } else {
            modifiedValue = transformDateTimeValueForSaving(initialValue);
        }

        return modifiedValue;
    }

    /**
     * Interprets the user format for times and maps the value to the usual
     * format (ISO 8601).<br/>
     * Mainly copied from transformDateValueForSaving.
     * 
     * @param initialValue
     * @return the ISO 8601 DateTime value, e.g. 2009-12-09T12:34:56.789+01:00.
     *         If any exception
     *         occurs or the format/input value is not well formed, returns the
     *         current date and
     *         time.
     */
    private String transformDateTimeValueForSaving(String initialValue) {

        TransformInfoBean bean = transformDateValueHelper(initialValue);
        String dateValue = bean.result;
        String timeInitialValue = initialValue.substring(bean.positionWhereStopped + 1);
        String timeValue = transformTimeValueHelper(timeInitialValue).result;
        return dateValue + 'T' + timeValue;
    }

    /**
     * Interprets the user format for times and maps the value to the usual
     * format (ISO 8601).<br/>
     * Mainly copied from transformDateValueForSaving.
     * 
     * @param timeValue
     * @return the time in HH:MM:SS.mmm+TimeZone, e.g. 12:34:56.789+01:00. If
     *         any exception occurs
     *         or the format/input value is not well formed, returns the current
     *         date.
     */
    protected String transformTimeValueForSaving(String timeValue) {

        return transformTimeValueHelper(timeValue).result;
    }

    /**
     * @param timeValue
     * @param format
     * @return
     */
    private TransformInfoBean transformTimeValueHelper(String timeValue) {
        String format = MsgPool.getMsg(MsgId.MSG_FORMAT_TIME_OUTPUT);

        StringBuffer result = new StringBuffer("");
        // # 0: hour, 1: min, 2: sec, 3: millis
        final int HH = 0, MM = 1, SS = 2, MILLIS = 3;
        String[] values = { "", "00", "00", "000" };
        int[] lengths = { 2, 2, 2, 3 };
        int posStart;
        int posEnd;
        String idxStr;
        int idx;
        String finalResult;
        boolean error = false;
        String localValue = timeValue;

        posStart = format.indexOf('{');

        // collect the values
        while ((posStart != -1) && (error == false)) {
            posEnd = format.indexOf('}', posStart);
            if (posEnd == -1) {
                error = true;
            } else {
                idxStr = format.substring(posStart + 1, posEnd);
                if (idxStr.length() == 1) {
                    if (posStart != 0) {
                        // get rid of supplementary characters
                        localValue = localValue.substring(posStart);
                    }
                    try {
                        idx = Integer.parseInt(idxStr);
                        if ((idx >= 0) && (idx <= 3)) {
                            values[idx] = localValue.substring(0, lengths[idx]);
                            // get rid of what's been consumed
                            localValue = localValue.substring(lengths[idx]);
                            format = format.substring(posEnd + 1);
                            posStart = format.indexOf('{');
                        } else {
                            error = true;
                        }
                    } catch (Exception e) {
                        error = true;
                    }
                } else {
                    // we don't support 2-digit placeholders
                    error = true;
                }
            }
        }

        // if incomplete format, return the default
        if (error || (values[HH].length() != 2) || (values[SS].length() != 2)) {
            finalResult = createXFormsInitialValue("Time", null, null, null);
        } else {
            result.append(values[HH]);
            result.append(":");
            result.append(values[MM]);
            result.append(":");
            result.append(values[SS]);
            result.append(".");
            result.append(values[MILLIS]);

            String localTimeZone = createXFormsInitialValue("Time", null, null, null).substring(12);
            result.append(localTimeZone);
            finalResult = result.toString();
        }

        return new TransformInfoBean(finalResult, -1);
    }

    /**
     * Interprets the user format for dates and maps the value to the usual
     * format (ISO 8601).
     * 
     * @param dateValue
     * @return the date in YYYY-MM-DD, e.g. 2009-12-09. If any exception occurs
     *         or the format/input
     *         value is not well formed, returns the current date.
     */
    protected String transformDateValueForSaving(String dateValue) {
        return transformDateValueHelper(dateValue).result;
    }

    /**
     * @param dateValue
     * @return
     */
    private TransformInfoBean transformDateValueHelper(String dateValue) {
        StringBuffer result = new StringBuffer("");
        // # 0: yyyy; 1: yy; 2: mm; 3: dd
        final int YYYY = 0, YY = 1, MM = 2, DD = 3;
        String[] values = { "", "", "", "" };
        int[] lengths = { 4, 2, 2, 2 };
        int posStart;
        int posEnd;
        String idxStr;
        int idx;
        boolean error = false;
        String finalResult;
        String format = MsgPool.getMsg(MsgId.MSG_FORMAT_DATE_OUTPUT);
        String localValue = dateValue;
        int skipped = 0;

        posStart = format.indexOf('{');

        // collect the values for YYYY, YY, MM and DD
        while ((posStart != -1) && (error == false)) {
            posEnd = format.indexOf('}', posStart);
            if (posEnd == -1) {
                error = true;
            } else {
                idxStr = format.substring(posStart + 1, posEnd);
                if (idxStr.length() == 1) {
                    if (posStart != 0) {
                        // get rid of supplementary characters
                        localValue = localValue.substring(posStart);
                        skipped += posStart;
                    }
                    try {
                        idx = Integer.parseInt(idxStr);
                        if ((idx >= 0) && (idx <= 3)) {
                            values[idx] = localValue.substring(0, lengths[idx]);
                            // get rid of what's been consumed
                            localValue = localValue.substring(lengths[idx]);
                            skipped += lengths[idx];
                            format = format.substring(posEnd + 1);
                            posStart = format.indexOf('{');
                        } else {
                            error = true;
                        }
                    } catch (Exception e) {
                        error = true;
                    }
                } else {
                    // we don't support 2-digit placeholders
                    error = true;
                }
            }
        }

        // if incomplete format, return the default
        if (error || ((values[YYYY].length() != 4) && (values[YY].length() != 2)) || (values[MM].length() != 2)
                || (values[DD].length() != 2)) {
            finalResult = createXFormsInitialValue("Date", null, null, null);
        } else {
            result.append((values[YYYY].length() == 4) ? values[YYYY] : "20" + values[YY]);
            result.append("-");
            result.append(values[MM]);
            result.append("-");
            result.append(values[DD]);

            finalResult = result.toString();
        }

        return new TransformInfoBean(finalResult, skipped);
    }

    public static String getNameSpaceFromPrefix(String prefix) {
        MappingModelNS[] ns = MappingModelNS.values();
        for (MappingModelNS mappingModelNS : ns) {
            if (prefix.equals(mappingModelNS.getPrefix())) {
                return mappingModelNS.getNs();
            }
        }
        return MsgId.INT_NAMESPACE_BLUEXML_CLASS + "/" + prefix + "/1.0";
    }
}