Update case 4: Resolved value cannot be easily resolved by id alone

In some situations, the target of a foreign key field cannot be easly determined using id only. There are several reasons this could occur:

  • The entity might be effdated. It is difficult to load effdated entities using id alone.
  • The id might be unique only within the context of its parent. For example, a coverage’s id is a coverage pattern, but the same coverage (with the same id) can exist under multiple vehicles or buildings on the same policy. The id alone does not uniquely identify the desired coverage.
  • The id might be computed, which would make it possible but impractical to load the entity directly from the database. For example, for account contacts, the id is the id of the linked Contact entity. Querying on the AccountContact is possible, but it would require at least a join on the Contact table.
  • The id might not related to an entity. For example, the Cloud API PolicyContact maps to a PolicyContactWrapper, which is a POGO object and not a database entity.

For example, suppose you are using the Personal Auto line of business in the base configuration of PolicyCenter. In this line, there is a PersonalVehicle entity which has an array of VehicleDrivers. You want to be able to identify one of the drivers as the "primary driver" of the vehicle. To do this, you add a PrimaryDriver_Ext edge foreign key to PersonalVehicle that points to the primary driver. However, VehicleDriver is an effdated entity, and therefore the correct object to reference cannot be loaded from the bundle or database using only its id.

The KeyableBeanJsonValueResolver resolves foreign keys using only the target's id. This will not work for the circumstances mentioned in the previous list. To load these types of entities, you must use the CollectionBasedJsonValueResolver.

The CollectionBasedJsonValueResolver

The CollectionBasedJsonValueResolver can identify targets when the target cannot be easily identified by id alone, and when necessary, it can ensure that the root object and target object share a common ancestor. It has two getters:

  • get ResolvedValueType - Returns the type of the target object
  • get AncestorType - Returns the type of the common ancestor

It also has two methods for identifying the target:

  • getPossibleResolvedValues - Returns the collection that contains the target object
  • idMatchExpression - Returns a predicate that, when run on the collection, will identify the target

The CollectionBasedJsonValueResolver is abstract and therefore is never used directly. If you want to use it, you must implement a concrete subclass of this resolver. In the subclass, you must override the previously mentioned getters and methods. You then reference this subclass in the appropriate updater file.

Extending the CollectionBasedJsonValueResolver

When you extend the CollectionBasedJsonValueResolver, do the following:

  • Guidewire recommends putting the class in an appropriate package in the gw.rest.ext.<product> package. Guidewire also recommends naming the package after the target entity. For example, a resolver used for foreign keys that reference PersonalDriver instances could be in the gw.rest.ext.pc.policyperiod.pa.v1.driver package.
  • Guidewire recommends you name the class <ResolvedEntity>JsonValueResolver.
  • The class must extend CollectionBasedJsonValueResolver<V, A>, where:
    1. "V" is the entity type of the target. (For example, VehicleDriver.)
    2. "A" is the entity type of the ancestor that owns the relevant collection. (For example, PersonalVehicle.)

The class must include overrides of the following getters and methods using the following syntax:

protected override property get ResolvedValueType(): Class<TargetEntityType> {
  return TargetEntityType
}

protected override property get AncestorType(): Class<AncestorEntityType> {
  return AncestorEntityType
}

protected override function getPossibleResolvedValues(ancestor : AncestorEntity) : Collection<TargetEntity> {
  return ancestor.ArrayContainingPotentialResolvedValue
}

protected override function idMatchExpression(id: String) : Predicate<TargetEntity> {
  return <predicate containing the set of elements with matching ids>
}

where:

  • TargetEntityType is the target entity type (such as VehicleDriver)
  • AncestorEntityType is the ancestor entity type (such as PersonalVehicle)
  • ArrayContaininPotentialResolvedValue is the array that contains the target object (such as Drivers)

For example, suppose you are implementing the previously mentioned use case where you want to have a PrimaryDriver_Ext field on PersonalVehicle that points to an instance of VehicleDriver. The value resolver for this business requirement would be as follows.

package gw.rest.ext.pc.policyperiod.pa.v1.driver

uses gw.rest.core.pl.framework.v1.refs.CollectionBasedJsonValueResolver
uses java.util.function.Predicate

class PADriverJsonValueResolver extends CollectionBasedJsonValueResolver <VehicleDriver, PersonalVehicle> {

  protected override property get ResolvedValueType(): Class<VehicleDriver> {
    return VehicleDriver
  }

  protected override property get AncestorType(): Class<PersonalVehicle> {
    return PersonalVehicle
  }

  protected override function getPossibleResolvedValues(ancestor : PersonalVehicle) : Collection<VehicleDriver> {
    return ancestor.Drivers
  }

  protected override function idMatchExpression(id: String) : Predicate<VehicleDriver> {
    return \elt -> elt.RestV1EffDatedId == id
  }
}

The isEntityViewable method

An extension of the CollectionBasedJsonValueResolver can also include an isEntityViewable method. This method can be used to control whether the resolved value can be viewed. For example, you could use it to enforce business logic that states a foreign key can point to a object only if the object is in a draft or open state.

The implementation of an isEntityViewable method for the CollectionBasedJsonValueResolver is the same as that of the AbstractKeyableBeanJsonValueResolver. For more information, see Update case 3: Accessibility of resolved value is conditional.

Extending the updater

The updater must have a valueResolver property whose typeName is set to the concrete subclass of CollectionBasedJsonValueResolver. For example, the updater from the previous example would be as follows:
"updaters": {
  "PersonalVehicle": {
    "properties": {
      ...
      "primaryDriver_Ext": {
        "path": "PersonalVehicle.PrimaryDriver_Ext",
        "valueResolver": {
          "typeName": "gw.rest.ext.pc.policyperiod.pa.v1.driver.PADriverJsonValueResolver",
          "resolvedValueToAncestorPath": "resolvedValue.Vehicle"
        }
      },
      ...

With effdated entities, the object that has the foreign key property (the root object) and the object that the foreign key points to (the target object) must have a common ancestor.

  • You will always need a resolvedValueToAncestorPath property, as shown above, to identify how to map from the target object to the common ancestor.
  • If the common ancestor is some object above the root object, then you also need a rootToAncestorPath value that identifies how to navigate from the root to the common ancestor.

In this example, the root object (PersonalVehicle) is the common ancestor. Hence, the rootToAncestorPath is not required.