AW: [sdo] ISSUE 119: Projection and Keys

From
Barack, Ron <>
Date
2008-08-14T11:20:18+00:00
ID
Thread
AW: [sdo] ISSUE 119: Projection and Keys
Title: ISSUE 119: Projection and Keys

Hi Everyone,

 

I've modified the proposal a bit, based on 
Frank's suggestion that projection from keys to entities be made more 
intelligent.  In fact, I think that the behavior is now intelligent enough 
so that a second project method giving a "restoreContext" is no longer 
required.  New text is in blue. 

 

Comments of course are very 
welcome.

 

Ron

 

Use Cases for Keys and Projection 
We begin with a motivating 
use case. 
Domain 
models tend to be larger and complex than the views used by individual clients. 
Moreover, when going from a large server-side model, it is necessary to define 
the scope of the data which should be transmitted to a remote client. The 
boundaries of the data which should be transmitted in a single packet must 
somehow be defined. On the other hand, the door must be left open for clients to 
further explore the model in succeeding calls to the server.

Imagine a service that returns an Employee object. In the 
domain model, Employee has a refence to Department, and Department in turn has a 
list of all Employees. If we define the transmission packet to be the transient 
closure reachable from Employee, then we can never send a single employee, we 
will always send the complete list of employees in the department.

Our approach is to use keys to represent the boundary points 
in a transmission packet. It is assumed that clients that wish to explore the 
model beyond these boundaries will be able to use the keys to somehow perform 
lookups, but the API through which this is done is out-of-scope for SDO (being 
more of a DAS issue).

As an example, we can imagine that the server has a SDO type 
system in which Employee has a property with type Department. In the client's 
type system, however, the corresponding property has type {commonj.sdo}string. 
This value would represent the identity of the Department. The basic 
functionality we wish to achive is to allow projection between these two 
HelperContexts.

Keys and Projection 
A type and its keyType are 
compatible with each other.  That mean, an object with a reference to an 
entity in one context can be projected onto an object with a reference to the 
key type in another context.

We illustrate this with an example. 

  
    
HelperContext #1

Type Employee
* 
    property - id (Integer) - KEY
* property - name (String)
* property - 
    direct-report (List of Employee) 

is compatible with the metamodel 

  
    
HelperContext #2

Type Employee
* 
    property - id (Integer) - KEY
* property - name (String)
* property - 
    direct-report (List of Integer)

Here we see the type of the direct-report property has been 
replaced in the second HelperContext with the corresponding key type.

The projection from an entity to its key has some important 
semantic differences when compared with projection between entities.  
Projection between entities creates a new view of the same underlying 
data.  By contrast, a key does not represent a view, but rather a reference 
to an entity in the underlying data model.  When working with different 
views of the same underlying data, it is natural that changes in one view are 
reflected in all other views.  By contrast, changing the value of a key 
changes the target of the reference rather than the value of the key property in 
the referenced data object.  When projecting from an entity to a key, a new 
instance of the key is always created.  By contrast, when we project from 
entity to entity, we get the same number of objects in the target HelperContext 
that we had in the source HelperContext. 

Conversely, when projecting from a key to an entity then for 
each distinct key value within the graph being projected, all references to 
that key must resolve to the same entity.

In many 
scenarios data will round-trip between contexts, including between contexts 
in which entity map to keys.  Let us consider two context Ce and 
Ck, representing the entity and the key context, respectively, and a 
DataObject Oe, in context Ce.  Projecting Oe into Ck returns 
a DataObject, Ok.  The transitive closure reachable from Ok is 
Gk.  Every key value in Gk maps to an single entity in Ce, and it is this 
entity is that is found when Ok (and effective, all of Gk) is projected back 
into Ce.  In cases where the user has set a key property to a value that is 
not found in Gk, then, as a result of projecting Ok into Ce, a new entity will 
be created.  The created entity has default values for all properties other 
than the key fields.

We 
illustrate with an example.  Note that the example uses containment 
relationships in both contexts.  This is done for clarity, since it allows 
an XML representation of the data.  The use-case, however, is stronger when 
the contexts have different (or perhaps no) containment structures.  
Imagine the following data in HelperContext #1

<employee 
id="11">
        <name>Foo 
Bar</name>
        <direct-report 
id="21">
        
        <name>Jane 
Doe</name>
        
</direct-report>
        
<direct-report id="31">
        
        <name>Jim 
Jones</name>
        
</direct-report>
        
<direct-report id="22">
        
        <name>John 
Smith</name>
        
</direct-report>
</employee> 
After 
projecting to HelperContext #2, we have the following data 
<employee 
id="11">
        <name>Foo 
Bar</name
        
<direct-report>21</direct-report>
        
<direct-report>31</direct-report>
        
<direct-report>22</direct-report>
</employee> 

If we imagine the client changes the list of direct 
reports, so that the second item in the list has value “41” instead of “31”, 
then the meaning of the change is not that the employee with name “Jim Jones” 
now has a new ID, but that “Jim Jones” has been replaced by another 
employee.

On projecting from context 2 back into context 1, the 
instance of Employee with id="22" would be detached from the Employee with 
id="11".  Also a new Employee with id="41" would be created and 
added as a direct report to the Employee with id="11".  If the 
graph in context 1 were in scope of a ChangeSummary, then because the 
"direct-report" property in context1 is a containment property, the 
change would be tracked as a delete or a create.  If the 
relationship were non-containment, the change would be tracked as a 
modification to the employee with id=11, not as a delete to the 
employee with id=22 or as the creation of a new employee with 
id=41; semantically, it is only the reference to entity 22 that has 
changed, the entity itself still exists, and is 
unaltered.   Projecting this data from context 
2:

<employee 
id="11">
        <name>Foo 
Bar</name
        
<direct-report>21</direct-report>
        
<direct-report>41</direct-report>
        
<direct-report>22</direct-report>
</employee> 

Into context 1 yields: 
<employee 
id="11">
        <name>Foo 
Bar</name>
        <direct-report 
id="21">
        
        <name>Jane 
Doe</name>
        
</direct-report>
        
<direct-report id="41"/>
        
<direct-report id="22">
        
        <name>John 
Smith</name>
        
</direct-report>
</employee> 

Projection between entities and keys becomes more powerful when the 
relationship through which the entities tie into the graph are 
non-containment.  The following example shows how a complex model that 
lacks containment relationships can be projected onto a context that requires a 
specific XML serialization, which implicitly prunes the orignal domain to the 
requirements of a specific client.

HelperContext #1

Type School
* property - name 
(String) - KEY
* property - students (Student) - many=true
* property 
- courses (Course) - many=true

 Type Student
* 
property - name (String) - KEY
* property - courses (Course) - 
many=true, containment=false, opposite=students
* property - school 
(School) - containment=false, opposite=students

Type Course
* property - name 
(String) - KEY
* property - students (Students) - many=true, 
containment=false, opposite=courses
* property - school (School) - 
containment=false, opposite=courses

Notice the m:n relationship between Student and Course.  If we 
imagine a service that should expose this domain model to clients, it is 
possible that some clients will wish to obtain the list of students 
participating in a particular course, while other clients may wish to obtain the 
list of courses in which a particular student is enrolled.  The application 
cannot determine based on the structure of the data which of the two possible 
containment structures is "correct".  Notice also that returning the 
transitive closure would return all the data associated with the entire 
school.

In this example the client wants the data structured 
according to the following XSD

<?xml version="1.0" 
encoding="UTF-8"?>

<schema xmlns="http://www.w3.org/2001/XMLSchema" 
targetNamespace="http://projection" xmlns:tns="http://projection" 
elementFormDefault="qualified">

    <complexType 
name="School">

    
      
<sequence>

    
      
    <element name="students" 
type="tns:Student" maxOccurs="unbounded"/>

    
      
</sequence>

    
      <attribute name="name" 
type="string"/>

    
</complexType>

    
<complexType name="Student">

    
      
<sequence>

    
            
<element name="courses" type="string" 
maxOccurs="unbounded"/>

    
      
</sequence>

    
      <attribute name="name" 
type="string"/>

    
</complexType>

    

    
<element name="school" type="tns:School"/>

</schema>

Notice that we are 
imposing a containment structure on the original context, as well as pruning it 
by replacing the course entity by the corresponding key.  The following 
code illustrates the behavior of the project 
method.

  

  

  

   DataObject 
  cal = 
  _helperContext.getDataFactory().create(School.class);
  

  
   // Create the 
  SDO graph

  
   
  cal.set("name","Berkeley");

  
   DataObject 
  billy = cal.createDataObject("students");

  
   
  billy.set("name", "Billy Bear");

  
   DataObject 
  bob = cal.createDataObject("students");

  
   
  bob.set("name", "Bobbie Bear");

  
   DataObject 
  basketWeaving = cal.createDataObject("courses");

  
   
  basketWeaving.set("name", "Basket Weaving");

  
   DataObject 
  algol = cal.createDataObject("courses");

  
   
  algol.set("name", "Algol");

  
   DataObject 
  revolution = cal.createDataObject("courses");

  
   
  revolution.set("name", "Revolution");

  

  
   // hook 
  things up

  
   
  billy.getList("courses").add(basketWeaving);

  
   
  billy.getList("courses").add(algol);

  
   
  bob.getList("courses").add(basketWeaving);

  
   
  bob.getList("courses").add(revolution);

  

  
   // Create a 
  second context defined by an XSD

  
   HelperContext 
  hc2 = HelperProvider.getNewContext(); 

  
   
  hc2.getXSDHelper().define(getClass().getClassLoader().getResourceAsStream("com/sap/sdo/testcase/internal/pojo/ex/projection.xsd"), 
  null);

  

  
   // Project 
  from the java context to the XSD context

  
   DataObject 
  projection = hc2.getDataFactory().project(cal); 

  

  
   // Produce 
  XML based on the XSD

  
   String xml = 
  hc2.getXMLHelper().save(projection, "http://projection", 
     "school");

  

  
   // I'm 
  imagining here that sending the XML out over the wire (eg, using it as a 
  response to a

  
   // WebService 
  request. On the client side, we go from the XML back to SDO. We use the 
  context

  
   // based on 
  the XSD.

  
   DataObject 
  projection2 = hc2.getXMLHelper().load(xml).getRootObject();

  

  
   // We can 
  make some changes. We can add a new course...

  
   
  projection2.getList("students.0/courses").add("Fortran and 
  You");

  

  
   // So, now 
  the trip back to the server…

  
   //I'm 
  skipping the XML step, and simply projecting the modified (XML oriented) data 
  back into

  
   // my java 
  context

  
   DataObject 
  cal2 = _helperContext.getDataFactory().project(projection2);

  
      

  
   // 
  Test that there is one entity per key value

  
   DataObject billy2 = 
  (DataObject)cal2.getList("students").get(0);

  
   DataObject basketWeavingBill = 
  (DataObject)billy2.getList("courses")

  
                                                 .get(0);

  
   DataObject bob2 = (DataObject)cal2.getList("students").get(1);

  
   DataObject basketWeavingBob = 
  (DataObject)bob2.getList("courses").get(0);

  
   assertSame(basketWeaving2, 
  basketWeavingBob);