org.openbravo.dal.xml.XMLEntityConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.dal.xml.XMLEntityConverter.java

Source

/*
 *************************************************************************
 * The contents of this file are subject to the Openbravo  Public  License
 * Version  1.1  (the  "License"),  being   the  Mozilla   Public  License
 * Version 1.1  with a permitted attribution clause; you may not  use this
 * file except in compliance with the License. You  may  obtain  a copy of
 * the License at http://www.openbravo.com/legal/license.html 
 * Software distributed under the License  is  distributed  on  an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific  language  governing  rights  and  limitations
 * under the License. 
 * The Original Code is Openbravo ERP. 
 * The Initial Developer of the Original Code is Openbravo SLU 
 * All portions are Copyright (C) 2008-2015 Openbravo SLU 
 * All Rights Reserved. 
 * Contributor(s):  ______________________________________.
 ************************************************************************
 */

package org.openbravo.dal.xml;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.openbravo.base.exception.OBException;
import org.openbravo.base.model.Entity;
import org.openbravo.base.model.ModelProvider;
import org.openbravo.base.model.Property;
import org.openbravo.base.model.domaintype.PrimitiveDomainType;
import org.openbravo.base.provider.OBProvider;
import org.openbravo.base.structure.BaseOBObject;
import org.openbravo.base.structure.ClientEnabled;
import org.openbravo.base.structure.OrganizationEnabled;
import org.openbravo.base.util.Check;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.security.SecurityChecker;
import org.openbravo.model.ad.system.Client;
import org.openbravo.model.common.enterprise.Organization;

/**
 * Converts a XML string to an objectgraph with objects using Dom4j. This XMLEntityConverter can be
 * used for smaller xml strings (less than 100mb). This XMLEntityConverter can handle OneToMany
 * relations, this in contrast to the {@link StaxXMLEntityConverter}. During the XML parse phase
 * this converter will match XML tags with new or existing (in the database) business objects. The
 * matching logic is implemented in the {@link EntityResolver}.
 * <p/>
 * The XMLEntityConverter keeps track of which objects are new, which exist but do not need to be
 * updated or which objects exist but need to be updated.
 * <p/>
 * This converter does not update the database directly. However, it changes the properties of
 * existing objects. This means that a commit after calling the process method on the converter can
 * result in database updates by Hibernate.
 * 
 * @see Entity
 * 
 * @author mtaal
 */

public class XMLEntityConverter extends BaseXMLEntityConverter {
    // This class should translate the

    private static final Logger log = Logger.getLogger(EntityXMLConverter.class);

    public static XMLEntityConverter newInstance() {
        return OBProvider.getInstance().get(XMLEntityConverter.class);
    }

    private boolean isForDefaultValues;

    /**
     * The main entry point. This method creates a Dom4j Document and then calls
     * {@link #process(Document)}.
     * 
     * @param xml
     *          the xml string
     * @return the list of BaseOBObject present in the root of the xml. This list contains the
     *         to-be-updated, to-be-inserted as well as the unchanged business objects
     */
    public List<BaseOBObject> process(String xml) {
        try {
            final Document doc = DocumentHelper.parseText(xml);
            final List<BaseOBObject> result = process(doc);
            return result;
        } catch (final Exception e) {
            throw new EntityXMLException(e);
        }
    }

    /**
     * The main entry point. This method walks through the elements in the root and parses them. The
     * children of a business object (in the xml) are also parsed. Referenced objects are resolved
     * through the {@link EntityResolver}.
     * <p/>
     * After a call to this method the to-be-inserted objects can be retrieved through the
     * {@link #getToInsert()} method and the to-be-updated objects through the {@link #getToUpdate()}
     * method.
     * 
     * @param doc
     *          the dom4j Document to process
     * @return the list of BaseOBObject present in the root of the xml. This list contains the
     *         to-be-updated, to-be-inserted as well as the unchanged business objects
     */
    public List<BaseOBObject> process(Document doc, boolean filterOrganizations) {
        clear();
        getEntityResolver().setClient(getClient());
        getEntityResolver().setOrganization(getOrganization());

        // check that the rootelement is the openbravo one
        final Element rootElement = doc.getRootElement();
        if (!rootElement.getName().equals(XMLConstants.OB_ROOT_ELEMENT)) {
            throw new OBException("Root tag of the xml document should be: " + XMLConstants.OB_ROOT_ELEMENT
                    + ", but it is " + rootElement.getName());
        }

        String isForDefaultsStr = rootElement.attributeValue(XMLConstants.DEFAULT_VALUES_DATA);
        isForDefaultValues = isForDefaultsStr != null ? Boolean.parseBoolean(isForDefaultsStr) : false;

        // walk through the elements
        final Set<BaseOBObject> checkDuplicates = new HashSet<BaseOBObject>();
        final List<BaseOBObject> result = new ArrayList<BaseOBObject>();
        for (final Object o : rootElement.elements()) {
            final Element element = (Element) o;
            final BaseOBObject bob = processEntityElement(element.getName(), element, false, filterOrganizations);
            // only add it if okay
            if (bob != null && !checkDuplicates.contains(bob)) {
                result.add(bob);
                checkDuplicates.add(bob);
            }
        }
        repairReferences();
        checkDanglingObjects();
        return result;
    }

    public List<BaseOBObject> process(Document doc) {
        boolean filterOrganizations = false;
        return process(doc, filterOrganizations);
    }

    // processes a xml tag which denotes an instance of a business object
    @SuppressWarnings("unchecked")
    private BaseOBObject processEntityElement(String entityName, Element obElement, boolean theReferenced,
            boolean filterOrganizations) {
        // note: referenced is true for both childs and many-to-one references
        // it is passed to the entityresolver to allow searches in other
        // organization

        // note id maybe null for new objects
        final String id = obElement.attributeValue(XMLConstants.ID_ATTRIBUTE);
        if (entityName == null) {
            error("Element " + obElement.getName() + " has no entityname attribute, not processing it");
            return null;
        }
        try {
            log.debug("Converting entity " + entityName);
            final boolean hasReferenceAttribute = obElement
                    .attributeValue(XMLConstants.REFERENCE_ATTRIBUTE) != null;

            // resolve the entity, using the id, note that
            // resolve will create a new object if none is found
            BaseOBObject bob = resolve(entityName, id, false, filterOrganizations);

            // should never be null at this point
            Check.isNotNull(bob, "The business object " + entityName + " (" + id + ") can not be resolved");

            // warn/error is logged below if the entity is updated
            // update is prevented below
            final boolean writable = OBContext.getOBContext().isInAdministratorMode() || bob.isNewOBObject()
                    || SecurityChecker.getInstance().isWritable(bob);

            // referenced and not new, so already there, don't update
            if (hasReferenceAttribute && !bob.isNewOBObject()) {
                return bob;
            }

            // importing only new rows and not updating existent ones
            if (isForDefaultValues && !bob.isNewOBObject()) {
                return bob;
            }

            // do some checks to determine if this one should be updated
            // a referenced instance should not be updated if it is not new
            // note that embedded children are updated but non-embedded children
            // are not updated!
            // note the !bob.isNewOBObject() is here because at the end
            // after setting the properties (and organization) there is again a writable check
            // which catches this one
            final boolean preventRealUpdate = (!writable && !bob.isNewOBObject())
                    || (hasReferenceAttribute && !bob.isNewOBObject());

            final Entity entity = ModelProvider.getInstance().getEntity(obElement.getName());
            boolean updated = false;

            // check if there is an organization set
            final Organization originalResolvingOrganization = getEntityResolver().getOrganization();
            if (bob instanceof OrganizationEnabled) {
                Organization objectOrganization = null;
                if (bob.isNewOBObject()) {
                    for (final Element childElement : (List<Element>) obElement.elements()) {
                        if (childElement.getName().equals(Client.PROPERTY_ORGANIZATION)) {
                            // found
                            final String refId = childElement.attributeValue(XMLConstants.ID_ATTRIBUTE);
                            objectOrganization = (Organization) resolve(Organization.ENTITY_NAME, refId, true,
                                    filterOrganizations);
                        }
                    }
                } else {
                    objectOrganization = ((OrganizationEnabled) bob).getOrganization();
                }
                // ok use the organization of the object itself to
                // search for referenced objects
                if (objectOrganization != null) {
                    getEntityResolver().setOrganization(objectOrganization);
                }
            }

            try {
                // now parse the property elements
                for (final Element childElement : (List<Element>) obElement.elements()) {
                    final Property p = entity.getProperty(childElement.getName());
                    log.debug(">>> Exporting property " + p.getName());

                    // TODO: make this option controlled
                    final boolean isNotImportableProperty = p.isTransient(bob)
                            || (p.isAuditInfo() && !isOptionImportAuditInfo()) || p.isInactive() || p.isProxy()
                            || p.getEntity().isVirtualEntity() || p.isComputedColumn();

                    if (isNotImportableProperty) {
                        log.debug("Property " + p + " is inactive, transient or auditinfo, " + "ignoring it");
                        continue;
                    }

                    // ignore the id properties as they are already set, or should
                    // not be set
                    if (p.isId()) {
                        continue;
                    }

                    final Object currentValue = bob.get(p.getName());

                    // do the primitive values
                    if (p.isPrimitive()) {
                        Object newValue = ((PrimitiveDomainType) p.getDomainType())
                                .createFromString(childElement.getText());

                        // correct the value
                        newValue = replaceValue(bob, p, newValue);

                        log.debug("Primitive property with value " + newValue);

                        // only update if changed
                        if ((currentValue == null && newValue != null) || (currentValue != null && newValue == null)
                                || (currentValue != null && newValue != null && !currentValue.equals(newValue))) {
                            log.debug("Value changed setting it");
                            if (!preventRealUpdate) {
                                bob.set(p.getName(), newValue);
                                updated = true;
                            }
                        }
                    } else if (p.isOneToMany() && p.isChild()) {
                        // resolve the content of the list, but do not change the list
                        for (final Object o : childElement.elements()) {
                            final Element listElement = (Element) o;
                            processEntityElement(listElement.getName(), listElement, true, filterOrganizations);
                        }
                    } else {
                        Check.isTrue(!p.isOneToMany(), "One to many property not allowed here");

                        // determine the referenced entity
                        Object newValue;

                        // handle null value
                        if (childElement.attribute(XMLConstants.ID_ATTRIBUTE) == null) {
                            newValue = null;
                        } else {
                            // get the info and resolve the reference
                            final String refId = childElement.attributeValue(XMLConstants.ID_ATTRIBUTE);
                            final String refEntityName = p.getTargetEntity().getName();
                            newValue = resolve(refEntityName, refId, true, filterOrganizations);
                        }
                        newValue = replaceValue(bob, p, newValue);

                        // never update the org or client through xml!
                        final boolean clientUpdate = bob instanceof ClientEnabled
                                && p.getName().equals(Organization.PROPERTY_CLIENT);
                        final boolean orgUpdate = bob instanceof OrganizationEnabled
                                && p.getName().equals(Client.PROPERTY_ORGANIZATION);

                        boolean isAllowedUpdate = false;
                        if (orgUpdate) {
                            isAllowedUpdate = bob.isNewOBObject()
                                    && isWritableOrganization((BaseOBObject) newValue);
                        }

                        if (!isAllowedUpdate && !isOptionClientImport() && currentValue != null
                                && (clientUpdate || orgUpdate)) {
                            continue;
                        }

                        final boolean hasChanged = (currentValue == null && newValue != null)
                                || (currentValue != null && newValue == null)
                                || (currentValue != null && newValue != null && !currentValue.equals(newValue));
                        if (hasChanged) {
                            log.debug("Setting value " + newValue);
                            if (!preventRealUpdate) {
                                bob.set(p.getName(), newValue);
                                updated = true;
                            }
                        }
                    }
                }

                // now do this check as the object now has a correct organization
                if (bob.isNewOBObject()) {
                    final boolean recheckedWritable = OBContext.getOBContext().isInAdministratorMode()
                            || SecurityChecker.getInstance().isWritable(bob);
                    if (!recheckedWritable) {
                        error("Object " + entityName + "(" + id + ") is new but not writable");
                        return bob;
                    }
                }

                // do the unique constraint matching here
                // this check can not be done earlier because
                // earlier no properties are set for a new object
                bob = replaceByUniqueObject(bob);

                // add to the correct list on the basis of different characteristics
                addToInsertOrUpdateLists(id, bob, writable, updated, hasReferenceAttribute, preventRealUpdate);

                // do a check that in case of a client/organization import that the
                // client and organization are indeed set
                if (isOptionClientImport()) {
                    checkClientOrganizationSet(bob);
                }

                return bob;
            } finally {
                getEntityResolver().setOrganization(originalResolvingOrganization);
            }
        } catch (final Exception e) {
            error("Exception when parsing entity " + entityName + " (" + id + "):" + e.getMessage());
            log.error("Exception when parsing entity " + entityName + " (" + id + ")", e);
            return null;
        }
    }

    private boolean isWritableOrganization(BaseOBObject bob) {
        final String bobId = (String) bob.getId();
        if (bobId == null) {
            return false;
        }
        for (String orgId : OBContext.getOBContext().getWritableOrganizations()) {
            if (orgId.equals(bobId)) {
                return true;
            }
        }
        return false;
    }
}