Modeling Business Objects
|
Code examples in this chapter have been reformatted for presentation purposes and, therefore, may contain line number, hidden editing characters (such as tabs and end-of-line characters) and extraneous spaces. If you cut and paste code from this manual, check for these characters and remove them before attempting to use the example in your application.
|
Overview of Windchill Persistence
The Windchill persistence architecture maps Java classes and fields to database rows and columns (schema). Modeling, the manner in which schema is described, consists of annotating the class declarations of Java source files with PTC “GenAs” annotations that include annotation members for describing properties, associations, and other meta-data.
Java annotation processors are invoked during javac compilation and are responsible for generating fields, accessors, RMI and PDS externalization APIs, and other methods (such as equals() and hashCode()). SQL scripts representing the database schema (tables, columns, and indexes) are generated separately post-compilation.
The Java compiler not only generates and compiles the necessary artifacts to implement the model as declared by the annotations, it also produces information required at runtime to introspect on the model. It registers classes (modelRegistry.properties), associations (associationRegistry.properties), and their hierarchy (descendentRegistry.properties) and produces ClassInfo files which are utilized by runtime introspection.
Jython
Jython is a Java implementation of the popular Python programming language. Jython is used in this chapter to provide working code that demonstrates the examples in “real world” scenarios. Jython’s dynamic nature makes it particularly attractive (over Java programs) because its interpreter facilitates interaction and exploration that simply is not possible with compiled code and a debugger. Additionally, while Jython is a Python implementation (on top of Java), the syntax is readily familiar to Java programmers and instantly translatable.
Jython can be obtained from
http://jython.org.
|
The examples using Jython assume a Windchill CLASSPATH, which is readily available if run from inside a Windchill shell (which can be started by invoking <load point>/Windchill/bin shell).
|
Modeling Tables
Modeled class anatomy (GenAsPersistable)
To begin with, here is a simple, functional example. Other sections within this chapter will refer back to this example.
Listing 1: SimpleExample.java
01 package com.acme.example;
02
03 import wt.fc.InvalidAttributeException;
04 import wt.fc.WTObject;
05 import wt.inf.container.WTContained;
06 import wt.util.WTException;
07 import wt.util.WTPropertyVetoException;
08
09 import com.ptc.windchill.annotations.metadata.*;
10
11 @GenAsPersistable(superClass=WTObject.class,
interfaces={WTContained.class},
12 properties={
13 @GeneratedProperty(name="name", type=String.class,
14 constraints=@PropertyConstraints(required=true))
15 })
16 public class SimpleExample extends _SimpleExample {
17 static final long serialVersionUID = 1;
18
19 public static SimpleExample newSimpleExample() throws WTException {
20 final SimpleExample instance = new SimpleExample();
21 instance.initialize();
22 return instance;
23 }
24
25 @Override
26 public void checkAttributes() throws InvalidAttributeException {
27 super.checkAttributes();
28 try {
29 nameValidate(name);
30 } catch (WTPropertyVetoException wtpve) {
31 throw new InvalidAttributeException(wtpve);
32 }
33 }
34 }
A class, SimpleExample (line 16) is defined which extends _SimpleExample. The class is annotated by the GenAsPersistable annotation (lines 11-15), which indicates that this class is to be persisted as a table in the database. The class has a single property called “name” (lines 13–14) which is a (required) String. Additionally, two methods have been implemented; a static factory method (lines 19-23) and an override of the checkAttributes() method (lines 25-33).
The source of the _SimpleExample is as follows:
• The annotation processor for the GenAsPersistable annotation generates the parent class (more precisely , it generates a source file which the compiler then compiles).
• The parent class provides an implementation (query constant, field, getter, setter, and setter validation) for the “name” property as well as externalization logic and other needed methods.
The “_” class consists of the fields and methods shown in Listing 2.
Listing 2: _SimpleExample javap results
01 public abstract class com.acme.example._SimpleExample extends wt.fc.WTObject implements wt.inf.container.WTContained,java.io.Externalizable{
02 static final long serialVersionUID;
03 static final java.lang.String RESOURCE;
04 static final java.lang.String CLASSNAME;
05 public static final java.lang.String NAME;
06 static int NAME_UPPER_LIMIT;
07 java.lang.String name;
08 wt.inf.container.WTContainerRef containerReference;
09 public static final long EXTERNALIZATION_VERSION_UID;
10 public com.acme.example._SimpleExample();
11 public java.lang.String getName();
12 public void setName(java.lang.String) throws wt.util.WTPropertyVetoException;
13 void nameValidate(java.lang.String) throws wt.util.WTPropertyVetoException;
14 public java.lang.String getContainerName();
15 public wt.inf.container.WTContainer getContainer();
16 public wt.inf.container.WTContainerRef getContainerReference();
17 public void setContainer(wt.inf.container.WTContainer) throws wt.util.WTPropertyVetoException, wt.util.WTException;
18 public void setContainerReference(wt.inf.container.WTContainerRef) throws wt.util.WTPropertyVetoException;
19 void containerReferenceValidate(wt.inf.container.WTContainerRef) throws wt.util.WTPropertyVetoException;
20 public java.lang.String getConceptualClassname();
21 public wt.introspection.ClassInfo getClassInfo() throws wt.introspection.WTIntrospectionException;
22 public java.lang.String getType();
23 public void writeExternal(java.io.ObjectOutput) throws java.io.IOException;
24 protected void super_writeExternal_SimpleExample(java.io.ObjectOutput) throws java.io.IOException;
25 public void readExternal(java.io.ObjectInput) throws java.io.IOException, java.lang.ClassNotFoundException;
26 protected void super_readExternal_SimpleExample(java.io.ObjectInput) throws java.io.IOException, java.lang.ClassNotFoundException;
27 public void writeExternal(wt.pds.PersistentStoreIfc) throws java.sql.SQLException, wt.pom.DatastoreException;
28 public void readExternal(wt.pds.PersistentRetrieveIfc) throws java.sql.SQLException, wt.pom.DatastoreException;
29 boolean readVersion6009937787959182077L(java.io.ObjectInput, long, boolean) throws java.io.IOException, java.lang.ClassNotFoundException;
30 protected boolean readVersion(com.acme.example.SimpleExample,
java.io.ObjectInput, long, boolean, boolean) throws java.io.IOException,
java.lang.ClassNotFoundException;
31 protected boolean super_readVersion_
SimpleExample
(com.acme.example._SimpleExample, java.io.ObjectInput, long, boolean, boolean) throws java.io.IOException, java.lang.ClassNotFoundException;
32 boolean readOldVersion(java.io.ObjectInput, long, boolean, boolean) throws java.io.IOException, java.lang.ClassNotFoundException;
33 static {};
34 }
The annotation consists of a declaration that is then implemented by the compiler. Rather than implement all aspects of “name” manually, it is declared as a property within the annotation and the “_” parent file implements all the necessary components. Listing 3 contains an example of the effect required=true has on the nameValidate() method.
Listing 3: nameValidate() snippet
01 if (name == null || name.trim().length() == 0)
02 throw new wt.util.WTPropertyVetoException("wt.fc.fcResource", wt.fc.fcResource.REQUIRED_ATTRIBUTE,
03 new Object[] { new wt.introspection.PropertyDisplayName (CLASSNAME, "name") },
04 new java.beans.PropertyChangeEvent(this, "name", this.name, name));
The purpose of superClass and interfaces (line 11, listing 1) is as follows:
• The superClass annotation member is simple; extends was co-opted by the “_” class so modeled elements could be generated into code and incorporated into the class. Consequently, superClass is needed to allow you to specify your “true” (logical) parent (in this case, WTObject), which the “_” class (_SimpleExample) then extends for you (as seen above).
• The interfaces annotation member exists for a similar purpose: to make it possible to identify the interfaces you wish to implement. While implements was not similarly co-opted, various technical considerations made an annotation member the logical choice over utilizing implements directly. This example extends WTObject and implements WTContained. The compiler will enforce the “public class <X> extends _<X> { ... } ” syntax and generates a compile error if this syntax is not used.
Note the use of the factory pattern (newSimpleExample() (line 19 of Listing 1). Windchill utilizes this pattern over constructors because externalization needs to construct an instance using the noarg constructor and calling it should be inexpensive. The factory pattern consists of a static method of the form “public static <X> new<X>(<args...>) throws WTException { ... }” and should be used in place of constructors in annotated files. The body of the factory should follow the form demonstrated by the example. Specifically, it should:
1. Construct an instance of the class using its (default) no-arg constructor.
2. Call the (non-static) initialize method, passing the arguments provided to the static method. The static factory should never handle the arguments on its own because then no subclass could take advantage of the static method’s work (this is the primary purpose of the initialize methods: to make it possible to inherit behavior). You may need to create your own initialize method (if one does not exist or you need to augment behavior), in which case it should be of the form protected void initialize(args...) throws WTException and should call super.initialize(...).
3. Return the instance.
The following factory/initialize pair demonstrates the proper way one would add a factory method accepting name so as to assign the required property.
Listing 4: Factory/initialize pair example
01 public static SimpleExample newSimpleExample(final String name) throws WTException {
02 final SimpleExample instance = new SimpleExample();
03 instance.initialize(name);
04 return instance;
05 }
06
07 protected void initialize(final String name) throws WTException {
08 super.initialize();
09 try {
10 setName(name);
11 } catch (WTPropertyVetoException wtpve) {
12 throw new WTException(wtpve);
13 }
14 }
Additionally, the serialVersionUID (line 17 of Listing 1) is necessary since Windchill manages externalization logic (including, as needed, old version deserialization) and system-assigned serial version UIDs would break this logic. As before, the compiler will generate a compile error if this is not provided.
Finally, checkAttributes() is notable only in that it demonstrates the use of generated fields and methods (line 29, Listing 1). In particular, name and nameValidate() are generated with default access specifically to make them accessible to the annotated class.
Compilation
Compilation is covered extensively in the JavaDoc for the com.ptc.windchill.annotations.metadata package. However, the SimpleExample customization can be compiled and incorporated by executing the following steps:
1. cd to your load point (for example, /opt/ptc/Windchill)
2. Start a Windchill shell by invoking bin/windchill shell
3. Create the src/com/acme/example directory via mkdir -p src/com/acme/example
4. Create SimpleExample.java in this directory and give it the contents of the example above
5. Compile the example with ant -f bin/tools.xml class -Dclass.includes=com/acme/example/* (note that this command must be executed in the load point directory)
6. Generate SQL scripts with ant -f bin/tools.xml sql_script -Dgen.input=com.acme.example.*
7. Find the create_SimpleExample_Table.sql SQL script (it will be somewhere in db) and load it
8. Repeat for create_SimpleExample_Index.sql.
9. Start/restart the MethodServer
Jython can be used to quickly validate the example, as shown in Listing 5.
Listing 5: Persisting SimpleExample
01 from wt.fc import PersistenceHelper
02 from com.acme.example import SimpleExample
03
04 se = SimpleExample.newSimpleExample()
05 se.setName('test')
06 se = PersistenceHelper.manager.store(se)
When prompted, authenticate as the system administrator. The object will be created in the site container, which can be confirmed via sn.getContainer().getName(). Note that, had we created the factory/initialize methods with name as an argument, we could have avoided calling setName(’test’) separately.
Mapping classes to tables and columns
The example persisted an instance of SimpleExample, resulting in the addition of a row in a table. Details of the table are shown in Listing 6.
Listing 6: create_SimpleExample_Table.sql
01 exec WTPK.dropTable('SimpleExample')
02 set echo on
03 REM Creating table SimpleExample for com.acme.example.SimpleExample
04 set echo off
05 CREATE TABLE SimpleExample (
06 classnamekeycontainerReferen VARCHAR2(600),
07 idA3containerReference NUMBER,
08 name VARCHAR2(600) NOT NULL,
09 createStampA2 DATE,
10 markForDeleteA2 NUMBER NOT NULL,
11 modifyStampA2 DATE,
12 classnameA2A2 VARCHAR2(600),
13 idA2A2 NUMBER NOT NULL,
14 updateCountA2 NUMBER,
15 updateStampA2 DATE,
16 CONSTRAINT PK_SimpleExample PRIMARY KEY (idA2A2))
17 STORAGE ( INITIAL 20k NEXT 20k PCTINCREASE 0 )
18 ENABLE PRIMARY KEY USING INDEX
19 TABLESPACE INDX
20 STORAGE ( INITIAL 20k NEXT 20k PCTINCREASE 0 )
21 /
22 COMMENT ON TABLE SimpleExample IS 'Table SimpleExample created for com.acme.example.SimpleExample'
23 /
24 REM @//com/acme/example/SimpleExample_UserAdditions
The table’s name reflects the name of the class (this can be changed – see the JavaDoc for the annotation member “tableProperties” and the associated member “tableName” for details). Of the columns, “name” is certainly recognizable, though the rest may not be. The classnamekeycontainerReferen and idA3containerReference columns are necessary to store the reference to the container, which we got by being WTContained. Everything else comes from being Persistable, which is the top-level interface for persisting Windchill classes as tables.
Modeled associations (GenAsBinaryLink)
GenAsPersistable is one of two annotations for mapping classes to tables. The other is GenAsBinaryLink, which represents an association, linking two persistent objects (rows in two tables) together. Each binary link consists of two roles, an “A” role and a “B” role.
We can create a graph of SimpleExamples, in which a SimpleExample may have multiple parents and children.
Listing 7: SimpleExampleLink.java
01 package com.acme.example;
02
03 import wt.fc.ObjectToObjectLink;
04 import wt.util.WTException;
05
06 import com.ptc.windchill.annotations.metadata.*;
07
08 @GenAsBinaryLink(superClass=ObjectToObjectLink.class,
09 roleA=@GeneratedRole(name="parent", type=SimpleExample.class),
10 roleB=@GeneratedRole(name="child", type=SimpleExample.class))
11 public class SimpleExampleLink extends _SimpleExampleLink {
12 static final long serialVersionUID = 1;
13
14 public static SimpleExampleLink newSimpleExampleLink
(final SimpleExample parent, final SimpleExample child)
throws WTException {
15 final SimpleExampleLink instance = new SimpleExampleLink();
16 instance.initialize(parent, child);
17 return instance;
18 }
19 }
After similarly compiling it, it can be validated with Jython, as shown in Listing 8.
Listing 8: Persisting SimpleExampleLink
01 from wt.fc import PersistenceHelper
02 from com.acme.example import *
03
04 parent = SimpleExample.newSimpleExample()
05 parent.setName('parent')
06 parent = PersistenceHelper.manager.store(parent)
07
08 child = SimpleExample.newSimpleExample()
09 child.setName('child')
10 child = PersistenceHelper.manager.store(child)
11
12 l = PersistenceHelper.manager.store(SimpleExampleLink.
newSimpleExampleLink (parent, child))
As with SimpleExample, we have a factory method (lines 14-17). Links, however, always associate two objects, so the factory method (and associated initialize method) accepts the A/B roles as arguments.
Modeling Columns
Modeling Columns
GenAsPersistable and GenAsBinaryLink provide three mechanisms for specifying database columns:
1. properties (an array of GeneratedPropertys) represent strings, numbers, booleans, etc.
2. foreignKeys (an array of GeneratedForeignKeys) reference other persistent objects (and are stored as a classname/key pair)
3. roleA/roleB (GenAsBinaryLink only) are a special form of a foreign key used in associations
Additionally derivedProperties (an array of DerivedPropertys) provide convenience accessors to properties and foreign keys.
It is sometimes useful to collect a set of properties together into its own class which can then be managed as an entity and aggregated into a persistent object. The GenAsObjectMappable annotation exists for this purpose and is widely utilized by Windchill “cookies”. When a GenAsObjectMappable-annotated class is specified as the type for a GeneratedProperty, all its properties become columns in the owning class’s table. To prevent name-collisions, the names are “mangled” using a character/number pattern dictated by an arcane algorithm; it is this mangling that accounts for the “idA3” in idA3containerReference and the mapping of Persistable.thePersistInfo.theObjectIdentifier.id to idA2A2.
GeneratedProperty
An example of GeneratedProperty in our SimpleExample (name, which is required and of type String) has already been shown. GeneratedProperty supports a number of annotation members for controlling generation and behavior. See the Windchill JavaDoc for more information.
GeneratedForeignKey
A foreign key was not modeled, it was inherited from WTContained.
Listing 9: container definition snippet
01 @GeneratedForeignKey(name="ContainerLink",
02 foreignKeyRole=@ForeignKeyRole(name="container", type=wt.inf.container.WTContainer.class, referenceType=wt.inf. container.WTContainerRef.class, supportedAPI=SupportedAPI.PRIVATE,
03 constraints=@PropertyConstraints(required=true)),
04 myRole=@MyRole(name="contents", supportedAPI=SupportedAPI.PRIVATE))
The impact of the container reference had on SimpleExample’s schema is that it resulted in two columns (previously discussed). In fact, if we peel this back further, we can see that we got these fields from WTContainerRef – a GenAsObjectMappable – by way of ObjectReference. We can say that a foreign key is “simply” another property (and, in fact, you could model a property with a type of ObjectReference as the type and accomplish the “same” results (from the perspective of the columns that would be created)), however the Windchill persistence layer recognizes foreign keys as a type of association and can manage them for you. In particular:
• you can prevent the foreignKeyRole object from being deleted (when it participates in your assocation) by setting owner=false
• you can cause the myRole object to be deleted when the foreignKeyRole object is deleted by setting cascade=true
• when cascade=false (the default value) and foreignKeyRole role is deleted, the reference held by myRole will automatically be cleared
• you get accessors for both the reference and the referenced object generated for you
• you can automatically retrieve the referenced object (when required=true) by setting autoNavigate=true, preventing an additional database hit when accessing the referenced object
• you can navigate the foreign key link just as you would any other binary link.
The GeneratedForeignKey is used when a given persistable is associated to at most one other persistent object. It is preferable to utilize GeneratedForeignKey over a GeneratedProperty of some concrete subtype of WTReference because the former makes it possible for the Windchill persistence architecture to manage the reference as an association. However, there are exceptions to this. In particular, associations to WTPrincipals (users) should be modeled as GeneratedPropertys of type WTPrincipalReference rather than as GeneratedForeignKeys. This is because we don’t need the association management (users can’t be deleted) and because we only want accessors for the reference (which contains everything anyone should ever need to know about users).
|
Just as with GeneratedRoles, you can constrain an existing foreign key (modeled on a parent class/interface), as is commonly done to constrain an iteration’s master. When constraining a foreign key, you need only provide the values that are different from that of the foreign key you’re inheriting from; you needn’t faithfully re-specify all of the properties of your parent.
|
GeneratedRole
The GeneratedRole annotation is used to describe the role A and B of a GenAsBinaryLink, which we saw earlier in SimpleExampleLink. Roles can be thought of as special foreign keys associating the link with two persistables (and have the same inheritance capabilities as foreign keys). Whereas foreign keys are applicable only when the cardinality of the associated role is 0-1 and where the association requires no additional data (properties), binary links are applicable when the association is many-to-many, when the association carries properties, or when the association would otherwise be persisted on a PTC business object (since you cannot alter existing PTC classes).
DerivedProperties
Derived properties aren’t themselves persisted, but they do provide convenience accessors (getters, setters, and query constants) to persisted properties that would otherwise be buried in cookies or across foreign key references. Let’s say you have a field c which is buried inside a cookie b which is itself buried inside a cookie a which is a property of our current class.
We could access it using this.getA().getB().getC(), being careful to handle possible NullPointerExceptions should the cookies not be initialized. Better yet, we could just add this derived property to our class and let everything (including the handling of NullPointerException) be handled for us.
Listing 10: a.b.c derived property
@DerivedProperty(name="c", derivedFrom="a.b.c")
With this derived property, we can now simply call this.getC().
If you need to cross a foreign key to access a field in the associated object, use the “>” delimiter after the foreign key’s name in place of “.”. To directly access the name of the associated master, WTPart relies on the following derived property:
Listing 11: master>name derived property
@DerivedProperty (name="name " , derivedFrom="master>name " )
Specialized Persistence Constructs
GenAsUnPersistable “tables”
All classes recognized as being mapped to database tables must extend wt.fc.Persistable. The GenAsPersistable-generated “_” class automatically handles this for you; the GenAsBinaryLink-generated “_” class achieves this by implementing wt.fc.BinaryLink, which extends Persistable.
When modeling interfaces to be implemented by persistent classes, you will typically use these annotations to describe the columns (properties, foreign keys, and roles) you’ll ultimately want the implementing (concrete) classes to represent as columns. Windchill’s domain interfaces employ this strategy, allowing you to simply implement a bunch of interfaces to acquire important business logic like work-in-progress (Workable) lifecycle management (LifeCycleManaged), and so on.
You must annotate an interface if you want implementing classes to (automatically) persist the interface’s properties. However, not every interface is always intended to be implemented only by persistent classes. In some (exceeding rare) cases, an interface may be implemented by persistent classes (necessitating that the interface be annotated so its data can be persisted) and by non-persistent classes (for which GenAsPersistable and GenAsBinaryLink’s ensuring that the class ends up being Persistable is a problem). In these cases, you can use the GenAsUnPersistable annotation to bridge the gap: any persistent implementer will automatically treat the interface as though it were persistent, while non-persistent implementers won’t get persistence foisted on them.
It should be noted that you’ll likely want your non-persistent classes to be annotated with GenAsUnPersistable to get the same generation benefits as its persistent cousins do, but this is not necessary. Additionally, since externalization logic comes “for free”, you may wish to utilize this annotation in cases where the class is to be “persisted” as a BLOB , since RMI externalization is employed when blobbing instances. Here, “persisted as a BLOB” means that the entire class will be stored in a BLOB column inside a persisted class’s table. BLOBing an object is almost never recommended because it’s easy to overlook the complexities of reading the BLOB back out, especially if the class has changed since the instance was put in. Note that if you wish to store as a BLOB any class implementing wt.fc.NetFactor, you must make that class wt.util.Evolvable.
GenAsEnueratedType columns
Windchill provides support for modeling a discrete, localizable set of strings. Let’s say you want to store computer models in Windchill and wanted to categorize them as being either a desktop, laptop, or server. You could do this as follows:
Listing 12: ComputerType.java
01 package com.acme.example;
02
03 import com.ptc.windchill.annotations.metadata.*;
04
05 @GenAsEnumeratedType
06 public class ComputerType extends _ComputerType {
07 public static final ComputerType DESKTOP = toComputerType("desktop");
08 public static final ComputerType LAPTOP = toComputerType("laptop");
09 public static final ComputerType SERVER = toComputerType("server");
10 }
Listing 13: ComputerTypeRB.rbInfo
01 ResourceInfo.class=wt.tools.resource.EnumResourceInfo
02
03 desktop.value=Desktop
04 desktop.order=10
05
06 laptop.value=Laptop
07 laptop.order=20
08 laptop.defaultValue=true
09
10 server.value=Server
11 server.order=30
You can incorporate this into your Computer class using a GeneratedProperty as follows:
Listing 14: Computer type snippet
01 @GeneratedProperty(name="type", type=ComputerType.class, initialValue="ComputerType.getComputerTypeDefault()",
02 constraints=@PropertyConstraints(upperLimit=20, required=true))
The class follows the general format established previously: an annotated class that extends it’s (generated) “_” class. Classes annotated by GenAsEnumeratedType will ultimately extend wt.fc.EnumeratedType and, as can be seen, a number of methods are generated for you. Among them are to<X>(String) and get<X>Default(), where X is the name of our class (to see a complete listing, invoke javap com.acme.example._ComputerType).
Lines 7-9 (of Listing 12) consist of constant declarations which rely on the toComputerType(...) API to produce instances of the enumerated type. Note that these entries must be present in the corresponding rbInfo file, which is named <X>RB.rbInfo and resides in the same directory. This rbInfo file is of type ResourceInfo.class=wt.tools.resource.EnumResourceInfo and supports both localization and value ordering.
We incorporate this enumerated type as a GeneratedProperty. Note the use of initialValue and the constraints: all computer models must be assigned to one of our three types, and the default type is laptop. Enumerated types are stored (and serialized) as simple strings (in this case, the stored value will be one of desktop, laptop, or server). Since the default upperLimit for strings is fairly large at 200 characters (see JavaDoc for more information), a more reasonable limit is provided.
To build the rbInfo file, run ant -f bin/tools.xml bundle -Dbundle.input=com.acme.example.*. The extension must be “rbInfo”, and is case sensitive. If you neglect the uppercase “I”, the bundle target will ignore your file.
GenAsPrimitiveType columns
If you look at the JavaDoc for wt.fc.EnumeratedType. you will see that EnumeratedType is annotated with GenAsPrimitiveType. Also, the single argument to the annotation is String.class.
The GenAsPrimitiveType annotation is a simple annotation, requiring a single value: the “primitive” type the annotated class reduces to (for persistence and serialization). You will likely never use it, but it exists when you want to build a class (logic) around what is otherwise a pretty boring field. If you do use it, you not only need to specify the type to reduce this class to as part of the annotation, you also need to provide a constructor accepting the primitive type and a <type-in-lower-case>Value() method to return the current value.
For more information, refer to the annotation’s JavaDoc.
GenAsDatastoreSequence database sequences
Windchill, out-of-the-box, will automatically assign part number and document numbers. It does this by employing a database sequence.
Listing 15: MySequence.java
01 package com.acme.example;
02
03 import com.ptc.windchill.annotations.metadata.*;
04
05 @GenAsDatastoreSequence
06 public class MySequence extends _MySequence { }
As before, you need to generate and load the SQL (also, anytime you make a change to an annotated class, you will need to restart the MethodServer). Once completed, you can acquire values as demonstrated (the following example will print “1” and “2”, assuming you’ve not already acquired sequence values):
Listing 16: Acquiring sequence values
01 from com.acme.example import MySequence
02 from wt.fc import PersistenceHelper
03
04 print PersistenceHelper.manager.getNextSequence(MySequence )
05 print PersistenceHelper.manager.getNextSequence(MySequence )
|
The GenAsDatastoreSequence is one of four GenAsDatastore annotations.
|
Services
Windchill services provide APIs and logic to manage modeled business objects. They consist of:
• An (optional) helper consisting of static fields and methods, including a static field (generally named service or manager) referring to a service
• A service interface consisting of remotely-invocable method declarations
• A standard service which implements the service and is registered to run as a service in the Method Server.
Services are covered elsewhere in this document. However, this is the basic pattern.
Listing 17: ExampleHelper.java
01 package com.acme.example;
02
03 import wt.services.ServiceFactory;
04
05 /** Helpers are not instantiated and should consist of only static
fields/methods **/
06 public final class ExampleHelper {
07 /** Use the ServiceFactory to acquire an instance of the service. **/
08 public static final ExampleService service = ServiceFactory.
getService(ExampleService.class);
09 }
Listing 18: ExampleService.java
01 package com.acme.example;
02
03 import wt.method.RemoteInterface;
04 import wt.util.WTException;
05
06 /** RemoteInterface annotation is required for all service interfaces **/
07 @RemoteInterface
08 public interface ExampleService {
09 /** All interface methods are callable via RMI and must
throw WTException **/
10 SimpleExample createSimpleExampleByName(final String name)
throws WTException;
11 }
Listing 19: StandardExampleService.java
01 package com.acme.example;
02
03 import wt.fc.PersistenceHelper;
04 import wt.services.StandardManager;
05 import wt.util.WTException;
06 import wt.util.WTPropertyVetoException;
07
08 /** service must extend StandardManager, implement service interface **/
09 public class StandardExampleService extends StandardManager implements
ExampleService {
10 /** MethodServer refectively calls this API during startup **/
11 public static StandardExampleService newStandardExampleService()
throws WTException {
12 final StandardExampleService instance = new StandardExampleService();
13 instance.initialize();
14 return instance;
15 }
16
17 @Override
18 public SimpleExample createSimpleExampleByName(final String name)
throws WTException {
19 final SimpleExample example = SimpleExample.newSimpleExample();
20 try {
21 example.setName(name);
22 }
23 catch (WTPropertyVetoException wtpve) {
24 throw new WTException(wtpve);
25 }
26 return (SimpleExample) PersistenceHelper.manager.store(example);
27 }
28 }
The service must be registered in site.xconf; the number 99999 must be unique so as not to replace an existing service. Note that changes to site.xconf must be propagated using xconfmanager. All customization services must be registered in site.xconf.
Listing 20: site.xconf fragment
01 <Property name="wt.services.service.99999"
02 targetFile="codebase/wt.properties"
03 value="com.acme.training.MyService/com.acme.
training.StandardMyService"/>
With this service, we could reduce our Jython example to persist a SimpleExample to the following:
Listing 21: Persisting SimpleExample with service
01 from com. acme . example import
02
03 se = ExampleHelper.service.createSimpleExampleByName(’test’)
Localized Text
We have already seen an example localized text; ComputerTypeRB.rbInfo contains the (default English) text for ComputerType. Beyond providing localized text for enumerated types, we will want to localize class and fields names as well as messages (like exception messages).
Class and attribute names
Introspection provides APIs to get the (localized) display name for classes and properties. Default display names are calculated when explicit values aren’t provided, as shown by the following example which produces “Simple Example.Name”:
Listing 22: Display values for SimpleExample
01 from wt.introspection import WTIntrospector
02 from com.acme.example import SimpleExample
03
04 snci = WTIntrospector.getClassInfo(SimpleExample)
05 print "%s.%s" % (snci.getDisplayName(), snci.getPropertyDisplayName
(snci.getPropertyDescriptor('name'), None))
Let’s say we want to display SimpleName as “Simple” and SimpleName.name as “aka”. You can do this by creating a MetadataResourceInfo in the same directory, as follows:
Listing 23: exampleModelRB.rbInfo
01 ResourceInfo.class=wt.tools.resource.MetadataResourceInfo
02
03 # Entry Format (values equal to default value are not included)
04 # <key>.value=
05 # <key>.category=
06 # <key>.comment=
07 # <key>.argComment<n>=
08 # <key>.constant=
09 # <key>.customizable=
10 # <key>.deprecated=
11 # <key>.abbreviatedDisplay=
12 # <key>.fullDisplay=
13 # <key>.shortDescription=
14 # <key>.longDescription=
15
16 # Entry Contents
17 SimpleExample.value=Simple
18 SimpleExample.name.value=aka
These bundles are similar to the bundle style used for enumerated types and are compiled in the same manner. Once compiled, our Jython script will produce “Simple.aka”.
Messages
Localizing class names and properties is not sufficient. Everything you wish to communicate to users should be localized (note: a developer is not, necessarily, a user). The rbInfo format is used for enumerated types as well as classes and their properties. For general messages (typically included in exception or status messages reported by services), Java source/class files ultimately extending java.util.ListResourceBundle are utilized. As the contract for ListResourceBundle is arcane, we’ll utilize WTListResourceBundle, which is more declarative. An example:
Listing 24: exampleResource.java
01 package com.acme.example;
02
03 import wt.util.resource.*;
04
05 /** This example blatantly plagiarized from the JavaDoc. **/
06 @RBUUID("com.acme.example.exampleResource")
07 public class exampleResource extends WTListResourceBundle {
08 @RBEntry("This is the localized text with a single substitution: \"{0}\".")
09 @RBComment("An example entry.")
10 @RBArgComment0("Any string...")
11 public static final String EXAMPLE_STRING = "0";
12 }
With this, we produce a message or an exception:
Listing 25: Messages utilizing exampleResource
01 from com.acme.example import exampleResource
02 from wt.util import WTException, WTMessage
03
04 print WTMessage(exampleResource.getName(), exampleResource.EXAMPLE_STRING,
['substitution']).getLocalizedMessage()
05
06 raise WTException(exampleResource.getName(), exampleResource.EXAMPLE_STRING,
['substitution'])
The first call will produce This is the localized text with a single substitution: ‘‘substitution’’. and the second will raise (throw) an exception with the message wt.util.WTException: (com.acme.example.exampleResource/0) wt.util.WTException: This is the localized text with a single substitution: ‘‘substitution’.
For more information, refer to the JavaDoc for the wt.util.resource package, particularly wt.util.resource.WTListResourceBundle.
Documenting the Model
Every (supported) class is documented as part of JavaDoc, and every (supported) GenAs-annotated class includes the GenAs annotation in its JavaDoc. This means that Windchill’s schema is entirely documented in JavaDoc and is available for reference when modeling your own class. Furthermore, the annotations themselves are included in JavaDoc (and reside in the com.ptc.windchill.annotations.metadata package). Information about, and example usages of annotations are readily available in JavaDoc.
JavaDoc is particularly useful for understanding how to model and for mining Windchill’s model for examples. However, it can be difficult to determine the associations a particular class participates in from the JavaDoc alone, as the role members typically don’t include references to their associations in their own JavaDoc. Information about the associations a given object participates is available via Windchill introspection and can be accessed via two utilities.
The first utility is InfoReport. When run against a specified class in a Windchill shell, InfoReport will generate everything that is known about the class, including properties, database info, descendants, and, especially, associations.
The second utility is included as part of the Windchill user interface, but is not enabled by default. The client, when enabled, provides a navigable interface for browsing introspection information.
To enable it:
1. Go to Site -> Utilities -> Preference Manager
2. “Find in tree” Client Customization
3. Right click, select Set Preference, and assign the value to “yes”.
4. Refresh the browser
5. Browse to the tool icon (next to Site)
6. Select Tools, then Modeled Objects
Developing with Eclipse
The examples in this chapter (and customizations similar to it) can be developed using nothing more than a text editor for authoring and Ant for building. They can also be developed using Eclipse (
http://eclipse.org), which is an IDE (
tools.xml’s “
eclipse_project.help” target will help you get started).
In particular, you will need to to install Eclipse, install the FileSync plug-in, generate a workspace and project (via the eclipse_project target), and configure Eclipse to use both the workspace and project. All of this is covered in eclipse_project.help.
The generated Eclipse project is configured to work with source in <load point>/src, just as is tools.xml. It builds into an Eclipse-managed output directory (<load point>/eclipse_codebase) and utilizes FileSync to propagate changes to the “real” codebase (load point/codebase), which ensures a final result equivalent to invoking the class target. The generated project doesn’t compile directly to <load point>/codebase because Eclipse would otherwise obliterate it.
Eclipse supports compilation and annotation processing: whenever a source file is saved, it is immediately compiled. Compilation includes, as necessary, invocation of annotation processors and generation of code. Eclipse support does not extend to building rbInfo files and generating SQL, however, requiring the continued use of tools.xml to build those artifacts.
Eclipse users will want to utilize WTListResourceBundle and its annotations over the old rbInfo-formatted StringResourceInfo bundles. rbInfo files of type ResourceInfo.class=wt.tools.resource.StringResourceInfo are the only rbInfo format to compile to Java class files. Eclipse simply does not recognize class files produced in this way and won’t provide completion for it (it flags usages of the constants in these bundles as errors). The WTListResourceBundle is, by contrast, fully compiled by Eclipse and Eclipse is fully aware of it. If you have existing StringResourceInfo files, consider converting them using the bundle_convert target.
The generated Eclipse workspace includes a debug configuration for debugging the MethodServer (see the help for details). Even though Windchill does not ship source, but you will be able to debug your own code.
The eclipse_target, notably, is fairly bereft of options. This is an intentional restriction to ensure a working workspace/project is created. A cursory glance at tools.xml’s targets relating to eclipse_project and the output of eclipse_project itself will give you a good sense of what is going on and will give you insight as to how you might tailor the results to make it more suitable to your needs.
Deploying Modeled Customizations
This section documents guidelines for deploying modeled customizations. By “deploying” in this context we mean copying the customizations from one system to another – from a source to a destination. The process you use will depend on the actual source and destination of the customizations.
Modeled customizations, when built, will impact the installation in three primary ways:
1. 1. Registering modeled (annotated) classes in <loadpoint>/codebase in the following files:
◦ modelRegistry.properties
◦ associationRegistry.properties
◦ descendentRegistry.properties
2. Adding compiled and generated files:
◦ Java Classes (*.class)
◦ introspection artifact files (*.ClassInfo.ser)
◦ serialized bundle files (*.RB.ser)
3. Updating database scripts in <loadpoint>/db to incorporate any additional schema (including indexes)
◦ database files (*.sql)
Additionally, you may have added entries to site.xconf to register services.
In any case, you must:
1. Copy over the changes introduced by your customization into the destination system (these are the files enumerated above)
2. Install the schema by invoking the SQL scripts necessary to create tables and indexes
3. Augment the destination system’s site.xconf to incorporate the customization’s changes (and run xconfmanager)
4. Restart the destination system
Deploying Modeled Customizations from Development to Production
One common scenario is an environment where the source is a Development system and the destination is a Production system. In this scenario we can make assumptions that simplify the deployment process. Most importantly, we assume the development and production systems are essentially clones.
This section uses “Development System” to refer to the source where the customizations are being developed. “Production System” refers to the destination that is either currently being used by the customer for their production work, or the new production system that is being developed as part of a new deployment, migration or upgrade. Your environment may include other systems that need to be maintained such as a test system or staging (pre-production) system. All the steps described below for deploying customizations to production apply as well to the other systems – as long as the same assumptions apply.
In this scenario we assume that the development and production systems are clones, and that as you make changes to the development system, you can copy them to the production system. For that reason it is important that the production system and development system share the same fundamental state (the same products are installed, the same patches, and all are at the same release level) or your classes may be incompatible, your registry files may contain extraneous entries (or drop important ones), and your database schema may be incorrect.
If these assumptions are met, then you can follow the guidelines in the following steps:
1. Copy over the changes introduced by your customization into the production system (these are the files enumerated above)
2. Copy over the changes introduced by your customization into the production system.
a. Copy the follow registry files before starting customization to: C:\temp\original_registry_files :
▪ associationRegistry.properties
▪ descendentRegistry.properties
▪ modelRegistry.properties
▪ moduleRegistry.properties
c. To create merge files for the additions and deletions inside: d:\Windchill\temp\_model_registry run the following command in a Windchill shell.:
ant -f bin/tools.xml registry_diff -Dregistry_diff.basedir=C:\temp\original_registry_files -Dregistry_diff.modsdir=D:\windchill\codebase -Dregistry_diff.diffdir=d:\Windchill\temp\_model_registry
d. Copy the _model_registry folder to the new Windchill target system and paste it in d:\Windchill\temp
e. Run the following command in a Windchill shell on the new target system:
ant -f bin/tools.xml registry_merge -Dregistry_merge.adds=D:\Windchill\temp\_model_registry\reg_adds -Dregistry_merge.removes=D:\Windchill\temp\_model_registry\reg_removes
3. Install the schema by invoking the necessary SQL scripts to create tables and indexes
4. Augment the production system’s site.xconf to incorporate the customization’s changes (and run xconfmanager)
5. Restart the production system.
Additional Strategies for Deploying Modeled Customizations
Deploying your customization from your source to your destination entails replaying your actions in the source environment on the destination system. One strategy would be to simply copy over the source and rebuild it on the destination system. This strategy, however, necessitates compiling on the destination system, but if that is your production system then that is probably not acceptable.
Another strategy is to use a version control system, like Git or Subversion, putting your entire load point under it, allowing the version control system to tell you how your customization impacted the installation, and merge over the changes.
Merging Modeled Customizations
If the destination system includes customizations that are not part of the source system you are deploying, you will need to create incremental registry files that can be merged into the registry files in the destination system. The registry_diff target can be run against the source system files to produce the incremental files to merge into the destination system with the registry_merge target.
The registry_diff target can be run from the Windchill shell of either system, as long it has access to the required folders, described below.
ant -f <loadpoint>/bin/tools.xml registry_diff -Dregistry_diff.basedir=/wt_dev/base
-Dregistry_diff.modsdir=/wt_dev/codebase -Dregistry_diff.diffdir=/wt_dev/diff
• registry_diff.basedir - the folder that contains the original registry file, prior to developing the customizations
Ideally you would have preserved the original state of your registry files before doing any customization. If not, then to access your original registry files you may need to unregister your customized classes, preserve the registry files as your base, and then rebuild your customizations. For example, to unregister your customized classes that begin with the prefix “ext.”:
Remove lines beginning with “ext.” or containing “=ext.”:
ant -f <loadpoint>/bin/tools.xml model_uninstall
-Dmodel_uninstall.filter="\Aext\.|=ext\."
Note: This will match any key or value that begins with “ext.”.
For additional usage examples execute: ant -f <loadpoint>/bin/tools.xml model_uninstall.help
• registry_diff.modsdir is the folder that contains the development registry files (ie the development codebase), that is the version of the registry files with your customizations registered.
• registry_diff.diffdir is the folder where the delta between the basedir and modsdir will be output. Within the directory two folders could be created: reg_adds and reg_removes. These two folders will be used by the registry_merge command on the destination system as described below.
The registry_merge target must be run from the Windchill shell of the destination system, and it must have access to the files generated by the registry_diff target.
ant -f <loadpoint>/bin/tools.xml registry_merge
-Dregistry_merge.adds=/wt_dev/diff/reg_adds
-Dregistry_merge.removes=/wt_dev/diff/reg_removes
• registry_merge.adds is the folder that contains the customization entries to add to the destination system
• registry_merge.removes is the (optional) folder that contains the customization entries to remove from the destination system