r15 - 08 Mar 2006 - 10:44:08 - AlecFlettYou are here: OSAF >  Projects Web  >  ChandlerHome > ObsoleteChandlerInformation > ParcelFramework > ParcelManager
This page is out of date and irrelevant for versions of chandler after 0.5. Starting in 0.6 parcels are written and managed completely in Python.

Parcel Manager

Overview

The parcel framework handles the addition of new functionality, schema, and data to the Chandler application. Much of Chandler's core functionality, aside from bootstrapping code, is integrated into the app via this framework. This document explains how to use the parcel manager to load items (described in XML) into the repository, and how to use other services such as parcel namespace lookups.

Parcels

Parcels are the unit of extensibility in Chandler; they contain:

  • XML files which describe the items (schema or data items) to be loaded into the repository
  • Python files adding functionality
  • Other data files such as images or HTML, etc.
  • A namespace name which uniquely identifies the parcel

Structure

There are three hierarchies involved in the structure of a parcel:

  1. The filesystem directory hierarchy
  2. The repository path hierarchy
  3. The python package+module hierarchy

As much as possible, the goal is to keep the mapping between the three simple and straightforward. To that end, the filesystem directory containing the parcels to be loaded -- let's refer to this as the "parcel root" -- is added to sys.path (the python class loader's search path). Currently in Chandler the parcel root is chandler/parcels. When items are loaded into the repository from a given parcel.xml, the repository paths they are given correspond to that parcel.xml's filesystem location relative to the parcel root.

Using the Content Model parcel as an example:

  • Filesystem location: chandler/parcels/osaf/contentmodel/parcel.xml
  • Repository path: //parcels/osaf/contentmodel
  • Python package: osaf.contentmodel

Parcel directories may contain subdirectories, each containing a parcel.xml file. Remember that if your parcel contains python source you will need to include an _ _ init _ _.py file to let python know the directory represents a package (otherwise imports will fail).

Parcel XML Syntax

Defining Items

The purpose of parcel.xml is to define items which get loaded into the repository, and also to set attribute values on those items. There are two flavors of XML elements appearing in parcel.xml: item elements and assignment elements. Item elements are those that have an "itsName=" attribute; when the parcel manager sees one of these elements, it creates an item in the repository. If an element does not have "itsName=" then it is assumed to be an assignment element, setting the value of one of the parent element's attributes. For example:

<Foo itsName="bar">
   <x value="y" />
</Foo>

Since the outer element, 'Foo', has an itsName= attribute, an item named 'bar' of kind 'Foo' is created in the repository. The inner element, 'x' is treated as an attribute assignment on the 'bar' item, setting bar.x = y. We'll talk more about attribute assignments later.

Parent and Child Items

Nested item elements can be used to indicate parent-child relationships in the repository, i.e., if you define an item inside another item's XML element, the inner item will be a child of the outer item in the repository.

Defining the Parcel Item

The root element in a parcel.xml file is always 'Parcel' from the 'http://osafoundation.org/parcels/core' namespace.

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core" >
</Parcel>

Seeing this XML, the parcel manager would create a Parcel item named 'example'. Note: the name of a parcel item must match the name of its encompassing directory. Each parcel needs a unique namespace name to identify it, and you can assign one via the parcel 'namespace' attribute:

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core" >

   <namespace value="http://morgen.com/chandler/parcels/example"/> 

</Parcel>

Once a parcel has an assigned namespace name, other parcels can refer to it via that name (both in XML and in python). If a parcel item does not have an explicit namespace name assigned, it will inherit one from its parent parcel -- with the parcel's own name appended to it. For example, if I create a 'data' parcel below my 'example' parcel, the data parcel's namespace name would automatically be 'http://morgen.com/chandler/parcels/example/data' if another name wasn't explicitly assigned. In Chandler, parcels/osaf/parcel.xml defines the 'http://osafoundation.org/parcels/osaf' namespace name, and therefore all OSAF parcels automatically inherit an appropriate namespace name.

To make use of another parcel (in this case, the Content Model) via it's namespace name:

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core" 
   xmlns:cm="http://osafoundation.org/parcels/osaf/contentmodel" >

   <namespace value="http://morgen.com/chandler/parcels/example" />

   <cm:Note itsName="note1">
   </cm:Note>

</Parcel>

Here, an item named 'note1' will be created in the repository as a child of the example Parcel item; the kind to use for creating 'note1' is determined by looking up the element name "Note" in the namespace referred to by 'xmlns:cm', which is done as follows:

  1. The parcel manager determines which parcel item the 'http://osafoundation.org/parcels/osaf/contentmodel' namespace name refers to (via a namespace-to-parcel lookup table)
  2. If that parcel item has defined a namespace map (a dictionary which maps strings to items), the parcel manager checks to see if "Note" is one of the keys; if so, the item it refers to is used for the kind
  3. The parcel manager checks to see if that parcel item has a child named "Note", then that item is used for the kind
  4. If both checks fail, an error is thrown

Using such a namespace map a parcel developer could flatten a tree of Kinds and Attributes, for example, and then rearrange them without breaking existing code. Defining a namespace map is optional. For an example, see parcels/core/parcel.xml, which has a map that would allow us to rearrange the layout of the data model schema items without breaking parcels.

Attribute Assignments: Literals

As mentioned above, setting attribute values on items is done via assignment elements (those XML elements that do not have an itsName= attribute) nested within an item element. You can use one of two forms for setting a value:

<cm:Note itsName="note1">
   <displayName value="My Note" />
   <description>Here is my note</description>
</cm:Note>

In the second form, the value appears between the beginning and ending tags. In the first form the end tag is not needed, and the value appears in quotes using the "value=" syntax.

If the attribute you are assigning to has a cardinality of "list", you may assign values to that attribute with the same syntax as above, just do separate assignments for each value:

<cm:Note itsName="note1">
   <displayName value="My Note" />
   <description>Here is my note</description>
   <issues value="An issue" />
   <issues value="Another issue" />
   <issues value="Yet another issue" />
</cm:Note>

For an attribute whose cardinality is "dict", assignment elements have an additional 'key=' attribute:

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core">

   <namespaceMap key="Integer" value="//Schema/Core/Integer" />
   <namespaceMap key="Float" value="//Schema/Core/Float" />
   <namespaceMap key="String" value="//Schema/Core/String" />

</Parcel>

Attribute Assignments: References

In addition to the literal values (strings, numbers, and other data types), you may assign references to repository items as well; this is done by specifying 'itemref='. Here is an example of one parcel ('example') containing an item ('note1') with a reference to an item ('contact1') in a different parcel ('data'):

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core" 
   xmlns:cm="http://osafoundation.org/parcels/osaf/contentmodel"
   xmlns:data="http://morgen.com/chandler/parcels/example/data" >

   <namespace value="http://morgen.com/chandler/parcels/example" />

   <cm:Note itsName="note1">
         <creator itemref="data:contact1" />
   </cm:Note>

</Parcel>

<Parcel itsName="data"
   xmlns="http://osafoundation.org/parcels/core" 
   xmlns:cm="http://osafoundation.org/parcels/osaf/contentmodel" >

   <cm:Contact itsName="contact1">
   </cm:Contact>

</Parcel>

When assigning to note1's 'creator' attribute, the parcel manager will determine which item to make a reference to by using the same lookup process described above. When it sees "data:contact1", the parcel manager will lookup the parcel referred to by xmlns:data, then use the item named "contact1" within that parcel. Had the data parcel defined a namespace map, it would have first looked in there for a key "contact1". The same applies to making references to items within your own parcel -- you will need to define an xmlns: prefix which maps to your own namespace:

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core" 
   xmlns:cm="http://osafoundation.org/parcels/osaf/contentmodel"
   xmlns:self="http://morgen.com/chandler/parcels/example" >

   <namespace value="http://morgen.com/chandler/parcels/example" />

   <cm:Note itsName="note1">
         <creator itemref="self:contact2" />
   </cm:Note>

   <cm:Contact itsName="contact2">
   </cm:Contact>

</Parcel>

Creating a Schema

Creating a schema involves defining Kind and Attribute items within a parcel; once you have defined a Kind item, you can then create "data" items based on that Kind (think object-oriented 'classes' and instances of those classes). Note: in the current implementation, a data item cannot be defined in the same parcel.xml file as its Kind item. The Attribute items associated with a Kind determine what attributes a data item will have. Kinds can be given superKinds (multiple inheritance is allowed) and attributes are inherited from superKinds.

If you wish to add custom behavior for items of a given Kind, you may associate a python class to that Kind via the 'classes' attribute; the python objects representing the data items of that Kind will then be of that class:

<Parcel itsName="example"
   xmlns="http://osafoundation.org/parcels/core" 
   xmlns:cm="http://osafoundation.org/parcels/osaf/contentmodel"
   xmlns:self="http://morgen.com/chandler/parcels/example" >

   <namespace value="http://morgen.com/chandler/parcels/example" />

   <Kind itsName="Photo">
         <displayName value="Photo Kind"/>
         <superKinds itemref="cm:ContentItem"/>
         <classes key="python" value="morgencom.example.Photo.Photo"/>

         <description value="A Photo Content Item"/>

         <Attribute itsName="dateTaken">
            <description value="When the photo was taken"/>
            <cardinality value="single"/>
            <type itemref="DateTime"/>
         </Attribute>

         <Attribute itsName="file">
            <description value="The path to the photo file"/>
            <cardinality value="single"/>
            <type itemref="String"/>
         </Attribute>

   </Kind>

</Parcel>

The parcel.xml file above defines a Photo Kind which is a subKind of Content Item, and two Attribute items (dateTaken and file). By nesting the Attributes within the Kind they are automatically associated with it. If you want to associate an Attribute that is defined outside of the Kind, assign it to the Kind's 'attributes' attribute.

For a description of the various attribute 'aspects' such as cardinality and type, please see the epydoc for the Item class.

Setting Initial Values

When defining attributes you may optionally specify an initial default value they will be set to when an item is created. By default, if no initialValue is assigned, an item's attribute won't exist when a data item doesn't specify anything for that attribute.

To set the initial value of an itemref to None:

xmlns:core="//Schema/Core" ...
<initialValue itemref="core:None" />
Or, if //Schema/Core happens to be your default namespace then you would do it like this:
<initialValue itemref="None" />

If an attribute has cardinality "list" or "dict", then to set the initial value to be an empty list [ ] or dict { }:

<initialValue />

If you want to set the initial value for a single-cardinality attribute, be sure to set the type like this:

<initialValue type="String" value=""/>

Special Item Methods

There are several method conventions to be aware of when dealing with the parcel loader and the repository. When writing the classes for your Kinds, you may want to implement these. The methods that can get called are:

  1. The standard _ _ init _ _ ( ) method
  2. startupParcel( )
  3. onItemLoad( )
  4. onItemUnload( )
  5. onItemCopy( )

_ _ init _ _ ( ) is called when the parcel loader encounters an item definition, and is called before the attribute assignments are performed. Of course if you create items directly using the repository API, that will call this method as well. Over the lifespan of an item, this will only ever get called once.

startupParcel( ) is called on every Parcel item after all the parcels have been loaded, and all items have been created and had their attributes assigned. This happens each time Chandler starts up.

onItemLoad( ) gets called each time an item is loaded from the repository. Similarly onItemUnload( ) gets called whenever the repository is about to unload an item.

onItemCopy( ) is called whenever an item is copied.

In the future, the set of methods that get called during the lifespan of an item may change.

Lookup Mechanism

Similar to how parcels refer to each other in XML via registered namespace URIs, in python code you can find another parcel via (assuming 'pm' is an instance of application.Parcel.Manager)...

CPIA="http://osafoundation.org/parcels/osaf/framework/blocks"
cpiaParcel = pm.lookup(CPIA)

...or do an item lookup through a namespace map...

blockItem = pm.lookup(CPIA, "Block")


Additional Information


-- MorgenSagen - 20 Oct 2004

Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r15 < r14 < r13 < r12 < r11 | More topic actions
 
Open Source Applications Foundation
Except where otherwise noted, this site and its content are licensed by OSAF under an Creative Commons License, Attribution Only 3.0.
See list of page contributors for attributions.