package gw.beantransfer.impl uses com.guidewire.pl.system.internal.InternalMethods uses gw.api.beantransfer.BeanTransferException uses gw.api.importing.ImportingUtil uses gw.api.importing.XmlExporter uses gw.api.importing.graph.Element uses gw.api.importing.graph.Graph uses gw.api.importing.graph.MapBackedGraph uses gw.api.system.PLDependenciesGateway uses gw.api.util.TypeUtil uses gw.beantransfer.BeanTransferUtil uses gw.entity.IColumnPropertyInfo uses gw.entity.IEntityPropertyInfo uses gw.entity.IEntityType uses gw.entity.ILinkPropertyInfo uses gw.entity.ITypekeyPropertyInfo uses gw.lang.reflect.TypeSystem uses gw.util.AutoMap uses gw.util.Pair uses java.io.InputStream /** * Graph representation of the BeanTransfer XML. */ @ReadOnly class BeanImportGraph implements Graph { delegate _graph represents Graph var _rootBeanIDs : Set as readonly RootBeanIDs = {} var _elements : LinkedHashMap = {} static final var BASED_ON_VALUE_ATTRIB = "BasedOnValue" // The following Map is used to locate the element based on PublicID. // Does not contain entries for empty-string publicIDs. E.g. those cleared by clearEffDatedPublicIds, or those cleared during export. var _elementByPublicID: Map, BeanImportElement> // The following Map is used to locate the element based on Database ID. var _elementByDatabaseID: Map, BeanImportElement> construct(inputStream : InputStream, rootEntity : Type) { // Use the MapBackedGraph to parse the XML. Most calls to BeanImportGraph are also delegated to it. this(new MapBackedGraph("BeanImport", inputStream), rootEntity) } construct(graph : Graph, rootEntity : Type) { _graph = graph var elementIds = _graph.getElementIds() elementIds.each(\id -> { var srcElement = _graph.getElement(id) if (not srcElement.TypeKey and srcElement.EntityTypeName == rootEntity.Name) { _rootBeanIDs.add(id) } _elements[id] = new BeanImportElement(id, srcElement) }) refreshElementLookups() } override function getElement(id : String) : BeanImportElement { return _elements[id] } function removeElement(id : String) : BeanImportElement { return _elements.remove(id) } override property get ElementIds() : Collection { return _elements.keySet() } /** * Re-key all the FixedIDs in this Graph. The db-id's are also set to the new FixedID's since all the EffDated beans * are created from the Submission. * * First, build two data structures. One is a Set that contains all the EffDated beans in the XML (effDatedElements). * The other is a Map of all the bean properties that reference EffDated beans (effDatedProps). The Map key is a * Pair of (, ). The key identifies an instance of an EffDated * bean. The Map value is a List of BeanImportEffDatedProperty that references the EffDated bean instance. * BeanImportEffDatedProperty contains the XML Element and the EffDated property name. * The List contains all the bean properties referencing a particular instance of an EffDated bean. * * To re-key, it traverses through the EffDated beans (effDatedElements) and updates the FixedID's (and DB ID's). * Then, it looks up all the bean properties referencing that EffDated and update the FixedID's of those references. * */ function rekeyFixedIds() { var effDatedElements = new HashSet() // The "Pair" key used for the following map contains (, ). var effDatedProps = new HashMap, Set>() .toAutoMap(\k -> {return {}}) for (var element in _elements.Values) { if (element.TypeKey or element.Reference) { continue } var entityType = BeanTransferUtil.getEntityIntrinsicType(element.EntityTypeName) if (InternalMethods.asIEntityTypeInternal(entityType).EffDated) { effDatedElements.add(element) } // Go through all the entity attributes and keep track of all the EffDated properties. for (var propName in element.AttributeNames) { var entityProp = entityType.TypeInfo.getProperty(propName) if (TypeUtil.isNominallyOrStructurallyAssignable(EffDated.Type, entityProp.FeatureType) and InternalMethods.asILinkPropInfoInternal(entityProp as ILinkPropertyInfo).isEffDated()) { // The reason why we are using the "base" EffDated type below is because we use a single DB table to store // all subtypes of an EffDated bean type. For example, we have one table storing PersonalAutoCovCost and // PersonalVehicleCovCost because they are both subtypes of PACost. Basically, it's because our persistence // layer uses the "Single Table Inheritance" PoEAA pattern to store entities in the database. var baseEntityType = (entityProp.FeatureType as IEntityType).RootType var key = Pair.make(baseEntityType, element.getAttribute(propName)) effDatedProps[key].add(new BeanImportEffDatedProperty(element, propName)) } } } updateFixedIds(effDatedElements, effDatedProps) } /** * Clears any public ids for effdated entities that are not references, and then refreshes the element lookups. * * This should be used in cases like High Volume Quoting where imported effdated entities should not have populated * public ids. Preemptivly clear them before processing the the graph to avoids clashes between public ids from the * source and target system. */ function clearEffDatedPublicIds() { for (var element in _elements.Values) { if (element.TypeKey or element.Reference) { continue } var entityType = BeanTransferUtil.getEntityIntrinsicType(element.EntityTypeName) if (InternalMethods.asIEntityTypeInternal(entityType).EffDated) { element.updateAttribute(ImportingUtil.PUBLIC_ID_ATTRIB, "") } } // refresh the element lookups so that calls to BeanImportGraph#getElementByPublicId can no longer find elements by their old public ids. refreshElementLookups() } private function updateFixedIds(effDatedElements : Set, effDatedProps : Map, Set>) { var keyGenerator = PLDependenciesGateway.getKeyGenerator() var effDatedEntityWithBasedOn = new ArrayList() /* * The effDatedElements contain references to beans with fixed IDs from the previous system that need to be * replaced with new fixed IDs from this system. If the "based-on" attribute is not set, then the element usually * represents the first version of a bean that needs a new fixed ID issuing. If the "based-on" attribute is set, * then the element will represent a bean which is a later version of one of these entities, and hence it can * reuse the fixed ID issued the first version of the bean. Elements can come through in any sequence, so you can't * rely on the first version being processed before the later version. * * It's possible that there are multiple elements using the same fixed ID which have no "based-on" value, * for example the same CPLocation in a submission and a related preemption. In this case we must not give the * two beans different new fixed IDs because they represent the same CPLocation. So we use an automap to both * issue the new fixed ID and cache it so that it can be reused if the same bean is referenced again. */ var mapOfFixedIds = new AutoMap, String>(\ key -> String.valueOf(keyGenerator.getNextID(key.First).Value)) for (var element in effDatedElements) { // get all the effDated elements with BasedOn value if (element.getAttribute(BASED_ON_VALUE_ATTRIB) != null) { effDatedEntityWithBasedOn.add(element) } else { // update fixedIds for effDated entity with no based-ons var entityType = BeanTransferUtil.getEntityIntrinsicType(element.EntityTypeName) var originalFixedID = element.getAttribute(ImportingUtil.FIXED_ID_ATTRIB) /* * Create the key for the fixed ID and get either a new fixed ID or one that has been previously created for * this key. */ var updateFixedID = mapOfFixedIds.get(Pair.make(entityType.RootType, originalFixedID)) updateFixedIdReferences(element, updateFixedID, effDatedProps) } } //For an effDated bean with based-on (non-submission), rekey the FixedId with based-on's fixedID updateFixedIdsForEffDatedBeansWithBasedOn(effDatedEntityWithBasedOn, mapOfFixedIds, effDatedProps) } /** * Rekey the FixedIds of an effDated entity that has a based-on value (non-submission). * @param effDatedEntityWithBasedOn List of effDated entities with non-null basedOn values * @param mapOfFixedIds Map of original FixedIds of the effDatedBeans and the new FixedIds of the basedOn-EffDated entities * @param effDatedProps Map of Pair value containing entityType and FixedId, and their references (effDated properties) */ private function updateFixedIdsForEffDatedBeansWithBasedOn(effDatedEntityWithBasedOn: List, mapOfFixedIds : Map, String>, effDatedProps : Map, Set>) { for (element in effDatedEntityWithBasedOn) { var originalFixedId = element.getAttribute(ImportingUtil.FIXED_ID_ATTRIB) var entityType = BeanTransferUtil.getEntityIntrinsicType(element.EntityTypeName) var newFixedID = mapOfFixedIds[Pair.make(entityType.RootType, originalFixedId)] updateFixedIdReferences(element, newFixedID, effDatedProps) } } /** * Updates the FixedID for effDated entity references across branches * @param element BeanImportElement, which is the effDated entity * @param newFixedID the new FixedID for update * @param originalFixedID Original FixedID for lookup * @param effDatedProps effDated entity references that can be looked up using the original fixedID */ private function updateFixedIdReferences(element: BeanImportElement, newFixedID: String, effDatedProps: Map, Set>) { var originalFixedId = element.getAttribute(ImportingUtil.FIXED_ID_ATTRIB) element.updateAttribute(ImportingUtil.FIXED_ID_ATTRIB, newFixedID) var entityType = BeanTransferUtil.getEntityIntrinsicType(element.EntityTypeName) var props = effDatedProps[Pair.make(entityType.RootType, originalFixedId)] props?.each(\prop -> { prop.updateFixedID(newFixedID) }) } function getAssignableElementsForType(sourceType: IEntityType) : Set { var importElements = new HashSet() _elements.eachKeyAndValue(\id, importElement -> { if (not getElement(id).TypeKey and BeanTransferUtil.isSourceTypeAssignable(sourceType, BeanTransferUtil.getEntityIntrinsicType(getElement(id).EntityTypeName))) { importElements.add(importElement) } }) return importElements } function getElementByPublicId(entityType : IEntityType, publicId : String) : BeanImportElement { return _elementByPublicID[Pair.make(entityType, publicId)] } function getElementByDatabaseId(entityType : IEntityType, databaseId : String) : BeanImportElement { return _elementByDatabaseID[Pair.make(entityType, databaseId)] } /** * Refresh the lookup maps used to locate elements based on PublicIDs and DatabaseIDs. */ protected function refreshElementLookups() { _elementByPublicID = {} _elementByDatabaseID = {} _elements.eachValue(\element -> { // No public IDs or entity types for typekeys. if (not element.TypeKey) { var entityType = BeanTransferUtil.getEntityIntrinsicType(element.EntityTypeName) var publicID = element.getAttribute(ImportingUtil.PUBLIC_ID_ATTRIB) if (publicID == null) { throw new BeanTransferException("Null Public ID is not supported: " + element.DocID) // If an element's publicID is empty, it's not valid to look it up by publicID. // Multiple elements of the same type would clobber each other in the map. } else if (publicID.NotBlank) { _elementByPublicID[Pair.make(entityType, publicID)] = element } // In MapBackedGraph, database IDs are not parsed for referenced-entities and there are no database IDs for // typekeys, so we can only store them for regular entities. if (not element.Reference) { var databaseID = element.getAttribute(ImportingUtil.DATABASE_ID_ATTRIB) if (databaseID == null) { throw new BeanTransferException("Null Database ID is not supported: " + element.DocID) } _elementByDatabaseID[Pair.make(entityType, databaseID)] = element } } }) } /** * An XML Element class representing a bean transfer import bean. * * This class adds a method for updating attribute values. It also explicitly adds in attributes that don't * appear in the element, for any properties that are null on the exported entity. */ static class BeanImportElement implements Element { var _element : Element var _docID : String as readonly DocID // This map contains both attributes that are updated from the underlying _element as well as additional // attributes not defined on _element var _attributes = new HashMap() construct(docID : String, element : Element) { _docID = docID _element = element // Check Element#TypeKey first because one implementation of Element#Reference throws instead of returning false if (element.TypeKey or element.Reference) { return } // For entity elements (those that aren't typekeys or references), check if there are any exportable properties // that aren't in the element attributes. If so, add them as attributes with a null value. var exportableProperties = getExportablePropertyNames(_element.EntityTypeName) var nullProperties = exportableProperties.subtract(_element.AttributeNames) for (nullProperty in nullProperties) { _attributes.put(nullProperty, null) } } override property get AttributeNames() : Collection { var names = new HashSet(_attributes.Keys) names.addAll(_element.AttributeNames) return names } override property get EntityTypeName() : String { return _attributes.get(MapBackedGraph.ENTITY_TYPE_ATTRIB) ?: _element.EntityTypeName } override property get PublicId() : String { return _attributes.get(ImportingUtil.PUBLIC_ID_ATTRIB) ?: _element.PublicId } override property get DisplayName() : String { return _attributes.get(ImportingUtil.DISPLAY_NAME_ATTRIB) ?: _element.DisplayName } override property get Reference() : boolean { var type = _attributes.get(MapBackedGraph.EL_TYPE_ATTRIB) return type == null ? _element.Reference : ImportingUtil.REFERENCE_TYPE == type } override property get TypeKey() : boolean { var type = _attributes.get(MapBackedGraph.EL_TYPE_ATTRIB) return type == null ? _element.TypeKey : ImportingUtil.TYPEKEY_TYPE == type } override function getAttribute(name : String) : String { // Return the attribute value from the override map if it exists, and delegate to _element otherwise return _attributes.containsKey(name) ? _attributes[name] : _element.getAttribute(name) } override function removeAttribute(name : String) { _element.removeAttribute(name) _attributes.remove(name) } function updateAttribute(name : String, value : String) { if (!_attributes.containsKey(name) && _element.getAttribute(name) == null // Can only override attributes that already exist, except for EL_TYPE_ATTRIB. // If there's no value for EL_TYPE_ATTRIB in the _element Map, it means it's not a TypeKey or Reference Entity. // It would just be an Entity. If we want to convert an Entity element to a Reference element, we need to add // a value ({@link ImportingUtil#REFERENCE_TYPE}) for this attribute. and name != MapBackedGraph.EL_TYPE_ATTRIB) { throw new IllegalArgumentException("Cannot update attribute that doesn't exist: " + name) } _attributes[name] = value } function updateElementToReferenceType(bean : KeyableBean) { updateAttribute(MapBackedGraph.EL_TYPE_ATTRIB, ImportingUtil.REFERENCE_TYPE) updateAttribute(ImportingUtil.DATABASE_ID_ATTRIB, String.valueOf(bean.ID.Value)) updateAttribute(ImportingUtil.PUBLIC_ID_ATTRIB, bean.PublicID) } /** * Returns the names of properties on "entityTypeName" that may be exported by {@link XmlExporter}. */ private static function getExportablePropertyNames(entityTypeName : String) : Set { var entityType = TypeSystem.getByRelativeName(entityTypeName) as IEntityType var entityProperties = entityType.EntityProperties.toList() var exportableProperties = entityProperties.where(\prop -> isExportableProperty(prop)) return exportableProperties*.Name.toSet() } /** * {@link XmlExporter} exports non-denormalized columns and typekeys, with a few exceptions. */ private static function isExportableProperty(prop : IEntityPropertyInfo) : boolean { return (prop typeis IColumnPropertyInfo or prop typeis ITypekeyPropertyInfo) and !InternalMethods.asIEntityPropInfoInternal(prop).Denorm and !NON_EXPORTABLE_PROPERTY_NAMES.contains(prop.Name) } private static var NON_EXPORTABLE_PROPERTY_NAMES : Set = { KeyableBean.ID_DYNPROP.Name, KeyableBean.PUBLICID_DYNPROP.Name, Extractable.ARCHIVEPARTITION_DYNPROP.Name, "Subtype" } } /** * EffDated bean property in a bean transfer XML. */ static class BeanImportEffDatedProperty { var _element : BeanImportElement var _attributeName : String construct(element : BeanImportElement, attributeName : String) { _element = element _attributeName = attributeName } /** * Update the referenced FixedID to the specified value. * * @param fixedID */ function updateFixedID(fixedID : String) { _element.updateAttribute(_attributeName, fixedID) } } override function toString() : String { var buffer = new StringBuffer() ElementIds.each(\id -> { var el = getElement(id) buffer.append(el.EntityTypeName) buffer.append(System.lineSeparator()) el.AttributeNames.each(\attr -> { buffer.append(String.format("* %s=%s", {attr, el.getAttribute(attr)})) buffer.append(System.lineSeparator()) }) }) return buffer.toString() } }