com.bluexml.xforms.controller.alfresco.agents.MappingAgent.java Source code

Java tutorial

Introduction

Here is the source code for com.bluexml.xforms.controller.alfresco.agents.MappingAgent.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.alfresco.agents;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

import javax.servlet.ServletException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.bluexml.xforms.actions.GetAction;
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.GetInstanceFormBean;
import com.bluexml.xforms.controller.beans.PersistFormResultBean;
import com.bluexml.xforms.controller.beans.WorkflowTaskInfoBean;
import com.bluexml.xforms.controller.binding.AssociationType;
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.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.SearchFormType;
import com.bluexml.xforms.controller.binding.WorkflowTaskType;
import com.bluexml.xforms.controller.mapping.MappingToolAlfrescoToClassForms;
import com.bluexml.xforms.controller.mapping.MappingToolAlfrescoToForms;
import com.bluexml.xforms.controller.mapping.MappingToolClassFormsToAlfresco;
import com.bluexml.xforms.controller.mapping.MappingToolCommon;
import com.bluexml.xforms.controller.mapping.MappingToolFormsToAlfresco;
import com.bluexml.xforms.controller.mapping.MappingToolSearch;
import com.bluexml.xforms.controller.navigation.FormTypeEnum;
import com.bluexml.xforms.messages.MsgId;
import com.bluexml.xforms.messages.MsgPool;

/**
 * The Class MappingTool.<br>
 * Delegates operations to instances of MappingToolCommon
 */
public class MappingAgent {

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

    /** The mapping tool impl alfresco to x forms. */
    private MappingToolAlfrescoToClassForms mappingToolImplAlfrescoToXForms;

    /** The mapping tool impl x forms to alfresco. */
    private MappingToolClassFormsToAlfresco mappingToolImplXFormsToAlfresco;

    /** The mapping tool alfresco to forms. */
    private MappingToolAlfrescoToForms mappingToolAlfrescoToForms;

    /** The mapping tool forms to alfresco. */
    private MappingToolFormsToAlfresco mappingToolFormsToAlfresco;

    /** The mapping tool forms to alfresco. */
    private MappingToolSearch mappingToolSearch;

    private Mapping mapping;

    private AlfrescoController controller;

    /** The current upload path, set by getParamUploadPathInFileSystem */
    private File currentUploadDir;

    public void loadMappingXml(boolean failIfError) throws Exception {
        URL url = AlfrescoController.class.getResource("/mapping.xml");
        File file = new File(new URI(url.toString()));
        InputStream mappingStream = new FileInputStream(file);
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance("com.bluexml.xforms.controller.binding");
            Unmarshaller mappingUnmarshaller = jaxbContext.createUnmarshaller();
            Mapping mappingInstance = (Mapping) mappingUnmarshaller.unmarshal(mappingStream);
            this.mapping = mappingInstance;
        } catch (JAXBException e) {
            if (logger.isErrorEnabled()) {
                logger.error("Error loading the mapping file", e);
            }
            if (failIfError) {
                throw new RuntimeException(e);
            }
        } finally {
            mappingStream.close();
        }
    }

    /**
     * Instantiates a new mapping tool impl.
     * 
     * @param mapping
     *            the mapping
     * @param controller
     *            the controller
     * @throws Exception
     */
    public MappingAgent(AlfrescoController controller) throws Exception {
        super();
        loadMappingXml(true); // we need the first loading to fail in case of error
        this.controller = controller;
        mappingToolImplAlfrescoToXForms = new MappingToolAlfrescoToClassForms(mapping, controller);
        mappingToolImplXFormsToAlfresco = new MappingToolClassFormsToAlfresco(mapping, controller);
        mappingToolAlfrescoToForms = new MappingToolAlfrescoToForms(mapping, controller);
        mappingToolFormsToAlfresco = new MappingToolFormsToAlfresco(mapping, controller);
        mappingToolSearch = new MappingToolSearch(mapping, controller);
    }

    /**
     * Creates the class forms instance.
     * 
     * @param transaction
     *            the login
     * @param type
     *            the type
     * @param formIsReadOnly
     * 
     * @return the document
     */
    private Document createClassFormsInstance(AlfrescoTransaction transaction, String type, boolean formIsReadOnly,
            boolean isServletRequest) {
        return mappingToolImplAlfrescoToXForms.createClassFormsInstance(transaction, type,
                transaction.getInitParams(), new Stack<AssociationType>(), formIsReadOnly, isServletRequest);
    }

    /**
     * Gets the content file name.
     * 
     * @param transaction
     *            the login
     * @param alfClass
     *            the alf class
     * 
     * @return the content file name
     */
    private List<FileUploadInfoBean> getUploadBeansFilesystem(AlfrescoTransaction transaction,
            GenericClass alfClass) {
        return mappingToolImplXFormsToAlfresco.getFileUploadBeans(transaction, alfClass,
                MsgId.INT_UPLOAD_DEST_FILE.getText(), MsgId.INT_SUFFIX_UPLOAD_FILE.getText());
    }

    /**
     * Gets the beans containing info for repository contents.
     * 
     * @param transaction
     *            the login
     * @param alfClass
     *            the alf class
     * 
     * @return the bean, or null if no repository content file exists in the class
     */
    private List<FileUploadInfoBean> getUploadBeansRepo(AlfrescoTransaction transaction, GenericClass alfClass) {
        return mappingToolImplXFormsToAlfresco.getFileUploadBeans(transaction, alfClass,
                MsgId.INT_UPLOAD_DEST_REPO.getText(), MsgId.INT_SUFFIX_UPLOAD_REPO.getText());
    }

    /**
     * Gets the repository content info.
     * 
     * @param transaction
     *            the login
     * @param alfClass
     *            the alf class
     * 
     * @return the bean, or null if no repository content file exists in the class
     */
    private FileUploadInfoBean getNodeContentInfo(AlfrescoTransaction transaction, GenericClass alfClass) {
        return mappingToolFormsToAlfresco.getNodeContentInfo(transaction, alfClass);
    }

    /**
     * Sets the content file name.
     * 
     * @param alfClass
     *            the alf class
     * @param fileName
     *            the file name
     */
    private void setFileUploadFileName(String fileName, GenericAttribute attr) {
        mappingToolImplXFormsToAlfresco.setFileUploadFileName(fileName, attr);
    }

    /**
     * Transform alfresco to class forms.
     * 
     * @param transaction
     *            the login
     * @param alfrescoNode
     *            the alfresco node
     * @param stack
     *            the stack
     * @param isServletRequest
     * 
     * @return the document
     * 
     * @throws ServletException
     *             the alfresco controller exception
     */
    public Document transformAlfrescoToClassForms(AlfrescoTransaction transaction, Document alfrescoNode,
            Stack<AssociationType> stack, boolean formIsReadOnly, boolean isServletRequest)
            throws ServletException {
        return mappingToolImplAlfrescoToXForms.transformAlfrescoToClassForms(transaction, alfrescoNode, stack,
                formIsReadOnly, isServletRequest);
    }

    /**
     * Transform class forms to alfresco.
     * 
     * @param node
     *            the node
     * @param isServletRequest
     * @param initParams
     * 
     * @return the com.bluexml.xforms.controller.alfresco.binding. class
     */
    private GenericClass transformClassFormsToAlfresco(Node node, boolean isServletRequest,
            Map<String, String> initParams) {
        return mappingToolImplXFormsToAlfresco.transformClassFormsToAlfresco(node, isServletRequest, initParams);
    }

    /**
     * Removes references to the given id from the document.
     * 
     * @param node
     *            the node
     * @param elementId
     *            the element id
     */
    public void removeReference(Document node, String elementId) {
        mappingToolImplXFormsToAlfresco.removeReference(node, elementId);
    }

    /**
     * Checks for sub types.
     * 
     * @param dataType
     *            the data type
     * 
     * @return true, if successful
     */
    public boolean hasSubTypes(String dataType) {
        return mappingToolImplXFormsToAlfresco.hasSubTypes(dataType);
    }

    /**
     * Creates the form instance.
     * 
     * @param transaction
     *            the login
     * @param formName
     *            the form name
     * @param isMassTagging
     * @param alfrescoId
     *            the alfresco id
     * 
     * @return the document
     * 
     */
    private Document createFormInstance(AlfrescoTransaction transaction, String formName, boolean formIsReadOnly,
            boolean isMassTagging) {
        return mappingToolAlfrescoToForms.newFormInstance(formName, transaction, transaction.getInitParams(),
                formIsReadOnly, isMassTagging);
    }

    /**
     * Provides an instance of the given form pre-filled with properties stored in the given object.
     * 
     * @param transaction
     * @param type
     *            the form id
     * @param id
     *            the node id of the object that provides the info
     * @param formIsReadOnly
     * @return
     * @throws ServletException
     */
    private Document loadFormInstance(AlfrescoTransaction transaction, String type, String id,
            boolean formIsReadOnly) throws ServletException {
        return mappingToolAlfrescoToForms.getFormInstance(transaction, type, id, transaction.getInitParams(),
                formIsReadOnly);
    }

    /**
     * Transform forms to alfresco. Builds a GenericClass for a form from the given node. Extracted
     * from persistForm to provide an access to workflow actions.
     * 
     * @param transaction
     *            the login
     * @param formName
     *            the form name
     * @param formNode
     *            the instance provided by the XForms engine.
     * 
     * @return the com.bluexml.xforms.controller.alfresco.binding. class
     * 
     * @throws ServletException
     */
    private GenericClass transformsToAlfresco(AlfrescoTransaction transaction, String formName, Node formNode,
            Map<String, String> initParams, boolean isMassTagging) throws ServletException {
        return mappingToolFormsToAlfresco.transformsToAlfresco(transaction, formName, formNode, initParams,
                isMassTagging);
    }

    /**
     * Gets the class type.
     * 
     * @param type
     *            the type, under the form package + "." + name
     * 
     * @return the class type
     */
    private ClassType getClassType(String type) {
        return mappingToolImplAlfrescoToXForms.getClassType(type);
    }

    private EnumType getEnumType(String type) {
        return mappingToolImplAlfrescoToXForms.getEnumType(type);
    }

    /**
     * Provides the Alfresco name for a class data type.
     * 
     * @param type
     *            the type, under the form package + "." + name
     * @return
     */
    public String getClassTypeAlfrescoName(String type) {
        return mappingToolImplAlfrescoToXForms.getClassType(type).getAlfrescoName();
    }

    /**
     * 
     * @param type
     * @return
     */
    public String getEnumTypeName(String type) {
        return mappingToolImplAlfrescoToXForms.getEnumType(type).getName();
    }

    /**
     * Gets the value of the field size property for a data type from the mapping.
     * 
     * @param attrType
     *            the complete type name of the items in the model choice field
     * @param defaultVal
     *            the value to return if no field size value is found
     * @param formName
     *            the id of the form
     * @return
     */
    public String getFieldSizeForField(String attrType, String defaultVal, String formName) {
        // we'll test only one form so the form name should be unique in the mapping file
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();
        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof FormType) {
                FormType form = (FormType) element.getValue();
                if (StringUtils.equals(form.getName(), formName)) {
                    List<ModelChoiceType> modelChoices = form.getModelChoice();
                    for (ModelChoiceType modelChoice : modelChoices) {
                        ClassType rc = modelChoice.getRealClass();
                        String completeName = rc.getAlfrescoName().replace("_", ".");
                        if (completeName.equalsIgnoreCase(attrType)) {
                            String value = mappingToolAlfrescoToForms.getFieldSize(modelChoice);
                            // the name may exist without having a value for field size
                            return ((value == null) ? defaultVal : value);
                        }
                    }
                }
            }
        }
        return defaultVal;
    }

    /**
     * Retrieves the label for a model choice field that implements a specific association on a
     * specific form.
     * 
     * @param completeAssoName
     *            the Alfresco name of the association
     * @param formId
     *            the name of the form
     * @return
     */
    public String getDisplayLabelFromAssociationName(String completeAssoName, String formId) {
        FormType formType = mappingToolAlfrescoToForms.getFormType(formId);
        if (formType != null) {
            List<ModelChoiceType> modelChoices = formType.getModelChoice();
            for (ModelChoiceType modelChoice : modelChoices) {
                if (modelChoice.getAlfrescoName().equals(completeAssoName)) {
                    return modelChoice.getDisplayLabel();
                }
            }
        }
        ClassType classType = getClassType(formId);
        if (classType != null) {
            List<AssociationType> assos = classType.getAssociation();
            for (AssociationType asso : assos) {
                if (asso.getAlfrescoName().equals(completeAssoName)) {
                    return asso.getCaption();
                }
            }
        }
        return ""; // normally, we should not get here (unless there are multiple violations)
    }

    /**
     * Marshalls the batch queue of the given transaction into a string.
     * 
     * @param transaction
     * @return
     * @throws ServletException
     */
    public static String marshalBatch(AlfrescoTransaction transaction) throws ServletException {
        return MappingToolCommon.marshal(transaction.getBatch());
    }

    /**
     * Unmarshalls a document based on classes generated from the xsd files.
     * 
     * @param alfrescoNode
     * @return
     * @throws JAXBException
     */
    private static synchronized GenericClass unmarshal(Document alfrescoNode) throws JAXBException {
        return MappingToolCommon.unmarshal(alfrescoNode);
    }

    /**
     * Checks if is an enumeration is dynamic.
     * 
     * @param type
     *            the type
     * 
     * @return true, if is dynamic enum
     */
    public boolean isDynamicEnum(String type) {
        EnumType enumType = getEnumType(type);
        if (enumType != null) {
            return mappingToolFormsToAlfresco.isDynamic(enumType);
        }
        return false; // happens for search operators enums; they don't get into the mapping file
    }

    public boolean isRendered(ClassType classType) {
        return mappingToolFormsToAlfresco.isRendered(classType);
    }

    /**
     * Gets the suffix (specified at generation time) for read only forms.
     * 
     * @return the suffix
     */
    public String getReadOnlyFormsSuffix() {
        return mapping.getGenInfo().getReadOnlyFormsSuffix();
    }

    public boolean getDebugModeStatus() {
        return mapping.getGenInfo().isDebugMode();
    }

    private FormType getFormType(String refName) {
        return mappingToolAlfrescoToForms.getFormType(refName);
    }

    private SearchFormType getSearchType(String refName) {
        return mappingToolAlfrescoToForms.getSearchFormType(refName);
    }

    private WorkflowTaskType getWorkflowTaskType(String refName, boolean byId) {
        return mappingToolAlfrescoToForms.getWorkflowTaskType(refName, byId);
    }

    @SuppressWarnings("unused")
    @Deprecated
    private WorkflowTaskType getWorkflowTaskTypeWithField(String fieldName) {
        return mappingToolAlfrescoToForms.getWorkflowTaskTypeWithField(fieldName);
    }

    public String getWorkflowFieldAlfrescoName(String wkFormName, String fieldName) {
        return mappingToolAlfrescoToForms.getWorkflowFieldAlfrescoName(wkFormName, fieldName);
    }

    private void collectTaskProperties(AlfrescoTransaction transaction, Document instance, Element taskElt,
            String wkFormName, Map<String, GenericClass> alfrescoNodes, boolean formIsReadOnly) {
        mappingToolAlfrescoToForms.collectTaskProperties(transaction, instance, taskElt, wkFormName, alfrescoNodes,
                formIsReadOnly);
    }

    public boolean isStartTaskForm(String wkFormName) {
        return mappingToolFormsToAlfresco.isStartTaskForm(wkFormName);
    }

    public String getCustomFormForDatatype(String dataType) {
        return mappingToolFormsToAlfresco.getCustomFormForDatatype(dataType);
    }

    public String getDefaultFormForDatatype(String dataType) {
        return mappingToolFormsToAlfresco.getDefaultFormForDatatype(dataType);
    }

    /**
     * Retrieves (from the mapping file) the name/id of a form that implements the start task for a
     * workflow definition name.<br/>
     * Added for the demo webapp.
     * 
     * @param workflowDefName
     *            e.g. jbpm$wfbxwfTest:wfTest
     * @return the id of a form, e.g. "wfTest_Start"
     */
    public String getWorkflowStartTaskFormName(String namespacePrefix) {
        return mappingToolFormsToAlfresco.getWorkflowStartTaskFormName(namespacePrefix);
    }

    /**
     * Provides the name of the data type supported by the FormClass with the given id.
     * 
     * @param formName
     *            the valid id of a FormClass that has been generated.
     * @return the data type as defined in the class model, or <code>null</code> if the form name is
     *         unknown.
     */
    public String getUnderlyingClassForForm(String formName) {
        FormType formType = getFormType(formName);
        if (formType == null) {
            return null;
        }
        ClassType classType = formType.getRealClass();
        if (classType == null) {
            return null;
        }
        return classType.getAlfrescoName();
    }

    /**
     * Provides the name of the data type supported by the data form of the FormWorkflow with the
     * given id.
     * 
     * @param formName
     *            the valid id of a FormWorkflow that has been generated.
     * @return the data type of the workflow form's data form, as defined in the class model, or
     *         <code>null</code> if the workflow form name is unknown or no data form is defined.
     */
    public String getUnderlyingClassForWorkflow(String wkFormName) {
        WorkflowTaskType taskType = getWorkflowTaskType(wkFormName, false);
        if (taskType == null) {
            return null;
        }
        String dataFormName = taskType.getDataForm();
        if (dataFormName == null) {
            return null;
        }
        return getUnderlyingClassForForm(dataFormName);
    }

    /**
     * Provides the id of the data form linked to the given workflow form.
     * 
     * 
     * @param formName
     *            the valid id of a FormWorkflow that has been generated.
     * @return the id of the data form.
     */
    public String getUnderlyingDataFormForWorkflow(String wkFormName) {
        WorkflowTaskType taskType = getWorkflowTaskType(wkFormName, false);
        if (taskType == null) {
            return null;
        }
        String dataForm = taskType.getDataForm();
        if (dataForm == null) {
            return null;
        }
        FormType dataFormType = getFormType(dataForm);
        return dataFormType.getName();
    }

    /**
     * Provides the pooled actors defined on the form that supports the given task name.
     * 
     * @param name
     *            a task definition name.
     * @return the content of the "pooled actors" property.
     */
    public String getWorkflowTaskPooledActorsById(String taskName) {
        WorkflowTaskType taskType = getWorkflowTaskType(taskName, true);
        if (taskType == null) {
            return null;
        }
        return taskType.getPooledActors();
    }

    /**
     * Provides the actor Id defined on the form that supports the given task name.
     * 
     * @param name
     *            a task definition name.
     * @return the content of the "actor id" property.
     */
    public String getWorkflowTaskActorIdById(String taskName) {
        WorkflowTaskType taskType = getWorkflowTaskType(taskName, true);
        if (taskType == null) {
            return null;
        }
        return taskType.getActorId();
    }

    @SuppressWarnings("unused")
    @Deprecated
    private String getTaskNameFromFormName(String wkFormName) {
        WorkflowTaskType taskType = getWorkflowTaskType(wkFormName, false);
        if (taskType == null) {
            return null;
        }
        return taskType.getName();
    }

    /**
     * Provides a pool of information about the form whose name is given.
     * 
     * @param wkFormName
     *            the id of a valid workflow form.
     * @return the information bean.
     */
    public WorkflowTaskInfoBean getWorkflowTaskInfoBean(String wkFormName) {
        WorkflowTaskType taskType = getWorkflowTaskType(wkFormName, false);
        if (taskType == null) {
            return null;
        }
        return new WorkflowTaskInfoBean(taskType.getTaskId(), taskType.getName(), taskType.getDataForm(),
                taskType.getActorId(), taskType.getPooledActors(), taskType.getTitle(), taskType.getProcessTitle());
    }

    /**
     * Provides a pool of information about the form that supports the given task id.
     * 
     * @param taskId
     *            the id of a task.
     * @return the information bean.
     */
    public WorkflowTaskInfoBean getWorkflowTaskInfoBeanById(String taskId) {
        WorkflowTaskType taskType = getWorkflowTaskType(taskId, true);
        if (taskType == null) {
            return null;
        }
        return new WorkflowTaskInfoBean(taskType.getTaskId(), taskType.getName(), taskType.getDataForm(),
                taskType.getActorId(), taskType.getPooledActors(), taskType.getTitle(), taskType.getProcessTitle());
    }

    /**
     * Returns the form name for a task based on the full task id.<br/>
     * Added for the demo webapp.
     * 
     * @param fullTaskId
     *            e.g. "wfbxwfTest:T1"
     * @return the id of the workflow form that can be used for the task
     */
    public String getWorkflowFormNameByTaskId(String taskId) {
        WorkflowTaskType taskType = getWorkflowTaskType(taskId, true);
        if (taskType == null) {
            if (logger.isErrorEnabled()) {
                logger.error("No task definition in the mapping for task '" + taskId + "'");
            }
            return "";
        }
        return taskType.getName();
    }

    /**
     * Persists a default form: transforms the XForms instance into a GenericClass, and either saves
     * or updates a node.
     * 
     * @param instance
     *            the instance
     * @param transaction
     *            the transaction
     * @param isServletRequest
     * 
     * @return the object id as given by the transaction manager
     * 
     * @throws ServletException
     */
    public PersistFormResultBean persistClass(AlfrescoTransaction transaction, Node instance,
            boolean isServletRequest, Map<String, String> initParams) throws ServletException {
        GenericClass alfClass = transformClassFormsToAlfresco(instance, isServletRequest, initParams);
        if (alfClass.getId() == null) {
            alfClass.setId(saveObject(transaction, alfClass));
        } else {
            uploadProcessOnUpdate(transaction, alfClass);
        }
        PersistFormResultBean resultBean = new PersistFormResultBean();
        resultBean.setResultStr(alfClass.getId());

        return resultBean;
    }

    /**
     * Persists a FormClass: transforms the XForms instance into a GenericClass, and either saves or
     * updates a node.
     * 
     * @param transaction
     *            the transaction
     * @param type
     *            the type
     * @param instance
     *            the instance
     * @param isMassTagging
     *            TODO
     * 
     * @return the temporary id assigned to the class
     * 
     * @throws ServletException
     */
    public PersistFormResultBean persistForm(AlfrescoTransaction transaction, String type, Node instance,
            Map<String, String> initParams, boolean isMassTagging) throws ServletException {
        PersistFormResultBean resultBean = new PersistFormResultBean();
        GenericClass alfClass = this.transformsToAlfresco(transaction, type, instance, initParams, isMassTagging);
        if (alfClass.getId() == null) {
            alfClass.setId(saveObject(transaction, alfClass));
        } else {
            if (isMassTagging) {
                transaction.queueUpdate(alfClass);
            } else {
                updateObject(transaction, alfClass);
            }
        }
        resultBean.setResultStr(alfClass.getId());

        return resultBean;
    }

    /**
     * Transforms a FormClass instance into a JSON string for the given form id.
     * 
     * @param transaction
     * @param alfrescoController
     * @param formName
     * @param instance
     * @return
     * @throws ServletException
     */
    public String persistFormToJSON(AlfrescoTransaction transaction, String formName, Node instance,
            boolean shortPropertyNames, Map<String, String> initParams) throws ServletException {
        return mappingToolFormsToAlfresco.transformsToJSON(transaction, formName, instance, shortPropertyNames,
                initParams);
    }

    /**
     * Transforms the instance to the format for searches wrt the given form id.
     * 
     * @param formName
     *            the id of the search form
     * @param instance
     * @return
     * @throws ServletException
     */
    public String persistSearch(String formName, Node instance, boolean shortPropertyNames,
            Map<String, String> initParams) throws ServletException {
        return mappingToolSearch.transformSearchForm(formName, instance, shortPropertyNames, initParams);
    }

    /**
     * Builds a GenericClass object from the instance of a workflow and processes the possible
     * upload fields.
     * 
     * @param transaction
     * @param taskTypeName
     *            the id of the workflow form
     * @param taskElt
     *            the root element containing the workflow properties
     * @return
     * @throws ServletException
     */
    public PersistFormResultBean persistWorkflow(AlfrescoTransaction transaction, String taskTypeName, Node taskElt,
            Map<String, String> initParams) throws ServletException {
        GenericClass transformed = transformsToAlfresco(transaction, taskTypeName, taskElt, initParams, false);

        // #1209: we must support FileFields on workflow forms so we also simulate a queuing
        saveObject(transaction, transformed);

        PersistFormResultBean resultBean = new PersistFormResultBean();
        resultBean.setResultClass(transformed);

        return resultBean;
    }

    /**
     * Creates or loads the instance for a default class form.
     * 
     * @param transaction
     *            the transaction
     * @param type
     *            the content type
     * @param id
     *            the id
     * @param initParams
     *            the init params
     * @param idAsServlet
     *            whether the request comes from a servlet
     * 
     * @return the class
     * 
     * @throws ServletException
     *             the alfresco controller exception
     */
    public Document getInstanceClass(AlfrescoTransaction transaction, String type, String id,
            boolean formIsReadOnly, boolean isServletRequest) throws ServletException {
        Document instance = null;
        try {
            if (id == null) {
                instance = createClassFormsInstance(transaction, type, formIsReadOnly, isServletRequest);
            } else {
                instance = controller.getObjectInstance(transaction, id, new Stack<AssociationType>(),
                        formIsReadOnly, isServletRequest);
            }
        } catch (ServletException se) {
            throw se; // just propagate
        } catch (Exception e) {
            throw new ServletException(e);
        }
        return instance;
    }

    /**
     * Creates or loads the instance for a FormClass.
     * 
     * @param transaction
     *            the transaction
     * @param type
     *            the content type
     * @param id
     *            the id of the object to load. If <b>null</b>, the form is empty (with the
     *            exception of default values [from in the model] and initial values [from URL
     *            parameters]. If non null, the form is filled with values from the object.
     * 
     * @return the form
     * 
     * @throws ServletException
     *             the alfresco controller exception
     */
    public Document getInstanceForm(AlfrescoTransaction transaction, GetInstanceFormBean bean)
            throws ServletException {
        Document instance = null;
        try {
            if (bean.getId() == null) {
                boolean isMassTagging = bean.isMassTagging();
                instance = createFormInstance(transaction, bean.getFormName(), bean.isReadOnly(), isMassTagging);
            } else {
                instance = loadFormInstance(transaction, bean.getFormName(), bean.getId(), bean.isReadOnly());
            }

        } catch (Exception e) {
            throw new ServletException(e);
        }
        return instance;
    }

    /**
     * Returns an instance for workflow forms so that they can be displayed.
     * 
     * @param transaction
     * @param formName
     * @see {@link GetAction}
     * @param formName
     * @return the instance document, which is never <code>null</code>.
     */
    public Document getInstanceWorkflow(AlfrescoTransaction transaction, String formName) {

        // DOM structure
        Document instance = AlfrescoController.getDocBuilder().newDocument();
        Element taskElt = instance.createElement(formName);
        Element rootElement = instance.createElement(MsgId.INT_INSTANCE_WKFLW_NODESET.getText());
        rootElement.appendChild(taskElt);
        instance.appendChild(rootElement);

        // fill the instance with the properties values
        Map<String, GenericClass> alfrescoNodes = new HashMap<String, GenericClass>();
        collectTaskProperties(transaction, instance, taskElt, formName, alfrescoNodes, false);

        return instance;
    }

    /**
     * Returns an XForms instance document for a FormSearch.
     * 
     * @param formName
     * @return
     */
    public Document getInstanceSearch(String formName) {
        return mappingToolSearch.getInstanceSearch(formName);
    }

    /**
     * Saves a node. Moves uploaded files to the file system/repository and enqueues the 'save'
     * operation in the transaction, returning the id given by the transaction manager. <br/>
     * NOTE: The values from the instance <b>must</b> have already been collected.
     * 
     * @param alfClass
     *            the alf class
     * @param transaction
     *            the transaction
     * 
     * @return the string
     * 
     * @throws ServletException
     */
    private String saveObject(AlfrescoTransaction transaction, GenericClass alfClass) throws ServletException {
        // enqueue the operation
        transaction.queueSave(alfClass);

        // process file upload fields if any
        uploadProcessOnSave(transaction, alfClass);

        return alfClass.getId();
    }

    /**
     * Updates a node: enqueues the update operation in the transaction and processes file uploads.
     * 
     * @param transaction
     * @param alfClass
     * @return
     * @throws ServletException
     */
    private String updateObject(AlfrescoTransaction transaction, GenericClass alfClass) throws ServletException {
        // enqueue the operation
        transaction.queueUpdate(alfClass);

        // process file upload fields if any
        uploadProcessOnUpdate(transaction, alfClass);

        return alfClass.getId();
    }

    /**
     * Attaches the referenced content to a transaction. The attachment is not done if the file name
     * is not a valid file URI (which happens when 1-there's no uploaded file and 2-a content has
     * been uploaded previously).
     * 
     * @param transaction
     * @param alfClass
     * @param fileName
     * @param filePath
     * @param mimeType
     * @param shouldAppendSuffix
     * @throws ServletException
     *             if the file doesn't exist
     */
    private void uploadAttachContent(AlfrescoTransaction transaction, GenericClass alfClass, String fileName,
            String filePath, String mimeType, boolean shouldAppendSuffix) throws ServletException {
        if (filePath != null && filePath.startsWith("file:")) {
            File file;
            URI fileURI = URI.create(filePath);
            String fullFileName = fileURI.getPath();
            file = new File(fullFileName);

            if (!file.exists()) {
                if (logger.isErrorEnabled()) {
                    logger.error("The file '" + fullFileName + "' to be uploaded does not exist.");
                }
                throw new ServletException(
                        "The file to upload does not exist. Your session may have expired. Please load and submit the form again.");
            }

            if (file.length() > 0) {
                transaction.queueAttachContent(alfClass.getId(), fileName, fullFileName, mimeType,
                        shouldAppendSuffix, alfClass.getQualifiedName());
            }
        }
    }

    /**
     * Processes all upload fields on initial submission. Moves filesystem uploads to the directory,
     * stores repo uploads into the repository and attaches the (possible) node content to the
     * transaction. If the transaction fails subsequently, all uploads should be removed.
     * 
     * @param transaction
     * @param alfClass
     * @throws ServletException
     * @throws ServletException
     */
    private void uploadProcessOnSave(AlfrescoTransaction transaction, GenericClass alfClass)
            throws ServletException {
        String fileName = null;
        String filePath = null;
        String mimeType = null;

        // content file(s); these will be saved to the server's filesystem
        List<FileUploadInfoBean> fileBeans = getUploadBeansFilesystem(transaction, alfClass);
        for (FileUploadInfoBean infoBean : fileBeans) {
            fileName = infoBean.getPath();
            if (fileName.startsWith("file:")) {
                String type = alfClass.getQualifiedName();
                fileName = uploadMoveFileToDir(type, fileName, transaction);
            }
            setFileUploadFileName(fileName, infoBean.getAttribute());
        }

        // repository content file(s); these will be directly uploaded to the repository
        List<FileUploadInfoBean> repoBeans = getUploadBeansRepo(transaction, alfClass);
        for (FileUploadInfoBean infoBean : repoBeans) {
            fileName = null;
            fileName = infoBean.getName();
            filePath = infoBean.getPath();
            mimeType = infoBean.getMimeType();
            GenericAttribute attribute = infoBean.getAttribute();
            if (filePath.startsWith("file:")) {
                String location = controller.getParamUploadPathInRepository(transaction.getInitParams());
                fileName = uploadMoveFileToRepo(transaction, fileName, filePath, location, mimeType,
                        infoBean.isShouldAppendSuffix());
                if (StringUtils.trimToNull(fileName) == null) {
                    throw new ServletException(MsgPool.getMsg(MsgId.MSG_UPLOAD_FAILED));
                }
            }
            setFileUploadFileName(fileName, attribute);
        }

        // node content file; there's at most one instance of this.
        FileUploadInfoBean nodeInfoBean = getNodeContentInfo(transaction, alfClass);
        if (nodeInfoBean != null) {
            fileName = nodeInfoBean.getName();
            filePath = nodeInfoBean.getPath();
            mimeType = nodeInfoBean.getMimeType();

            uploadAttachContent(transaction, alfClass, fileName, filePath, mimeType,
                    nodeInfoBean.isShouldAppendSuffix());
        }
    }

    /**
     * Processes the upload files on update: deletes (if relevant) the old files and uploads the new
     * ones. The deletions are not actually performed here: files/nodes to be deleted are put in
     * queues.<br/>
     * <p>
     * <b>The file providing the node content is not deleted!</b> Don't know whether we should.
     * </p>
     * 
     * @param alfClass
     *            the alf class
     * @param transaction
     *            the transaction
     * 
     * @throws ServletException
     *             the alfresco controller exception
     * @throws ServletException
     */
    private void uploadProcessOnUpdate(AlfrescoTransaction transaction, GenericClass alfClass)
            throws ServletException {
        List<FileUploadInfoBean> previousFileContentInfo;
        List<FileUploadInfoBean> previousRepoContentInfo;
        List<FileUploadInfoBean> newFileContentInfo;
        List<FileUploadInfoBean> newRepoContentInfo;

        // read the old class
        GenericClass oldClass = null;
        try {
            oldClass = MappingAgent.unmarshal(controller.readObjectFromRepository(transaction, alfClass.getId()));
        } catch (JAXBException e) {
            throw new ServletException(e);
        }
        previousFileContentInfo = getUploadBeansFilesystem(transaction, oldClass);
        previousRepoContentInfo = getUploadBeansRepo(transaction, oldClass);
        newFileContentInfo = getUploadBeansFilesystem(transaction, alfClass);
        newRepoContentInfo = getUploadBeansRepo(transaction, alfClass);

        //
        // enqueue files/nodes to be deleted
        String fileName;
        FileUploadInfoBean oldBean;
        for (FileUploadInfoBean newBean : newFileContentInfo) {
            fileName = newBean.getPath();
            if (fileName != null && fileName.startsWith("file:")) { // value is being replaced
                String qualifiedName = newBean.getAttribute().getQualifiedName();
                oldBean = uploadFindBean(previousFileContentInfo, qualifiedName);
                if (oldBean != null) {
                    // we need to register as a temp file for deletion in case of success
                    transaction.registerTempFileName(
                            controller.getParamUploadPathInFileSystem(transaction.getInitParams()) + File.separator
                                    + oldBean.getPath());
                }
            }
        }
        for (FileUploadInfoBean newBean : newRepoContentInfo) {
            fileName = newBean.getPath();
            if (fileName != null && fileName.startsWith("file:")) {
                String qualifiedName = newBean.getAttribute().getQualifiedName();
                oldBean = uploadFindBean(previousRepoContentInfo, qualifiedName);
                if (oldBean != null) {
                    String oldNodeId = oldBean.getPath();
                    transaction.registerTempNodeId(oldNodeId);
                }
            }
        }

        //
        // set the new version of the object
        uploadProcessOnSave(transaction, alfClass);
    }

    /**
     * Finds the upload bean with the given attribute qname.
     * 
     * @param previousFileContentInfo
     * @param qname
     * @return the bean or null if not found
     */
    private FileUploadInfoBean uploadFindBean(List<FileUploadInfoBean> list, String qname) {
        if (list == null) {
            return null;
        }
        for (FileUploadInfoBean bean : list) {
            GenericAttribute attribute = bean.getAttribute();
            if (attribute != null) {
                if (attribute.getQualifiedName().equals(qname)) {
                    return bean;
                }
            }
        }
        return null;
    }

    /**
     * Moves the uploaded file to a randomly chosen folder under the file system upload directory.
     * 
     * @param type
     *            the type
     * @param fileName
     *            the file name
     * 
     * @return the string
     * 
     * @throws ServletException
     *             the alfresco controller exception
     */
    private String uploadMoveFileToDir(String type, String fileName, AlfrescoTransaction transaction)
            throws ServletException {
        URI fileURI = URI.create(fileName);
        File sourceFile = null;
        try { // #1160
            sourceFile = new File(fileURI);
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                String message = "XForms Controller: error when processing the file to upload. Check the path: "
                        + fileURI;
                logger.error(message, e);
            }
            return null;
        }

        currentUploadDir = controller.getParamUploadPathInFileSystem(transaction.getInitParams());
        int depth = controller.getParamUploadPathDepth(transaction.getInitParams());
        File targetFile = findNewName(depth, type, sourceFile.getName());

        copyFile(sourceFile, targetFile);
        String relativePath = targetFile.getAbsolutePath().replace(currentUploadDir.getAbsolutePath(), "");

        String outputFileName = relativePath.replace('\\', '/');
        transaction.registerUploadedFileName(outputFileName);
        transaction.registerTempFileName(sourceFile.getAbsolutePath());

        return outputFileName;
    }

    /**
     * Process repository content. Moves the uploaded file to the given location into the content
     * manager.
     * 
     * @param transaction
     * @param fileName
     *            the file name, name and extension
     * @param filePath
     *            the file system complete path to the file to be uploaded. The name and extension
     *            may be different from parameter 'fileName'.
     * @param location
     *            path to a folder in the content management system
     * @param shouldAppendSuffix
     *            if set to true, an index [e.g. '(1)'] is appended to the filename if the original
     *            filename is not available
     * @return the string
     * @throws ServletException
     *             the alfresco controller exception
     */
    private String uploadMoveFileToRepo(AlfrescoTransaction transaction, String fileName, String filePath,
            String location, String mimetype, boolean shouldAppendSuffix) throws ServletException {

        // collect parameters
        Map<String, String> parameters = new TreeMap<String, String>();
        URI fileURI = URI.create(filePath);
        String fullFileName = fileURI.getPath();

        parameters.put("filename", fileName);
        parameters.put("filepath", fullFileName);
        parameters.put("location", location);
        parameters.put("mimetype", mimetype);
        parameters.put("suffixappend", "" + shouldAppendSuffix);
        // call the webscript
        String resultId = controller.requestString(transaction, parameters, MsgId.INT_WEBSCRIPT_OPCODE_UPLOAD);

        return resultId;
    }

    /**
     * Copies the content of a source file to a target file.
     * 
     * @param sourceFile
     *            the source file
     * @param targetFile
     *            the target file
     * 
     * @throws ServletException
     *             the alfresco controller exception
     */
    private void copyFile(File sourceFile, File targetFile) throws ServletException {
        FileChannel srcChannel = null;
        FileChannel dstChannel = null;
        try {
            try {
                srcChannel = new FileInputStream(sourceFile).getChannel();
                dstChannel = new FileOutputStream(targetFile).getChannel();
                dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
            } catch (IOException e) {
                throw new ServletException(e);
            } finally {
                if (srcChannel != null)
                    srcChannel.close();
                if (dstChannel != null)
                    dstChannel.close();
            }
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    /**
     * Finds a new name for an uploaded content. The name includes the content type, the random path
     * and the original file name.
     * <p/>
     * NOTE: uploadDir must have been set.
     * 
     * @param type
     *            the type
     * @param fileName
     *            the file name
     * 
     * @return the file
     */
    private File findNewName(int depth, String type, String fileName) {
        String lFileName = fileName;
        if (lFileName.contains("\\")) {
            int lastIndexOf = StringUtils.lastIndexOf(lFileName, '\\');
            lFileName = StringUtils.substring(lFileName, lastIndexOf + 1);
        }
        String rootPath = currentUploadDir.getAbsolutePath() + File.separator + type;

        String randomPath = RandomStringUtils.randomNumeric(depth);
        for (int i = 0; i < depth; i++) {
            rootPath = rootPath + File.separator + randomPath.charAt(i);
        }

        File root = new File(rootPath);

        File result = new File(root, lFileName);
        if (result.exists()) {
            int dotPos = lFileName.lastIndexOf(".");

            String fileNameWihoutExtension = null;
            String fileNameExtension = null;

            if (dotPos == -1) {
                fileNameWihoutExtension = lFileName;
            } else {
                fileNameWihoutExtension = lFileName.substring(0, dotPos);
                fileNameExtension = lFileName.substring(dotPos + 1);
            }
            int i = 0;
            do {
                String newFileName = fileNameWihoutExtension + "-" + i;
                if (fileNameExtension != null) {
                    newFileName = newFileName + "." + fileNameExtension;
                }
                result = new File(root, newFileName);
                i++;
            } while (result.exists());
        }
        result.getParentFile().mkdirs();
        return result;
    }

    /**
     * Gets the name of all custom forms available in the mapping file.
     * 
     * @return the list
     */
    public List<String> getAllCustomForms() {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        List<String> result = new ArrayList<String>();
        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof FormType) {
                FormType elt = (FormType) element.getValue();
                result.add(elt.getName());
            }
        }
        return result;
    }

    /**
     * Gets the list of all rendered default forms. CAUTION: this function returns a subset of all
     * default forms in the mapping file. As a consequence, there may be forms XX such that
     * isDefaultForm(XX)=true whereas XX is not returned by this function.
     * 
     * @return the list
     */
    public List<String> getAllDefaultForms() {
        List<ClassType> classes = mapping.getClazz();

        List<String> result = new ArrayList<String>();
        for (ClassType clazz : classes) {
            if (clazz.isRendered()) {
                result.add(mappingToolAlfrescoToForms.getClassTypeCompleteName(clazz));
            }
        }
        return result;
    }

    /**
     * Gets the list of all search forms available in the mapping file.
     * 
     * @return the list
     */
    public List<String> getAllSearchForms() {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        List<String> result = new ArrayList<String>();
        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof SearchFormType) {
                SearchFormType elt = (SearchFormType) element.getValue();
                result.add(elt.getName());
            }
        }
        return result;
    }

    /**
     * Gets the list of all workflow forms available in the mapping file.
     * 
     * @return the list
     */
    public List<String> getAllWorkflowForms() {
        List<JAXBElement<? extends CanisterType>> elements = mapping.getCanister();

        List<String> result = new ArrayList<String>();
        for (JAXBElement<? extends CanisterType> element : elements) {
            if (element.getValue() instanceof WorkflowTaskType) {
                WorkflowTaskType elt = (WorkflowTaskType) element.getValue();
                result.add(elt.getName());
            }
        }
        return result;
    }

    public boolean isCustomForm(String formName) {
        FormType form = getFormType(formName);
        return (form != null);
    }

    public boolean isDefaultForm(String formName) {
        ClassType form = getClassType(formName);
        return (form != null);
    }

    public boolean isSearchForm(String formName) {
        SearchFormType form = getSearchType(formName);
        return (form != null);
    }

    public boolean isWorkflowForm(String formName) {
        WorkflowTaskType form = getWorkflowTaskType(formName, false);
        return (form != null);
    }

    public String getXtensionFailurePage(String formName) {
        return mappingToolAlfrescoToForms.getXtensionFailurePage(formName);
    }

    public String getXtensionSuccessPage(String formName) {
        return mappingToolAlfrescoToForms.getXtensionSuccessPage(formName);
    }

    public String getXtensionNextPageCancel(String formName, FormTypeEnum formTypeEnum) {
        return mappingToolAlfrescoToForms.getXtensionNextPageCancel(formName, formTypeEnum);
    }

    public String getXtensionNextPageDelete(String formName, FormTypeEnum formTypeEnum) {
        return mappingToolAlfrescoToForms.getXtensionNextPageDelete(formName, formTypeEnum);
    }

    public String getXtensionNextPageSubmit(String formName, FormTypeEnum formTypeEnum) {
        return mappingToolAlfrescoToForms.getXtensionNextPageSubmit(formName, formTypeEnum);
    }

    public boolean getXtensionSkipAdditionalInfo(String formName, FormTypeEnum formTypeEnum) {
        return mappingToolAlfrescoToForms.getXtensionSkipAdditionalInfo(formName, formTypeEnum);
    }

}