org.openbravo.service.db.DataImportService.java Source code

Java tutorial

Introduction

Here is the source code for org.openbravo.service.db.DataImportService.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-2014 Openbravo SLU 
 * All Rights Reserved. 
 * Contributor(s):  ______________________________________.
 ************************************************************************
 */

package org.openbravo.service.db;

import java.io.Reader;
import java.sql.BatchUpdateException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
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.provider.OBProvider;
import org.openbravo.base.provider.OBSingleton;
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.base.validation.ValidationException;
import org.openbravo.dal.core.DalUtil;
import org.openbravo.dal.core.OBContext;
import org.openbravo.dal.core.SessionHandler;
import org.openbravo.dal.core.TriggerHandler;
import org.openbravo.dal.service.OBDal;
import org.openbravo.dal.xml.EntityResolver;
import org.openbravo.dal.xml.EntityResolver.ResolvingMode;
import org.openbravo.dal.xml.EntityXMLProcessor;
import org.openbravo.dal.xml.PrimitiveReferenceHandler;
import org.openbravo.dal.xml.StaxXMLEntityConverter;
import org.openbravo.dal.xml.XMLEntityConverter;
import org.openbravo.model.ad.access.Role;
import org.openbravo.model.ad.access.RoleOrganization;
import org.openbravo.model.ad.datamodel.Table;
import org.openbravo.model.ad.module.Module;
import org.openbravo.model.ad.system.Client;
import org.openbravo.model.ad.utility.ReferenceDataStore;
import org.openbravo.model.ad.utility.TreeNode;
import org.openbravo.model.common.enterprise.Organization;

/**
 * Imports business objects from XML. The business objects can be imported in a specific Client and
 * Organization or as a complete Client import.
 * 
 * @author Martin Taal
 */
public class DataImportService implements OBSingleton {
    private static final Logger log = Logger.getLogger(DataImportService.class);

    private static DataImportService instance;

    public static synchronized DataImportService getInstance() {
        if (instance == null) {
            instance = OBProvider.getInstance().get(DataImportService.class);
        }
        return instance;
    }

    public static synchronized void setInstance(DataImportService instance) {
        DataImportService.instance = instance;
    }

    /**
     * Imports the business objects using the client/organization. If there are no error messages and
     * no Exception occurred then the import method will persist the imported business objects.
     * However, the import method does not do a commit. It is the callers responsibility to call
     * commit (if the ImportResult does not contain error messages).
     * 
     * @param client
     *          the client in which the business objects are created/updated
     * @param organization
     *          the organization in which the business objects are created/updated
     * @param xml
     *          the xml containing the data
     * @return the ImportResult object contains error, log and warning messages and lists with the
     *         inserted and updated business objects
     */
    public ImportResult importDataFromXML(Client client, Organization organization, String xml) {
        return importDataFromXML(client, organization, xml, null);
    }

    /**
     * Imports the business objects using the client/organization. If there are no error messages and
     * no Exception occurred then the import method will persist the imported business objects.
     * However, the import method does not do a commit. It is the callers responsibility to call
     * commit (if the ImportResult does not contain error messages).
     * 
     * @param client
     *          the client in which the import takes place
     * @param organization
     *          the organization in which the business objects are created
     * @param xml
     *          the xml containing the data
     * @param module
     *          the module is used to update the AD_REF_DATA_LOADED table during the import action
     * @return the result of the import (error, log and warning messages, to-be-inserted and
     *         to-be-updated business objects
     */
    public ImportResult importDataFromXML(Client client, Organization organization, String xml, Module module,
            boolean filterOrganizations) {
        try {
            final Document doc = DocumentHelper.parseText(xml);
            return importDataFromXML(client, organization, doc, true, module, null, false, false,
                    filterOrganizations);
        } catch (final Exception e) {
            throw new OBException(e);
        }
    }

    public ImportResult importDataFromXML(Client client, Organization organization, String xml, Module module) {
        boolean filterOrganizations = false;
        return importDataFromXML(client, organization, xml, module, filterOrganizations);
    }

    /**
     * Imports a complete client. This import method behaves slightly differently than the other
     * import methods because it does not use the client/organization of the current user but uses the
     * client and organization of the data in the import file itself. In addition no unique-constraint
     * checking is done.
     * 
     * @param importProcessor
     *          the import processor which is called for each object after the import
     * @param importAuditInfo
     *          if true then the auditInfo (updated, updatedBy etc.) is also imported.
     * @param reader
     *          the xml stream
     * 
     * @return ImportResult which contains the updated/inserted objects and log and error messages
     * 
     * @see #importDataFromXML(Client, Organization, String)
     */
    public ImportResult importClientData(EntityXMLProcessor importProcessor, boolean importAuditInfo,
            Reader reader) {
        try {

            final ImportResult ir = new ImportResult();

            boolean rolledBack = false;
            OBContext.setAdminMode();
            try {
                // disable the triggers to prevent unexpected extra db actions
                // during import
                TriggerHandler.getInstance().disable();

                final StaxXMLEntityConverter xec = StaxXMLEntityConverter.newInstance();
                xec.setOptionClientImport(true);
                xec.setOptionImportAuditInfo(importAuditInfo);
                xec.getEntityResolver().setOptionCreateReferencedIfNotFound(true);
                xec.setEntityResolver(ClientImportEntityResolver.getInstance());
                xec.setImportProcessor(importProcessor);
                xec.process(reader);

                ir.setLogMessages(xec.getLogMessages());
                ir.setErrorMessages(xec.getErrorMessages());
                ir.setWarningMessages(xec.getWarningMessages());

                if (ir.hasErrorOccured()) {
                    OBDal.getInstance().rollbackAndClose();
                    rolledBack = true;
                    return ir;
                }

                if (importProcessor != null) {
                    try {
                        importProcessor.process(xec.getToInsert(), xec.getToUpdate());
                    } catch (final Exception e) {
                        // note on purpose caught and set in ImportResult
                        ir.setException(e);
                        ir.setErrorMessages(e.getMessage());
                        OBDal.getInstance().rollbackAndClose();
                        rolledBack = true;
                        return ir;
                    }
                }

                // validate the objects
                for (BaseOBObject bob : xec.getToInsert()) {
                    validateObject(bob, ir);
                }
                for (BaseOBObject bob : xec.getToUpdate()) {
                    validateObject(bob, ir);
                }

                if (ir.hasErrorOccured()) {
                    OBDal.getInstance().rollbackAndClose();
                    rolledBack = true;
                    return ir;
                }

                // now save and update
                // do inserts and updates in opposite order, this is important
                // so that the objects on which other depend are inserted first
                final List<BaseOBObject> toInsert = xec.getToInsert();
                int done = 0;
                final Set<BaseOBObject> inserted = new HashSet<BaseOBObject>();
                for (int i = toInsert.size() - 1; i > -1; i--) {
                    final BaseOBObject ins = toInsert.get(i);
                    insertObjectGraph(ins, inserted, new HashSet<BaseOBObject>());
                    ir.getInsertedObjects().add(ins);
                    done++;
                }
                Check.isTrue(done == toInsert.size(),
                        "Not all objects have been inserted, check for loop: " + done + "/" + toInsert.size());

                // flush to set the ids in the objects
                OBDal.getInstance().flush();

                // do the updates the other way around also
                done = 0;
                final List<BaseOBObject> toUpdate = xec.getToUpdate();
                for (int i = toUpdate.size() - 1; i > -1; i--) {
                    final BaseOBObject upd = toUpdate.get(i);
                    OBDal.getInstance().save(upd);
                    ir.getUpdatedObjects().add(upd);
                    done++;
                }
                Check.isTrue(done == toUpdate.size(),
                        "Not all objects have been inserted, check for loop: " + done + "/" + toUpdate.size());

                // flush to set the ids in the objects
                OBDal.getInstance().flush();

                // now walk through the objects to repair primitive reference id's
                // note updates both the ir and changes the entityresolver
                repairPrimitiveReferences(ir, xec.getEntityResolver());

                if (ir.hasErrorOccured()) {
                    OBDal.getInstance().rollbackAndClose();
                    rolledBack = true;
                    return ir;
                }

                OBDal.getInstance().flush();
            } catch (final Throwable t) {
                OBDal.getInstance().rollbackAndClose();
                rolledBack = true;
                final Throwable realThrowable = DbUtility.getUnderlyingSQLException(t);
                log.error(realThrowable.getMessage(), realThrowable);
                ir.setException(realThrowable);
            } finally {
                OBContext.restorePreviousMode();
                if (rolledBack) {
                    TriggerHandler.getInstance().clear();
                } else if (TriggerHandler.getInstance().isDisabled()) {
                    TriggerHandler.getInstance().enable();

                    // do a special thing to update the clientlist and orglist columns
                    // in the ad_role table with the newly created id's
                    // this is done through a stored procedure
                    SessionHandler.getInstance().getSession()
                            .createSQLQuery("UPDATE AD_ROLE_ORGACCESS SET AD_ROLE_ID='0' where AD_ROLE_ID='0'")
                            .executeUpdate();
                }
            }

            return ir;
        } catch (final Exception e) {
            throw new OBException(e);
        }
    }

    // the id's which are repaired are so-called primitive references
    // (for example AD_Tree_Node.node_id
    // these are references without foreign key which are modeled as a
    // string/varchar. During the import the id of an object may change, thereby
    // invalidating the primitive reference (which still uses the old id). This
    // method repairs those ids.
    // see the methods in XMLUtil related to primitive references
    private void repairPrimitiveReferences(ImportResult ir, EntityResolver entityResolver) {

        // at this point all references must exist
        entityResolver.setResolvingMode(ResolvingMode.MUST_EXIST);
        final List<BaseOBObject> repairReferences = new ArrayList<BaseOBObject>();
        for (BaseOBObject bob : ir.getUpdatedObjects()) {
            if (PrimitiveReferenceHandler.getInstance().hasObjectPrimitiveReference(bob)) {
                repairReferences.add(bob);
            }
        }
        for (BaseOBObject bob : ir.getInsertedObjects()) {
            if (PrimitiveReferenceHandler.getInstance().hasObjectPrimitiveReference(bob)) {
                repairReferences.add(bob);
            }
        }
        for (BaseOBObject objectToRepair : repairReferences) {
            if (objectToRepair instanceof TreeNode) {
                final TreeNode tn = (TreeNode) objectToRepair;
                Entity entity = ModelProvider.getInstance().getEntityFromTreeType(tn.getTree().getTypeArea());
                if (entity == null && tn.getTree().getTable() != null) {
                    entity = ModelProvider.getInstance()
                            .getEntityByTableId((String) DalUtil.getId(tn.getTree().getTable()));
                }
                if (entity == null) {
                    String msg = "Imported tree nodes belong to a tree  " + tn.getTree()
                            + " which is not related to any entity.";
                    if (ir.getWarningMessages() == null) {
                        ir.setWarningMessages(msg);
                    } else {
                        ir.setWarningMessages(ir.getWarningMessages() + "\n" + msg);
                    }
                    continue;
                }
            }
            for (Property p : PrimitiveReferenceHandler.getInstance()
                    .getPrimitiveReferences(objectToRepair.getEntity())) {
                final String value = (String) objectToRepair.get(p.getName());
                // also ignore 0 as there is a business partner tree node with 0
                if (value != null && !value.equals("0")) {
                    final Entity entity = PrimitiveReferenceHandler.getInstance()
                            .getPrimitiveReferencedEntity(objectToRepair, p);

                    final BaseOBObject referencedBob = entityResolver.resolve(entity.getName(), value, true);

                    if (referencedBob == null || referencedBob.getId() == null) {
                        if (ir.getErrorMessages() == null) {
                            ir.setErrorMessages("The object " + objectToRepair + " references an object (entity: "
                                    + entity + ") with id " + value
                                    + " which does not exist in the database or in the import set.");
                        } else {
                            ir.setErrorMessages(ir.getErrorMessages() + "\nThe object " + objectToRepair
                                    + " references an object (entity: " + entity + ") with id " + value
                                    + " which does not exist in the database or in the import set.");
                        }
                    } else if (!referencedBob.getId().equals(value)) {
                        objectToRepair.set(p.getName(), referencedBob.getId());
                    }
                }
            }
        }

    }

    private void validateObject(BaseOBObject bob, ImportResult ir) {
        int i = 0;
        final StringBuilder msgs = new StringBuilder();
        for (Property p : bob.getEntity().getProperties()) {
            if (p.isOneToMany() || p.isAuditInfo() || p.isClientOrOrganization() || p.isComputedColumn()) {
                continue;
            }
            final Object value = bob.get(p.getName());
            try {
                p.checkIsValidValue(value);
            } catch (ValidationException ve) {
                i++;
                // stop at 100 errors
                if (i > 100) {
                    break;
                }
                if (msgs.length() > 0) {
                    msgs.append("\n");
                }
                msgs.append("Object " + bob + " table/column: " + p.getEntity().getTableName() + "."
                        + p.getColumnName() + " " + ve.getMessage());
            }
        }
        if (msgs.length() > 0) {
            if (ir.getErrorMessages() == null) {
                ir.setErrorMessages(msgs.toString());
            } else {
                ir.setErrorMessages(ir.getErrorMessages() + msgs);
            }
        }
    }

    private ImportResult importDataFromXML(Client client, Organization organization, Document doc,
            boolean createReferencesIfNotFound, Module module, EntityXMLProcessor importProcessor,
            boolean isClientImport, boolean importAuditInfo, boolean filterOrganizations) {

        if (!isClientImport) {
            log.debug("Importing data for client " + client.getId()
                    + (organization != null ? "/" + organization.getId() : ""));
        }

        final ImportResult ir = new ImportResult();

        boolean rolledBack = false;
        List<BaseOBObject> listNew = new Vector<BaseOBObject>();
        List<BaseOBObject> listChanged = new Vector<BaseOBObject>();
        try {
            // disable the triggers to prevent unexpected extra db actions
            // during import
            TriggerHandler.getInstance().disable();

            final XMLEntityConverter xec = XMLEntityConverter.newInstance();
            xec.setClient(client);
            xec.setOrganization(organization);
            xec.setOptionClientImport(isClientImport);
            xec.setOptionImportAuditInfo(importAuditInfo);
            xec.getEntityResolver().setOptionCreateReferencedIfNotFound(createReferencesIfNotFound);

            listNew = xec.getToInsert();
            listChanged = xec.getToUpdate();
            if (isClientImport) {
                xec.setEntityResolver(ClientImportEntityResolver.getInstance());
            }
            xec.setImportProcessor(importProcessor);
            xec.process(doc, filterOrganizations);

            ir.setLogMessages(xec.getLogMessages());
            ir.setErrorMessages(xec.getErrorMessages());
            ir.setWarningMessages(xec.getWarningMessages());

            if (ir.hasErrorOccured()) {
                OBDal.getInstance().rollbackAndClose();
                rolledBack = true;
                return ir;
            }

            if (importProcessor != null) {
                try {
                    importProcessor.process(xec.getToInsert(), xec.getToUpdate());
                } catch (final Exception e) {
                    // note on purpose caught and set in ImportResult
                    ir.setException(e);
                    ir.setErrorMessages(e.getMessage());
                    OBDal.getInstance().rollbackAndClose();
                    rolledBack = true;
                    return ir;
                }
            }

            // note the ir object is adapted in this call
            saveUpdateConvertedObjects(xec, ir, isClientImport, module);

            // did an error occur during the saveUpdate
            if (ir.hasErrorOccured()) {
                OBDal.getInstance().rollbackAndClose();
                rolledBack = true;
                return ir;
            }
        } catch (final Throwable t) {
            boolean isBatchUpdateException = false;
            // We need to capture nested exception of BatchUpdate exception.
            Throwable cause = t.getCause();
            if (cause instanceof BatchUpdateException) {
                BatchUpdateException batchUpdateException = (BatchUpdateException) cause;
                if (batchUpdateException.getNextException() != null) {
                    String errorMessage = batchUpdateException.getNextException().getMessage();
                    String messageKey = errorMessage.substring("ERROR:".length()).trim();
                    isBatchUpdateException = true;
                    ir.setErrorMessages("isBatchUpdateException:" + messageKey);
                }
            }
            OBDal.getInstance().rollbackAndClose();
            rolledBack = true;
            if (!isBatchUpdateException) {
                ir.setException(t);
            }

        } finally {
            if (rolledBack) {
                TriggerHandler.getInstance().clear();
            } else if (TriggerHandler.getInstance().isDisabled()) {
                TriggerHandler.getInstance().enable();
                Vector<BaseOBObject> allObjs = new Vector<BaseOBObject>();
                allObjs.addAll(listNew);
                allObjs.addAll(listChanged);
                boolean containsAdRoleOrOrgAccess = false;
                for (BaseOBObject bob : allObjs) {
                    if (bob instanceof Role || bob instanceof RoleOrganization) {
                        containsAdRoleOrOrgAccess = true;
                        break;
                    }
                }
                if (containsAdRoleOrOrgAccess)
                    SessionHandler.getInstance().getSession()
                            .createSQLQuery("UPDATE AD_ROLE_ORGACCESS SET AD_ROLE_ID='0' where AD_ROLE_ID='0'")
                            .executeUpdate();
            }
        }

        if (ir.hasErrorOccured()) {
            SessionHandler.getInstance().setDoRollback(true);
        }

        return ir;
    }

    /**
     * Performs the actual update/insert of objects in the correct order in the database. Also sets
     * the {@link ReferenceDataStore} if required.
     * 
     * @param xec
     *          the converter containing the to-be-inserted and to-be-updated objects
     * @param ir
     *          the ImportResult, warning and error messages are added to this object, also its
     *          {@link ImportResult#getUpdatedObjects()} and {@link ImportResult#getInsertedObjects()}
     *          is set
     * @param isClientImport
     *          is true if the import is for a client, in that case the {@link ReferenceDataStore}
     *          data is not set
     * @param module
     *          is set if the import is for a module, null is allowed
     */
    public void saveUpdateConvertedObjects(XMLEntityConverter xec, ImportResult ir, boolean isClientImport,
            Module module) {
        // now save and update
        // do inserts and updates in opposite order, this is important
        // so that the objects on which other depend are inserted first
        final List<BaseOBObject> toInsert = xec.getToInsert();
        int done = 0;
        final Set<BaseOBObject> inserted = new HashSet<BaseOBObject>();
        for (int i = toInsert.size() - 1; i > -1; i--) {
            final BaseOBObject ins = toInsert.get(i);
            // for (final BaseOBObject ins : toInsert) {
            insertObjectGraph(ins, inserted, new HashSet<BaseOBObject>());
            ir.getInsertedObjects().add(ins);
            done++;
        }
        Check.isTrue(done == toInsert.size(),
                "Not all objects have been inserted, check for loop: " + done + "/" + toInsert.size());

        // flush to set the ids in the objects
        OBDal.getInstance().flush();

        // do the updates the other way around also
        done = 0;
        final List<BaseOBObject> toUpdate = xec.getToUpdate();
        for (int i = toUpdate.size() - 1; i > -1; i--) {
            final BaseOBObject upd = toUpdate.get(i);
            OBDal.getInstance().save(upd);
            ir.getUpdatedObjects().add(upd);
            done++;
        }
        Check.isTrue(done == toUpdate.size(),
                "Not all objects have been inserted, check for loop: " + done + "/" + toUpdate.size());

        // flush to set the ids in the objects
        OBDal.getInstance().flush();

        // now walk through the objects to repair primitive reference id's
        // note updates both the ir and changes the entityresolver
        repairPrimitiveReferences(ir, xec.getEntityResolver());

        OBDal.getInstance().flush();

        // store the ad_ref_data_loaded
        if (!isClientImport) {
            OBContext.setAdminMode();
            try {
                for (final BaseOBObject ins : xec.getToInsert()) {
                    final String originalId = xec.getEntityResolver().getOriginalId(ins);
                    // completely new object, manually added to the xml
                    if (originalId == null) {
                        continue;
                    }
                    final ReferenceDataStore rdl = OBProvider.getInstance().get(ReferenceDataStore.class);
                    if (ins instanceof ClientEnabled) {
                        rdl.setClient(((ClientEnabled) ins).getClient());
                    }
                    if (ins instanceof OrganizationEnabled) {
                        rdl.setOrganization(((OrganizationEnabled) ins).getOrganization());
                    }
                    rdl.setGeneric(originalId);
                    rdl.setSpecific((String) ins.getId());
                    rdl.setTable(OBDal.getInstance().get(Table.class, ins.getEntity().getTableId()));
                    if (module != null) {
                        rdl.setModule(module.getId());
                    }
                    OBDal.getInstance().save(rdl);
                }
                OBDal.getInstance().flush();
            } finally {
                OBContext.restorePreviousMode();
            }
        }
    }

    // insert an object and all its many-to-one dependencies
    // which have not been inserted yet
    // this works fine as long the graph has no cycles
    // if there are cycles then Hibernate needs to resolve those
    private void insertObjectGraph(BaseOBObject toInsert, Set<BaseOBObject> inserted,
            Set<BaseOBObject> cycleDetect) {
        // prevent infinite looping and don't do the ones we already inserted
        // in a previous objectgraph
        if (cycleDetect.contains(toInsert)) {
            // just save it for now and hope for the best...
            try {
                OBDal.getInstance().save(toInsert);
            } catch (final Exception e) {
                log.warn("There was a problem inserting data in the database.");
                log.info("The following exception was raised: ", e);
                throw new OBException(e);
            }
            cycleDetect.remove(toInsert);
            return;
        }
        if (inserted.contains(toInsert)) {
            return;
        }
        inserted.add(toInsert);
        cycleDetect.add(toInsert);
        final Entity entity = toInsert.getEntity();
        for (final Property property : entity.getProperties()) {
            if (!property.isPrimitive() && !property.isOneToMany()) {
                final Object value = toInsert.get(property.getName());
                if (value instanceof BaseOBObject && ((BaseOBObject) value).isNewOBObject()) {
                    insertObjectGraph((BaseOBObject) value, inserted, cycleDetect);
                }
            }
        }
        cycleDetect.remove(toInsert);
        try {
            OBDal.getInstance().save(toInsert);
        } catch (final Exception e) {
            log.warn("There was a problem inserting data in the database.");
            log.info("The following exception was raised: ", e);
            throw new OBException(e);
        }
    }
}