package com.thesearchagency.core.smb.dao.contentmodel; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.thesearchagency.core.smb.dao.DAOException; import com.thesearchagency.core.smb.domain.customization.contentmodel.dto.CustomizableEntity; import com.thesearchagency.core.smb.domain.customization.contentmodel.dto.Field; import com.thesearchagency.core.smb.domain.customization.contentmodel.dto.type.EntityType; import com.thesearchagency.core.smb.domain.customization.customer.fields.ContentField; import com.thesearchagency.core.smb.domain.customization.customer.fields.CustomerField; import com.thesearchagency.core.smb.domain.customization.customer.fields.FilterField; import com.thesearchagency.core.smb.domain.customization.customer.fields.FilterField.FilterableField; @Component("DOMPersistanceManager") public class DOMPersistanceManager implements IDOMPersistanceManager { /** Logger **/ private static final Logger LOGGER = Logger.getLogger(DOMPersistanceManager.class); @Autowired ICustomizableEntityDAO theCustomizableEntityDAO; @Autowired ICustomerFieldDAO theCustomerFieldDAO; @Autowired IContentFieldDAO theContentFieldDAO; /* The following methods deal with complete content models for a customer */ @Override public CustomizableEntity getContentModel(int anEntityId) { CustomizableEntity ret = null; List entities = theCustomizableEntityDAO.readAll(anEntityId); if(entities.size()>0) ret = constructEntityGraph(entities, null); return ret; } @Override @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = DAOException.class) public CustomizableEntity setContentModel(int anEntityId, CustomizableEntity aNewRecord) { CustomizableEntity currentRecord = null; if(anEntityId > 0) { currentRecord = getContentModel(anEntityId); } if(currentRecord!=null){ // update entities and fields that already exists, create new ones, delete old ones updateContentModelBranch(aNewRecord, false); currentRecord = getContentModelBranch(anEntityId, aNewRecord.getId()); }else{ currentRecord = createContentModel(aNewRecord); } return currentRecord; } private CustomizableEntity createContentModel(CustomizableEntity aNewRecord) { if(entityPassesSanityCheck(aNewRecord.getEntityId().intValue(), aNewRecord)){ int id = theCustomizableEntityDAO.create(aNewRecord); aNewRecord.setId(id); for(CustomerField field : aNewRecord.getCustomerFields()){ field.setEntityId(aNewRecord.getEntityId()); field.setCustomizableEntityId(id); LOGGER.info(" Creating CustomerField record : " + field.getEntityId() + " " + field.getTag().getName() + " " + field.getCustomizableEntityId()); field.setId( theCustomerFieldDAO.create(field) ); } for(ContentField field : aNewRecord.getContentFields()){ field.setEntityId(aNewRecord.getEntityId()); field.setCustomizableEntityId(id); LOGGER.info(" Creating ContentField record : " + field.getEntityId() + " " + field.getTag().getName() + " " + field.getCustomizableEntityId()); field.setId( theContentFieldDAO.create(field) ); } if(aNewRecord.getCustomizableEntityChildren() != null && !aNewRecord.getCustomizableEntityChildren().isEmpty()) { for(CustomizableEntity entity : aNewRecord.getCustomizableEntityChildren()){ entity.setEntityId(aNewRecord.getEntityId()); entity.setCustomizableEntityParentId(id); LOGGER.info(" Creating contentModel from : " + entity.getEntityId() + " " + entity.getDescription()); createContentModel(entity); } } }else{ throw new RuntimeException("Content model invalid, all customizable entities in a content model graph must have the same entityId and only CustomerSite objects may have parentId == 0."); } return aNewRecord; } /* The previous methods deal with complete content models */ /* The following methods deal with branches of content models */ @Override public CustomizableEntity getContentModelBranch(int anEntityId, int aCustomizableEntityId) { CustomizableEntity root = null; List entities = theCustomizableEntityDAO.readAll(anEntityId); for(CustomizableEntity entity : entities){ if(entity.getId()==aCustomizableEntityId){ root = entity; break; } } if(root!=null){ constructEntityGraph(entities, root); } return root; } @Override public CustomizableEntity setContentModelBranch(int anEntityId, CustomizableEntity entity) { CustomizableEntity ret = null; if(entityPassesSanityCheck(entity.getEntityId().intValue(), entity)){ int id = theCustomizableEntityDAO.create(entity); ret = theCustomizableEntityDAO.read(anEntityId, id); }else{ throw new RuntimeException("Content model invalid, all customizable entities in a content model graph must have the same entityId and only CustomerSite objects may have parentId == 0."); } return ret; } @Override public void updateContentModelBranch( CustomizableEntity aCustomizableEntity, Boolean isCustomization) { if(entityPassesSanityCheck(aCustomizableEntity.getEntityId().intValue(), aCustomizableEntity)){ CustomizableEntity updated = aCustomizableEntity; CustomizableEntity current = getContentModelBranch(aCustomizableEntity.getEntityId(), aCustomizableEntity.getId()); if(current==null) current = createContentModel(aCustomizableEntity); current.setDescription(aCustomizableEntity.getDescription()); theCustomizableEntityDAO.update(current); updateCustomerFields(current, updated, isCustomization); updateContentFields(current, updated, isCustomization); syncChildren(current, updated, isCustomization); }else{ throw new RuntimeException("Content model invalid, all customizable entities in a content model graph must have the same entityId and only CustomerSite objects may have parentId == 0."); } } private > void syncFields(ArrayList current, ArrayList updated, Boolean isCustomization, DAO aDAO){ // sync any CustomerFields that are in both ArrayList updatedFields = new ArrayList(); for(T updatedField : updated){ if(updatedField!=null){ T currentField = getSameField(updatedField, current); if(currentField != null){ if( updatedField.getValue()==null){ currentField.setValue(""); if(isCustomization) currentField.setCustomized(isCustomization); }else{ // Sometimes content has special characters that cause regex errors. // Pattern.quote suppresses the meaning of special characters. String currentText = Pattern.quote(currentField.getValue()); //Exit and re-enter literal quoting to allow for differences in spacing during regex. currentText = currentText.replaceAll(" ", "\\\\E\\\\s*\\\\Q"); String updatedText = updatedField.getValue(); currentText = currentText.replaceAll("GEO\\d*", "GEO"); updatedText = updatedText.replaceAll("GEO\\d*", "GEO"); Pattern pat = Pattern.compile(currentText); if (!pat.matcher(updatedText).matches()){ currentField.setValue(updatedField.getValue()); if(isCustomization) currentField.setCustomized(isCustomization); } } } if(currentField!=null) updatedFields.add(currentField); } } if(updatedFields.size()>0) aDAO.update(updatedFields); } private void updateContentFields(CustomizableEntity current, CustomizableEntity updated, Boolean isCustomization) { ArrayList currentContentFields = current.getContentFields(); ArrayList updatedContentFields = updated.getContentFields(); // make sure we have matching fields in both addUpdatedMissingFromCurrent(updatedContentFields, currentContentFields, theContentFieldDAO); removeCurrentMissingFromUpdated(currentContentFields, updatedContentFields, theContentFieldDAO); // sync any ContentFields that are in both syncFields(currentContentFields, updatedContentFields, isCustomization, theContentFieldDAO); } private void updateCustomerFields(CustomizableEntity current, CustomizableEntity updated, Boolean isCustomization) { ArrayList currentCustomerFields = current.getCustomerFields(); ArrayList updatedCustomerFields = updated.getCustomerFields(); // make sure we have matching fields in both addUpdatedMissingFromCurrent(updatedCustomerFields, currentCustomerFields, theCustomerFieldDAO); removeCurrentMissingFromUpdated(currentCustomerFields, updatedCustomerFields, theCustomerFieldDAO); // sync any CustomerFields that are in both syncFields(currentCustomerFields, updatedCustomerFields, isCustomization, theCustomerFieldDAO); } public void syncChildren(CustomizableEntity current, CustomizableEntity updated, Boolean isCustomization) { //remove children from current which are missing from updated for(CustomizableEntity child : current.getCustomizableEntityChildren()){ boolean stillExists = false; for(CustomizableEntity updatedChild : updated.getCustomizableEntityChildren()){ if(child.getId().intValue()==updatedChild.getId().intValue()){ stillExists = true; break; } } if(!stillExists) { EntityType childType = child.getType(); if(childType == EntityType.AD_COPY || childType == EntityType.KEYWORD){ theCustomizableEntityDAO.delete(current.getEntityId(), child.getId()); theCustomerFieldDAO.deleteCustomizableEntityFields(child.getId()); theContentFieldDAO.deleteCustomizableEntityFields(child.getId()); } else LOGGER.info("Not deleting child : " + child.toString() + " to preserve customization records. "); } } //sync children in updated for(CustomizableEntity child : updated.getCustomizableEntityChildren()){ updateContentModelBranch(child, isCustomization); } } /* The previous methods deal with branches of content models */ /* The following methods deal with layers of content models */ @Override public List getContentModelLayer(int anEntityId, EntityType anEntityType) { List ret = theCustomizableEntityDAO.readAll(anEntityId, anEntityType); List allEntities = theCustomizableEntityDAO.readAll(anEntityId); for(CustomizableEntity entity : ret){ constructEntityGraph(allEntities, entity); } return ret; } @Override public List getContentModelLayer(Integer anEntityId, EntityType aType, ArrayList filters) { HashMap> filterMap = buildFilterMap(filters); List layer = getContentModelLayer(anEntityId, aType); List filteredLayer = new ArrayList(); addParentsToEntities(anEntityId, layer); for(CustomizableEntity entity : layer){ if(entityPassesFilterSet(entity, filterMap)){ filteredLayer.add(entity); } } return filteredLayer; } /* The previous methods deal with layers of content models */ /* The following methods are helper methods which may be useful */ private CustomizableEntity constructEntityGraph( List entities, CustomizableEntity root) { HashMap> entityMap = new HashMap> (); for(CustomizableEntity entity : entities){ Integer parentId = entity.getCustomizableEntityParentId(); ArrayList parentList = entityMap.get(parentId); if(parentList==null) { parentList = new ArrayList(); entityMap.put(parentId, parentList); } parentList.add(entity); } if(root==null){ ArrayList roots = entityMap.get(new Integer(0)); for(CustomizableEntity entity : roots){ if(entity.getType()==EntityType.CUSTOMER_SITE){ root = entity; break; } } if(root == null) throw new RuntimeException("Invalid Content Model Entity Graph"); } addChildrenToEntity(entityMap, root); setAllFields(root); return root; } private T getSameField(T aField, ArrayList aFieldList) { T ret = null; for(T field : aFieldList){ if( aField.getId() != null && field.getId()!=null && field.getId().intValue() == aField.getId().intValue() ){ ret = field; break; } } return ret; } private > void removeCurrentMissingFromUpdated(ArrayList current, ArrayList updated, IFieldDAO aDAO){ // delete any in a which are missing from b for(T oldField : current){ if(getSameField(oldField, updated)==null){ aDAO.deleteCustomizableEntityField(oldField.getCustomizableEntityId(), oldField.getTag()); } } } private > void addUpdatedMissingFromCurrent(ArrayList current, ArrayList updated, IFieldDAO aDAO){ // add any new ones in a to b for(T newField : updated){ if(getSameField(newField, current)==null){ aDAO.create(newField); } } } private void addParentsToEntities(Integer anEntityId, Collection layer) { HashMap parents = new HashMap(); for(CustomizableEntity entity : layer){ if(entity != null && entity.getCustomizableEntityParentId() != 0) parents.put(entity.getCustomizableEntityParentId(), null); } LOGGER.info("Parents Found : " + parents.size()); if(parents.size() > 0){ theCustomizableEntityDAO.readList(anEntityId, parents); for(Integer key : parents.keySet()){ CustomizableEntity entity = parents.get(key); if(entity!=null){ setAllFields(entity); } } for(CustomizableEntity entity : layer){ if(entity!=null) { entity.setCustomizableEntityParent(parents.get(entity.getCustomizableEntityParentId())); LOGGER.info("Entity : " + entity.getId() + " Parent : " + parents.get(entity.getCustomizableEntityParentId())); } } addParentsToEntities(anEntityId, parents.values()); } } private void addChildrenToEntity( HashMap> entityMap, CustomizableEntity root) { ArrayList children = entityMap.get(root.getId()); if(children!=null){ root.getCustomizableEntityChildren().addAll(children); for(CustomizableEntity child : root.getCustomizableEntityChildren()){ setAllFields(child); child.setCustomizableEntityParent(root); addChildrenToEntity(entityMap, child); } } } private void setAllFields(CustomizableEntity entity){ setAllCustomerFields(entity, theCustomerFieldDAO.readCustomizableEntityFields(entity.getId())); setAllContentFields(entity, theContentFieldDAO.readCustomizableEntityFields(entity.getId())); } private void setAllCustomerFields(CustomizableEntity root, List allFields) { for(CustomerField field : allFields){ root.updateCustomerField(field); } } private void setAllContentFields(CustomizableEntity root, List allFields) { for(ContentField field : allFields){ root.updateContentField(field); } } /* The previous methods are helper methods which may be useful */ /* The following methods are used to filter entities by FilterableFields */ private HashMap> buildFilterMap(ArrayList filters){ HashMap> filterMap = new HashMap>() ; for(FilterField filter : filters){ ArrayList filterableFieldList = filterMap.get(filter.getField()); if(filterableFieldList==null){ filterableFieldList = new ArrayList(); filterMap.put(filter.getField(), filterableFieldList); } filterableFieldList.add(filter); } return filterMap; } private boolean entityPassesFilterSet(CustomizableEntity entity, HashMap> filterMap) { boolean ret = true; for(FilterableField key : filterMap.keySet()){ ArrayList filtersForKey = filterMap.get(key); boolean matchesCurrentFilter = false; for(FilterField filter : filtersForKey){ matchesCurrentFilter = filter.getField().checkMatches(filter, entity); if(matchesCurrentFilter) break; } if(!matchesCurrentFilter) { ret = false; break; } } return ret; } private boolean entityPassesSanityCheck(int anEntityId, CustomizableEntity aNewRecord) { /* * The following rules apply for a CustomizableEntity. * * -Only a CustomerSite entity may have a parentId of 0 * -All CustomizableEntities in a graph must have the same entityId. * */ boolean ret = true; if( anEntityId == aNewRecord.getEntityId().intValue() && (aNewRecord.getCustomizableEntityParentId().intValue() != 0 || aNewRecord.getType() == EntityType.CUSTOMER_SITE ) ){ for(CustomizableEntity child : aNewRecord.getCustomizableEntityChildren()){ if( ! entityPassesSanityCheck(anEntityId, child) ) { ret = false; break; } } }else{ ret = false; } return ret; } /* The previous methods are used to filter entities by FilterableFields */ }