LSPS documentation logo
LSPS Documentation
Change Proxy: Transient Data of Shared Records

To work with preliminary business data in one or multiple shared record instances, create a change proxy set with proxies of your shared records and introduce your changes to the proxies.

A proxy exists on a proxy level greater than 0: the "real" records exist on proxy level 0 and are not considered proxy objects. When you create a proxy from a record, the proxy exists on level 1; when you create a proxy from this proxy, it exists on proxy level 2, etc.

proxySets.png
Record instances with their proxy trees
Proxies of a record have to constitute a continuous chain of proxies across all their proxy levels: when you create a proxy of a record on proxy level 2, its proxy is automatically created also on level 1.

A proxy is created by a proxy set: a proxy set holds its proxies with their changes and holds the information on its proxy level. To apply the changes on the proxies held in a proxy set, merge the proxy set.

Proxies are useful especially in forms to allow to collect and validate all data before persisting it; refer to Using Proxies in Forms, and for usage examples to Creating a Model Popup and Creating a Public Popup.

Creating a Proxy

A proxy is created by a proxy set and it is the proxy set that defines the proxy level of its proxies. Hence to create a proxy, you need to first create a proxy set:

  1. To create a proxy set, call the createProxySet() function:
    • To create a proxy set on proxy level 1, call the function with the null parameter.
    • To create a proxy set on another proxy level, call with the parameter defining the underlying proxy set.
  2. To create proxies in a RecordProxySet, use its proxy() method:
    • To create a proxy of existing record instances:

      • proxy(T record*, Property... property) or proxy(T record*, List<Property> properties)
      • proxies(Collection<T> records*, Property... properties) or proxies(Collection<T> records*, List<Property> properties)

      If the proxy of the record already exists in the proxy set, the method returns the proxy.

    • To create proxies of record types, which is useful for cases when the record instance is to be created only once all the record data has been collected:
      • proxy(Type<T> recordType*)
      • proxyOfProperties(Record record*, Property... properties)

Example: Creating a proxy set on level 1 and creating a proxy:

//creating a proxy set on proxy level 1:
def RecordProxySet rps := createProxySet(null);
//creating a proxy of a record type in the proxy set:
rps.proxy(Persona);
//creating a proxy of a record in the proxy set:
rps.proxy(john);

Each proxy exists in a continuous chain of proxies across all previous proxy levels. If the proxy on a previous level does not exist, it is automatically added to the underlying proxy set on the lower level.

Consider the following case:

def SimpleRecord rec := new SimpleRecord(number -> 1);
//empty proxy set on level 1:
def RecordProxySet set1 := createProxySet(null);
//proxy set on level 2:
def RecordProxySet set2 := createProxySet(set1);
//proxy of rec added to proxy set on level 2:
def SimpleRecord proxy2 := set2.proxy(rec);

On runtime proxy of rec is created in set1 when it is created in set2. This allow successful merge of the proxy from set1 to set2 and to its rec when requested.

Creating a Proxy of a Related Record

  • To create a proxy of a related record instance, use the proxy(<RecordInstance>, <PropertiesOfRelatedRecords>, ..) call when creating the proxy.
    def Persona john := new Persona(name -> "John", address -> new Address());
    def RecordProxySet rps := createProxySet(null);
    //creates a proxy of john and its related address in rps:
    def Persona johnProxy := rps.proxy(john, Persona.address);
  • To create a proxy of a record type and its related record type, create the proxies and assign the related proxy to the proxy:
    def Persona personaProxy := rps.proxy(Persona);
    personaProxy.address := rps.proxy(Address);
  • To create a proxy of a related record type at the end of a set or list relationship and merge it, create the proxies of record on both end and use add to establish the relationship on the proxy level:
    def Persona personaProxy := rps.proxy(Persona);
    def Address a1 := rps.proxy(Address);
    personaProxy.addresses.add(a1);

Important: If you don't create a change proxy of a related record, the system will access the record instance on the underlying level when you navigate from the proxy to the related record. Let's take the following data model and code:

proxyRecordsExample.png
//shared record john is created and persisted:
def Persona john := new Persona(address -> new Address());
//proxy set on proxy level 0 is created:
def RecordProxySet rps := createProxySet(null);
//proxy of the john record is created in the set:
def Persona johnProxy := rps.proxy(john);
//proxy john is changed:
johnProxy.name := "John";
//NOT PROXIED address is changed:
johnProxy.address.country := "USA";

In this case, a record with an empty person and the address country "USA" is persisted.

Note that navigation across multiple relationships is not supported in the proxy() call; hence for the example data model, this call is not valid:

def Persona johnProxy := rps.proxy(john, Persona.address.street);

Merging a Proxy Set

To merge a proxy set into the underlying records or proxies, call merge(Boolean checkConflicts*), which returns the list of merged records.

Note: It is possible to merge a proxy directly to the underlying context without a RecordProxySets object; however, when you use a RecordProxySet and attempt to add a proxy object, such as, a proxy of a related record, the RecordProxySet checks if such a proxy already exists among its proxies thus keeping a clean tree of its proxied objects. It is strongly recommended to prefer RecordProxySets over direct merge.

If using optimistic locking on your records, consider using the merge call of with the Boolean parameter checkConflicts set to true so the merge checks for conflicts on the underlying records or proxies and handle the possible conflicts.

Handling Optimistic Lock on Proxy Set Merge

The merge(true) call on a RecordProxySet fails if one of its proxies tries to merge its changes into a record with optimistic locking that has been changed by another transaction since the proxy was created.

For proxies of records with optimistic locking, consider either handling the possible optimistic-lock exception or adjust your data model so that the collisions cannot occur. For example, aggregate the data that might collide in a collection and create the resulting entity later.

Note that the merge(true) call might succeed in this scenario

  • when the underlying record was changed in the same transaction;
  • if the underlying record as well as its proxies are created in the same transaction.

Catching OptimisticLockException

def RecordProxySet rps := createProxySet(null);
rps.proxy(myRec);
try rps.merge(true)
    catch "com.whitestein.lsps.common.OptimisticLockException" ->
    //log a message if the record has been changed:
    log("failed to merge changes", 100)
end

Important: If you assign to a proxied property an object from a higher proxy level, on merge, the association is applied on the target object: the original relationship is removed.

Deleting a Record Using a Proxy

To delete a proxy, call the deleteRecord() function on the proxy: this will delete the underlying record or proxy on merge.

//persona john is persisted:
def Persona john := new Persona(name -> "John");
def RecordProxySet rps := createProxySet(null);
//proxy of john created on proxy level 1:
def Persona johnProxy := rps.proxy(john);
//proxy object is deleted (persisted shared record still exists):
deleteRecords(johnProxy);
//on proxyset merge, the persisted john record is deleted:
rps.merge(false)

Checking if an Object is a Change Proxy

You can check if a record is a proxy with the isProxy() call.

Checking the Proxy Level of a Change Proxy

You can check the proxy level of a proxy with a getProxyLevel(<Proxy>) call, which returns an Integer. 0 designates the execution level with "real" context data.