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

To work with preliminary data for one or multiple shared record instances, and apply or discard the modifications later, create a change proxy over your shared record and change the data of this object instead: the change proxy object holds your changes, while the proxied record instance remains unchanged. You can then either apply the changes to the proxied record instance or abandon the change proxy.

Important: Proxies are not supported in ui::forms. Use the View Model component instead.

When you create a change proxy, it is not created in the current execution context but in its equivalent context on a proxy execution level. The proxy execution level overlays the current level.

Proxies can see only data in their own proxy level and the levels of their ancestors: All proxy objects can see the original record instance and any proxies in the overlay levels.

proxylevels.png
Proxy levels: Proxy object of the shared record exists on proxy level 1. It can see the record and any other data in its context. The proxy of the proxy lives on proxy level 2: It can see proxy level 1 and the original record; the proxy of record cannot see proxy level 2.
Proxies are useful especially in forms; refer to Using Proxies in Forms, and for usage examples to Creating a Model Popup and Creating a Public Popup.

Creating a Change Proxy of a Record

You can create a proxy either based on an existing record instance or based on a record. Once you have applied all the required changes on the proxy, you can merge it.

  • To create a proxy over a record instance, use the proxy(<RecordInstance>, <PropertiesOfRelatedRecords>, ..) or proxy(Collection<RecordInstances>, <PropertiesOfRelatedRecords>, ..). The proxy is automatically created in the context on the immediate proxy level. The changes are applied on the underlying record instance on merge. Note that the record instance might be a proxy as well.
    //creating proxy of the shared record instance and
    //properties of the related shared records:
    proxy(jane, Persona.address)
    proxy([jane, john], Persona.address);
    
  • To create a proxy over a record on proxy level 1, use proxy(<SharedRecord>). Alternatively use proxy(<SharedRecord>, <ProxyLevel>) to create a proxy on another level. This allows you to create and abandon the proxy without actually ever having created the record instance. On merge, a new record instance with the proxy data on the underlying level is created.
    //proxy of the record Persona on the proxy level 2:
    proxy(Persona, 2)
    

Creating a Change Proxy of a Related Record

You can create the proxy of a related record instance with the proxy(<RecordInstance>, <PropertiesOfRelatedRecords>, ..) call when creating the proxy.

If you need to create a proxy of a record instance with a related record without creating the related record on the underlying execution level, do the following:

  1. Create a variable that will hold the proxy of the record, for example, def Persona persona := proxy(Persona)
  2. Create the related proxy from the variable using the dot operator, for example, persona.address := proxy(Address)

If you do not 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.

Example related record proxy: Let us assume a record hierarchy with multiple related records:

proxyRecordsExample.png
Let's have the following proxy:

//Shared record instance:
def Persona persona :=
    new Persona( 
       name ->  "Night Mare",
       address -> new Address(street -> new Street(number -> 0, name -> "Elm Street")),
       documents -> new IDs(
         driversLicense -> "A000-0000-0000",
         insuranceNumber -> "000-00-0000-A",
         passport -> "00000000"
      )
    );
//Proxy:
def Persona proxyPersona := proxy(persona, Persona.address);

We have created a proxy object only over the Persona record and its Address record. If we access its documents via proxyPersona.documents, we will access the non-proxied record Address: to create a proxy of such a related record, add the property to the proxy() call:

def Persona proxyPersona := proxy(persona, Persona.address, Persona.documents);

Note that it is not possible to proxy the Street record from Persona.

Creating a Change Proxy of Record Properties

You can create the proxy of record properties with the proxyOfProperties(<RecordInstance>, <Properties>, ..) call, for example proxyOfProperties(a, Author.name). If you want to create proxies of all record properties, call the function without specifying the properties, for example, proxyOfProperties(a).

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 the getProxyLevel(<Proxy>) call, which returns an Integer. 0 designates the execution level with "real" context data.

Merging a Proxy

To merge proxies or to create the proxied record without optimistic lock check, you can use the following functions:

  • mergeProxies(recordInstance1, ..) and mergeAllProxies(List<RecordInstances>) merge the changes on the proxies into the underlying records or the immediate parent proxies.
  • mergeProxiesDeep(recordInstance1, ..) and mergeAllProxiesDeep(List<RecordInstances>) merge the changes on the proxies, their proxy properties, and any proxies of their related records on the same proxy level.

For example, in the case of the proxy of a persona with an address, mergeProxiesDeep() merges the persona as well as its related address proxy.

The merge() call store the data in the record even if the record uses the optimistic lock mechanism. To perform the optimistic locking check before merging, use an overloaded merge call.

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.

Checking Conflicts on Proxy Merge

When merging a proxy of a versioned shared record with active optimistic locking and you want to apply the optimistic locking, use a merge call that checks whether the underlying entity has been changed since the proxy was created:

  • mergeAllProxies(checkConflicts, Collection<proxyObject>), for example, mergeAllProxies(true, [ proxyPersona, proxyAddress]);
  • mergeProxiesDeep(checkConflicts, proxyPersona), for example,mergeProxiesDeep(true, proxyPersona, proxyAddress);

The merge fails if a proxy or shared record on the underlying level has been modified in another transaction since the proxy had been created or when the underlying record is changed in the same transaction.

When merging with checkConflicts set to true, consider either handling the potential optimistic-lock exception or adjust your data model so that a collision cannot occur; for example, aggregate the data that might collide in a collection and create the resulting entity later.

Example merge of related proxies: Author and Book are in 1:1 relationships named book and author

def Book book := new Book(title -> "Catch-22");
def Author heller := new Author(name -> "Heller", book -> book);
def Author hellerProxy := proxy(heller, Author.book);
hellerProxy.book.title := "God Knows";
 
//book title is update in the db to "God Knows":
try mergeProxiesDeep(true, hellerProxy)
    catch "com.whitestein.lsps.common.OptimisticLockException" ->
    //log a message if the record has been changed:
    log("failed to merge changes", 100)
end