Java tutorial
/* ************************************************************************* * 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-2011 Openbravo SLU * All Rights Reserved. * Contributor(s): ______________________________________. ************************************************************************ */ package org.openbravo.dal.xml; import static org.openbravo.model.ad.system.Client.PROPERTY_ORGANIZATION; import static org.openbravo.model.common.enterprise.Organization.PROPERTY_CLIENT; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.criterion.Restrictions; import org.openbravo.base.exception.OBException; import org.openbravo.base.model.AccessLevel; import org.openbravo.base.model.Entity; import org.openbravo.base.model.ModelProvider; import org.openbravo.base.model.Property; import org.openbravo.base.model.UniqueConstraint; import org.openbravo.base.provider.OBNotSingleton; import org.openbravo.base.provider.OBProvider; import org.openbravo.base.structure.BaseOBObject; import org.openbravo.base.util.Check; import org.openbravo.dal.core.OBContext; import org.openbravo.dal.security.OrganizationStructureProvider; import org.openbravo.dal.service.OBCriteria; import org.openbravo.dal.service.OBDal; import org.openbravo.model.ad.datamodel.Table; import org.openbravo.model.ad.system.Client; import org.openbravo.model.ad.utility.ReferenceDataStore; import org.openbravo.model.common.enterprise.Organization; import org.openbravo.model.common.plm.AttributeSet; import org.openbravo.model.common.plm.AttributeSetInstance; import org.openbravo.service.db.DalConnectionProvider; /** * The entity resolver will resolve an entity name and id to a business object. The resolver will * first try to find the business object in the database in the accessible clients and organizations * using the id and the content of the mapping table (AD_REF_DATA_LOADED table). If not found then * unique constraints are used to find matching objects in the database. If then still no existing * object is found then a new object is created and based on the accesslevel of the Entity the * client and organization are set. * * @author mtaal */ public class EntityResolver implements OBNotSingleton { /** * The resolving mode determines how the EntityResolver should response if no existing object can * be found for a certain entity name and id. * <p/> * ALLOW_NOT_EXIST (the default) will allow a not-yet existing object and will create a new one. * MUST_EXIST will result in an EntityNotFoundException if the object can not be found in the * database. */ public enum ResolvingMode { ALLOW_NOT_EXIST, MUST_EXIST } public static EntityResolver getInstance() { return OBProvider.getInstance().get(EntityResolver.class); } // keeps track of the mapping from id's to objects private Map<Object, BaseOBObject> data = new HashMap<Object, BaseOBObject>(); private Map<String, BaseOBObject> originalIdObjectMapping = new HashMap<String, BaseOBObject>(); private Map<BaseOBObject, String> objectOriginalIdMapping = new HashMap<BaseOBObject, String>(); private Client clientZero; private Organization organizationZero; private String[] zeroOrgTree = new String[] { "0" }; private Client client; private Organization organization; private String[] orgNaturalTree; private ResolvingMode resolvingMode = ResolvingMode.ALLOW_NOT_EXIST; private boolean lookForTranslatedIDs; private OrganizationStructureProvider organizationStructureProvider; private boolean optionCreateReferencedIfNotFound = true; // When the entity resolver is used to apply datasets, it has to look for the translated IDs // When the entity resolver is used from a DAL REST webservice, there is not need to look for the // translated IDs void clear() { data.clear(); originalIdObjectMapping.clear(); objectOriginalIdMapping.clear(); } /** * Searches for an entity using the entityname and the id, first the internal cache is searched * and then the database. Depending if the entity is searched for as a reference or as a main * object (in the root of the xml) the search is differs. If no existing object can be found then * new one is created.. * * @param entityName * the name of the entity searched for * @param id * the id, can be null * @param referenced * if the entity is searched because it is refered to or if it is in the root of the xml * @return an existing or a new entity */ // searches for a previous entity with the same id or an id retrieved from // the ad_ref_data_loaded table. The resolving takes into account different // access levels and public BaseOBObject resolve(String entityName, String id, boolean referenced, boolean filterOrgs) { Check.isNotNull(client, "Client should not be null"); Check.isNotNull(organization, "Org should not be null"); final Entity entity = ModelProvider.getInstance().getEntity(entityName); BaseOBObject result = null; // note id can be null if someone did not care to add it in a manual // xml file if (id != null) { result = data.get(getKey(entityName, id)); if (result != null) { return result; } result = searchInstance(entity, id); } // Only try to fetch the object if the id is not null if (result == null && id != null) { result = doSearch(id, entity, client.getId(), filterOrgs, ""); } // search using the id if it is a view, note can be wrong as there can // be duplicates in id for older id values, but is the best we can do // at the moment if (result == null && entity.isView()) { result = getObjectUsingOriginalId(id); } if (result != null) { // found, cache it for future use data.put(getKey(entityName, id), result); objectOriginalIdMapping.put(result, id); originalIdObjectMapping.put(id, result); } else { if (referenced && !isOptionCreateReferencedIfNotFound()) { throw new EntityNotFoundException("Entity " + entityName + " with id " + id + " not found"); } if (resolvingMode == ResolvingMode.MUST_EXIST) { throw new EntityNotFoundException("Entity " + entityName + " with id " + id + " not found"); } // not found create a new one result = (BaseOBObject) OBProvider.getInstance().get(entityName); if (id != null) { // keep the relation so that ad_ref_data_loaded can be filled // later objectOriginalIdMapping.put(result, id); originalIdObjectMapping.put(id, result); // check if we can keep the id for this one if (!OBDal.getInstance().exists(entityName, id)) { result.setId(id); } // force new result.setNewOBObject(true); // keep it here so it can be found later data.put(getKey(entityName, id), result); } setClientOrganization(result); } return result; } public BaseOBObject resolve(String entityName, String id, boolean referenced) { boolean filterOrgs = false; return resolve(entityName, id, referenced, filterOrgs); } protected void addObjectToCaches(String id, String entityName, BaseOBObject bob) { data.put(getKey(entityName, id), bob); objectOriginalIdMapping.put(bob, id); originalIdObjectMapping.put(id, bob); } protected void setClientOrganization(BaseOBObject bob) { setClientOrganizationZero(); final Entity entity = bob.getEntity(); // TODO: add warning if the entity is created in a different // client/organization than the inputted ones // Set the client and organization on the most detailed level // looking at the accesslevel of the entity Client setClient; Organization setOrg; if (entity.getAccessLevel() == AccessLevel.SYSTEM) { setClient = clientZero; setOrg = organizationZero; } else if (entity.getAccessLevel() == AccessLevel.SYSTEM_CLIENT) { setClient = client; setOrg = organizationZero; } else if (entity.getAccessLevel() == AccessLevel.CLIENT) { setClient = client; setOrg = organizationZero; } else if (entity.getAccessLevel() == AccessLevel.CLIENT_ORGANIZATION) { setClient = client; setOrg = organization; } else if (entity.getAccessLevel() == AccessLevel.ORGANIZATION) { // TODO: is this correct? That it is the same as the previous // one? setClient = client; setOrg = organization; } else if (entity.getAccessLevel() == AccessLevel.ALL) { setClient = client; setOrg = organization; } else { throw new EntityXMLException("Access level " + entity.getAccessLevel() + " not supported"); } if (entity.isClientEnabled()) { bob.setValue(PROPERTY_CLIENT, setClient); } if (entity.isOrganizationEnabled()) { bob.setValue(PROPERTY_ORGANIZATION, setOrg); } } // search on the basis of the access level of the entity protected BaseOBObject searchInstance(Entity entity, String id) { final AccessLevel al = entity.getAccessLevel(); BaseOBObject result = null; if (al == AccessLevel.SYSTEM) { result = searchSystem(id, entity); } else if (al == AccessLevel.SYSTEM_CLIENT) { final List<RefDataLoaded> refLoadeds = getRefLoadedsUsingSql(id, entity, null, false); // search client and system result = searchClientRefLoaded(id, entity, refLoadeds); if (result == null) { result = searchSystemRefLoaded(id, entity, refLoadeds); } if (result == null) { result = searchSystem(id, entity); } } else if (al == AccessLevel.CLIENT) { final List<RefDataLoaded> refLoadeds = getRefLoadedsUsingSql(id, entity, null, false); result = searchClientRefLoaded(id, entity, refLoadeds); } else if (al == AccessLevel.ORGANIZATION) { final List<RefDataLoaded> refLoadeds = getRefLoadedsUsingSql(id, entity, null, false); result = searchClientOrganizationRefLoaded(id, entity, refLoadeds); if (result == null) { result = searchClientReadableOrgRefLoaded(id, entity, refLoadeds); } } else if (al == AccessLevel.CLIENT_ORGANIZATION) { // search 2 levels final List<RefDataLoaded> refLoadeds = getRefLoadedsUsingSql(id, entity, null, false); result = searchClientOrganizationRefLoaded(id, entity, refLoadeds); if (result == null) { result = searchClientRefLoaded(id, entity, refLoadeds); } if (result == null && (entity.getName().compareTo(AttributeSetInstance.ENTITY_NAME) == 0 || entity.getName().compareTo(AttributeSet.ENTITY_NAME) == 0)) { result = OBDal.getInstance().get(entity.getName(), id); } if (result == null) { result = searchClientReadableOrgRefLoaded(id, entity, refLoadeds); } } else if (al == AccessLevel.ALL) { final List<RefDataLoaded> refLoadeds = getRefLoadedsUsingSql(id, entity, null, false); // First check if there is one with same client/org result = searchClientOrganizationRefLoaded(id, entity, refLoadeds); // search all three levels from the bottom if (result == null) { result = searchClientRefLoaded(id, entity, refLoadeds); } if (result == null) { result = searchSystem(id, entity); } if (result == null) { result = searchClientReadableOrgRefLoaded(id, entity, refLoadeds); } } return result; } private BaseOBObject searchClientReadableOrgRefLoaded(String id, Entity entity, List<RefDataLoaded> refLoadeds) { BaseOBObject result = null; for (RefDataLoaded refLoaded : refLoadeds) { if (refLoaded.getClientId().equals(client.getId())) { for (String orgId : OBContext.getOBContext().getReadableOrganizations()) { if (refLoaded.getOrgId().equals(orgId)) { result = doSearch(refLoaded.getSpecificId(), entity, client.getId(), orgId); if (result != null) { return result; } } } } } return doSearch(id, entity, client.getId(), organization.getId()); } private BaseOBObject searchClientOrganizationRefLoaded(String id, Entity entity, List<RefDataLoaded> refLoadeds) { BaseOBObject result = null; for (RefDataLoaded refLoaded : refLoadeds) { if (refLoaded.getClientId().equals(client.getId()) && refLoaded.getOrgId().equals(organization.getId())) { result = doSearch(refLoaded.getSpecificId(), entity, client.getId(), organization.getId()); if (result != null) { return result; } } } return doSearch(id, entity, client.getId(), organization.getId()); } private BaseOBObject searchClientRefLoaded(String id, Entity entity, List<RefDataLoaded> refLoadeds) { BaseOBObject result = null; for (RefDataLoaded refLoaded : refLoadeds) { if (refLoaded.getClientId().equals(client.getId()) && refLoaded.getOrgId().equals("0")) { result = doSearch(refLoaded.getSpecificId(), entity, client.getId(), "0"); if (result != null) { return result; } } } return doSearch(id, entity, client.getId(), "0"); } private BaseOBObject searchSystemRefLoaded(String id, Entity entity, List<RefDataLoaded> refLoadeds) { BaseOBObject result = null; for (RefDataLoaded refLoaded : refLoadeds) { if (refLoaded.getClientId().equals('0') && refLoaded.getOrgId().equals("0")) { result = doSearch(refLoaded.getSpecificId(), entity, "0", "0"); if (result != null) { return result; } } } return doSearch(id, entity, "0", "0"); } public String getOriginalId(BaseOBObject bob) { return objectOriginalIdMapping.get(bob); } public BaseOBObject getObjectUsingOriginalId(String id) { return originalIdObjectMapping.get(id); } protected BaseOBObject searchSystem(String id, Entity entity) { return doSearch(id, entity, "0", "0"); } protected BaseOBObject searchClient(String id, Entity entity) { return search(id, entity, "0"); } protected BaseOBObject searchClientOrganization(String id, Entity entity) { return search(id, entity, organization.getId()); } protected BaseOBObject search(String id, Entity entity, String orgId) { // first check if the object was already imported in this level // so check if there is a new id available final List<String> newIds = getId(id, entity, orgId); if (newIds.size() > 0) { for (final String newId : newIds) { final BaseOBObject result = doSearch(newId, entity, client.getId(), orgId); if (result != null) { return result; } } } return doSearch(id, entity, client.getId(), orgId); } protected BaseOBObject doSearch(String id, Entity entity, String clientId, String orgId) { boolean filterOrgs = true; return doSearch(id, entity, clientId, filterOrgs, orgId); } protected BaseOBObject doSearch(String id, Entity entity, String clientId, boolean filterOrgs, String orgId) { final String[] searchOrgIds = getOrgIds(orgId); final OBCriteria<?> obc = OBDal.getInstance().createCriteria(entity.getName()); obc.setFilterOnActive(false); obc.setFilterOnReadableClients(false); obc.setFilterOnReadableOrganization(false); if (entity.isClientEnabled()) { obc.add(Restrictions.eq(PROPERTY_CLIENT + "." + Organization.PROPERTY_ID, clientId)); } if (filterOrgs && entity.isOrganizationEnabled()) { // Note the query is for other types than client but the client // property names // are good standard ones to use obc.add(Restrictions.in(PROPERTY_ORGANIZATION + "." + Client.PROPERTY_ID, searchOrgIds)); } // same for here obc.add(Restrictions.eq(Organization.PROPERTY_ID, id)); final List<?> res = obc.list(); Check.isTrue(res.size() <= 1, "More than one result when searching in " + entity.getName() + " with id " + id); if (res.size() == 1) { return (BaseOBObject) res.get(0); } return null; } private List<RefDataLoaded> getRefLoadedsUsingSql(String id, Entity entity, String orgId, boolean filterByClient) { PreparedStatement ps = null; try { String st = "Select specific_id, generic_id, ad_client_id, ad_org_id from ad_ref_data_loaded where ad_client_id in ('" + client.getId() + "', '0') and generic_id='" + id + "' and ad_table_id='" + entity.getTableId() + "'"; ps = new DalConnectionProvider(false).getPreparedStatement(st); ps.execute(); ResultSet rs = ps.getResultSet(); List<RefDataLoaded> refDataLoadeds = new ArrayList<EntityResolver.RefDataLoaded>(); while (rs.next()) { RefDataLoaded rfl = new RefDataLoaded(); rfl.setSpecificId(rs.getString(1)); rfl.setGenericId(rs.getString(2)); rfl.setClientId(rs.getString(3)); rfl.setOrgId(rs.getString(4)); refDataLoadeds.add(rfl); } return refDataLoadeds; } catch (Exception e) { throw new OBException("Error while accessing the ad_ref_data_loaded table", e); } finally { try { ps.close(); } catch (SQLException se) { // won't happen } } } private class RefDataLoaded { private String specificId; private String genericId; private String clientId; private String orgId; public String getGenericId() { return genericId; } public void setGenericId(String genericId) { this.genericId = genericId; } public String getSpecificId() { return specificId; } public void setSpecificId(String specificId) { this.specificId = specificId; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getOrgId() { return orgId; } public void setOrgId(String orgId) { this.orgId = orgId; } } private List<String> getId(String id, Entity entity, String orgId) { List<ReferenceDataStore> rdls = getRefLoadeds(id, entity, orgId, true); final List<String> result = new ArrayList<String>(); for (final ReferenceDataStore rdl : rdls) { result.add(rdl.getSpecific()); } return result; } // get the new id which was created in previous imports // note that there is a rare case that when an instance is removed // and then re-imported that it occurs multiple times. private List<ReferenceDataStore> getRefLoadeds(String id, Entity entity, String orgId, boolean filterByClient) { OBContext.setAdminMode(); try { final OBCriteria<ReferenceDataStore> rdlCriteria = OBDal.getInstance() .createCriteria(ReferenceDataStore.class); rdlCriteria.setFilterOnActive(false); rdlCriteria.setFilterOnReadableOrganization(false); rdlCriteria.setFilterOnReadableClients(false); rdlCriteria.add(Restrictions.eq(ReferenceDataStore.PROPERTY_GENERIC, id)); if (filterByClient) { rdlCriteria.add(Restrictions.eq(ReferenceDataStore.PROPERTY_CLIENT + "." + Client.PROPERTY_ID, client.getId())); } else { String[] clients = new String[2]; clients[0] = client.getId(); clients[1] = "0"; rdlCriteria.add( Restrictions.in(ReferenceDataStore.PROPERTY_CLIENT + "." + Client.PROPERTY_ID, clients)); } if (orgId != null) { final String[] searchOrgIds = getOrgIds(orgId); rdlCriteria.add(Restrictions.in( ReferenceDataStore.PROPERTY_ORGANIZATION + "." + Organization.PROPERTY_ID, searchOrgIds)); } rdlCriteria.add(Restrictions.eq(ReferenceDataStore.PROPERTY_TABLE + "." + Table.PROPERTY_ID, entity.getTableId())); final List<ReferenceDataStore> rdls = rdlCriteria.list(); return rdls; } finally { OBContext.restorePreviousMode(); } } // determines which org ids to look, if 0 then only look zero // in other cases look only in the passed orgId if this is not // a referenced one, otherwise use the naturaltree private String[] getOrgIds(String orgId) { final String[] searchOrgIds; if (orgId.equals("0")) { searchOrgIds = zeroOrgTree; } else { searchOrgIds = orgNaturalTree; } return searchOrgIds; } protected void setClientOrganizationZero() { if (clientZero != null) { return; } OBContext.setAdminMode(); try { clientZero = OBDal.getInstance().get(Client.class, "0"); organizationZero = OBDal.getInstance().get(Organization.class, "0"); } finally { OBContext.restorePreviousMode(); } } protected Client getClient() { return client; } protected void setClient(Client client) { setClientOrganizationZero(); organizationStructureProvider = OBProvider.getInstance().get(OrganizationStructureProvider.class); organizationStructureProvider.setClientId(client.getId()); this.client = client; } protected Organization getOrganization() { return organization; } protected void setOrganization(Organization organization) { final Set<String> orgs = organizationStructureProvider.getNaturalTree(organization.getId()); orgNaturalTree = orgs.toArray(new String[orgs.size()]); this.organization = organization; } protected Map<Object, BaseOBObject> getData() { return data; } protected boolean isOptionCreateReferencedIfNotFound() { return optionCreateReferencedIfNotFound; } /** * This option controls if referenced objects (through an association) must exist in the database. * * @param optionCreateReferencedIfNotFound * if true then referenced objects are allowed to not exist in the database, meaning that * they a new object is created for a reference */ public void setOptionCreateReferencedIfNotFound(boolean optionCreateReferencedIfNotFound) { this.optionCreateReferencedIfNotFound = optionCreateReferencedIfNotFound; } public ResolvingMode getResolvingMode() { return resolvingMode; } /** * @see ResolvingMode */ public void setResolvingMode(ResolvingMode resolvingMode) { this.resolvingMode = resolvingMode; } // queries the database for another object which has the same values // for properties which are part of a uniqueconstraint // if found a check is done if the object is part of the current // installed protected BaseOBObject findUniqueConstrainedObject(BaseOBObject obObject) { // an existing object should not be able to violate his/her // own constraints if (!obObject.isNewOBObject()) { return null; } final Entity entity = obObject.getEntity(); final Object id = obObject.getId(); for (final UniqueConstraint uc : entity.getUniqueConstraints()) { final OBCriteria<BaseOBObject> criteria = OBDal.getInstance().createCriteria(entity.getName()); if (id != null) { criteria.add(Restrictions.ne("id", id)); } boolean ignoreUniqueConstraint = false; for (final Property p : uc.getProperties()) { final Object value = obObject.getValue(p.getName()); // a special check, the property refers to an // object which is also new, presumably this object // is also added in the import // in this case the // uniqueconstraint can never fail // so move on to the next if (value instanceof BaseOBObject && ((BaseOBObject) value).isNewOBObject()) { ignoreUniqueConstraint = true; break; } criteria.add(Restrictions.eq(p.getName(), value)); } if (ignoreUniqueConstraint) { continue; } criteria.setFilterOnActive(false); criteria.setFilterOnReadableOrganization(false); criteria.setFilterOnReadableClients(false); criteria.setMaxResults(1); final List<BaseOBObject> queryResult = criteria.list(); if (queryResult.size() > 0) { // check if the found unique match is a valid // object to use // TODO: this can be made faster by // adding client/organization filtering above in // the criteria final BaseOBObject searchResult = searchInstance(entity, (String) queryResult.get(0).getId()); if (searchResult == null) { // not valid return null return null; } return queryResult.get(0); } } return null; } protected Client getClientZero() { if (clientZero == null) { setClientOrganizationZero(); } return clientZero; } protected Organization getOrganizationZero() { if (organizationZero == null) { setClientOrganizationZero(); } return organizationZero; } /** * Replace an object in the cache with another one. The new one is then found when using the id of * the old one. The id of the previous one can be local to the xml. * * @param prevObject * the object current in the data cache * @param newObject * the new object which can then be found under the old object */ public void exchangeObjects(BaseOBObject prevObject, BaseOBObject newObject) { if (!(prevObject.getId() instanceof String)) { // these are never refered to anyway return; } final String id = (String) prevObject.getId(); if (id == null) { // no one will refer to these ones return; } Check.isTrue(prevObject.getEntityName().compareTo(newObject.getEntityName()) == 0, "Entity names are different for objects " + prevObject + " and " + newObject); data.put(getKey(prevObject.getEntityName(), id), newObject); } // convenience to prevent using id and entityName in the wrong order protected String getKey(String entityName, String id) { return entityName + id; } @Deprecated public void setLookForTranslatedIDs(boolean lookForTranslatedIDs) { this.lookForTranslatedIDs = lookForTranslatedIDs; } }