Package com.ptc.windchill.annotations.metadata


package com.ptc.windchill.annotations.metadata
Contains the annotations necessary to define persistent schema.

Overview

Windchill, much like other Java persistence frameworks (namely the Java Persistence API), uses annotations to specify persistence metadata. Windchill's annotations deviate from "standard" persistence annotations in two key regards:
  1. Annotations describe classes only -- fields and methods are not annotated
  2. Source (fields, accessors, accessor validation, query constants, and externalization (persistence and remoting) APIs) is generated from the annotations and compiled.
Consider the following simple example (taken from View with JavaDoc comments, import statements, and readOldVersion removed for brevity):
 package wt.vc.views;
 
 @GenAsPersistable(superClass=WTObject.class, 
    properties={
    @GeneratedProperty(name="name", type=String.class, supportedAPI=SupportedAPI.PUBLIC,
       javaDoc="The name of the View.  Must be unique.",
       constraints=@PropertyConstraints(upperLimit=30, required=true),
       columnProperties=@ColumnProperties(unique=true)),
    @GeneratedProperty(name="sortId", type=int.class,
       accessors=@PropertyAccessors(setAccess=SetAccess.PACKAGE))
    },
    tableProperties=@TableProperties(tableName="WTView")
 )
 public final class View extends _View {
    static final long serialVersionUID = 1;
     
    public static View newView(String viewName) throws WTException {
       View instance = new View();
       instance.initialize(viewName);
       return instance;
    }
 
    protected void initialize(String viewName) throws WTException {
       try {
          setName(viewName);
       }
       catch (WTPropertyVetoException wtpve) {
          throw new ViewException(RESOURCE, viewsResource.INVALID_VIEW_NAME, new Object[]{viewName});
       }
    }
 
    @Override
    public String getIdentity() {
       return getName();
    }
 }
 
View defines two properties, name (a String) and sortId (an int). name is required, must be 30 characters or less in length, and will be unique in the database. sortId is defined to have a package (default) accessible setter.

How does this work? The first key component is the following:

public final class View extends _View {
The parent class, _View, is a completely compiler-generated (and compiled) file. Java 6's Pluggable Annotation Processing API provides the framework to read View's annotations and generate its parent class, and it is this parent class which generates name's field, constant, and accessors, as shown by _View (note, JavaDoc removed for brevity):
    public static final String NAME = "name";
    static int NAME_UPPER_LIMIT = -1;
    java.lang.String name;
    public java.lang.String getName() {
       return name;
    }
    public void setName(java.lang.String name) throws wt.util.WTPropertyVetoException {
       nameValidate(name);
       this.name = name;
    }
    void nameValidate(java.lang.String name) throws wt.util.WTPropertyVetoException {
       if (name == null || name.trim().length() == 0)
          throw new wt.util.WTPropertyVetoException("wt.fc.fcResource", wt.fc.fcResource.REQUIRED_ATTRIBUTE,
                new Object[] { new wt.introspection.PropertyDisplayName(CLASSNAME, "name") },
                new java.beans.PropertyChangeEvent(this, "name", this.name, name));
       if (NAME_UPPER_LIMIT < 1) {
          try { NAME_UPPER_LIMIT = (Integer) wt.introspection.WTIntrospector.getClassInfo(CLASSNAME).getPropertyDescriptor("name").getValue(wt.introspection.WTIntrospector.UPPER_LIMIT); }
          catch (wt.introspection.WTIntrospectionException e) { NAME_UPPER_LIMIT = 30; }
       }
       if (name != null && !wt.fc.PersistenceHelper.checkStoredLength(name.toString(), NAME_UPPER_LIMIT, true))
          throw new wt.util.WTPropertyVetoException("wt.introspection.introspectionResource", wt.introspection.introspectionResource.UPPER_LIMIT,
                new Object[] { new wt.introspection.PropertyDisplayName(CLASSNAME, "name"), String.valueOf(Math.min(NAME_UPPER_LIMIT, wt.fc.PersistenceHelper.DB_MAX_SQL_STRING_SIZE/wt.fc.PersistenceHelper.DB_MAX_BYTES_PER_CHAR)) },
                new java.beans.PropertyChangeEvent(this, "name", this.name, name));
    }
 
Serialization and other methods (not shown) are also generated into _View. The nameValidate method, as can be seen, also implements the logic necessary to ensure the name is non-empty and under 30 characters in length.

Annotations

Windchill's persistence annotations can be split into three categories:
  1. Top-level persistence annotations annotate Windchill business classes to identify tables (entities in JPA nomenclature) and cookies (embeddable classes stored as columns)
  2. Top-level datastore annotations annotate internal Windchill classes to describe low-level internal database objects and types, such as sequences
  3. Attribute annotations represent fields (columns) of a business class and are modeled as members of a top-level annotation.

Top-Level Persistence Annotations

These annotations represent the entry point for describing persistence. Each annotation annotates a Java class or interface, describing class-level metadata and enumerating attribute annotations.

Top-Level Persistence Annotation Backing Interface Description
GenAsObjectMappable ObjectMappable Windchill "cookie" stored as column(s) in an owning table.
GenAsPersistable Persistable Windchill business object stored as a table.
GenAsBinaryLink BinaryLink Windchill link associating two persistent Windchill business objects.
GenAsPrimitiveType N/A Class that can be serialized as a simple field (String, or primitive type such as int).
GenAsEnumeratedType EnumeratedType Specialization of GenAsPrimitiveType which serializes a fixed set of values (an enumeration) as a string.
GenAsUnPersistable N/A Class that is itself not persistent but whose properties become persisted when implemented by a persistent class.

Note that all top-level persistence-annotated classes and interfaces must abide by the following form:

 @GenAs...
 public class <classname> extends _<classname> { ... }
 
Importantly, the class must extend its "_" class (as there'd be no other way to incorporate the generated code) and must not implement interfaces (interfaces and the superclass are specified in the annotation rather than as part of the class's declaration). Note that the compiler will enforce the above form, reporting compile errors when not followed.

Top-Level Datastore Annotations

These specialty annotations describe Windchill classes intended to bridge internal database objects. Most of these annotations are used internally to provide low-level database functionality and, save GenAsDatastoreSequence, are not likely to be of any interest generally or in a customization.

Top-Level Datastore Annotation Backing Interface Description
GenAsDatastoreSequence DatastoreSequence Database sequence providing unique, monotonically increasing numbers.
GenAsDatastoreStruct DatastoreStruct Database-recognized C-style "struct".
GenAsDatastoreArray DatastoreArray Database-recognized array of database types, including column types and structs.
GenAsDatastoreTable DatastoreTable Database table (NOT supported for customization).

Classes annotated with any of these datastore annotations must follow the same form as those of the top- level persistence annotations.

Attribute Annotations

These annotations are included in the top-level annotations as members and persisted as columns in their respective classes' tables. Windchill generates the appropriate constants, fields, accessor methods, setter validation, and externalization logic based on the information specified for these annotations.

Attribute Annotation Top-Level Member Name Description
GeneratedProperty properties Simple property stored as a column or -- if the type is a GenAsObjectMappable -- as columns.
GeneratedRole roleA/roleB Describes the "a" and "b" roles of a GenAsBinaryLink.
GeneratedForeignKey foreignKeys A reference to another persistent object (row in another table) which is stored locally in the annotated class's table.
DerivedProperty derivedProperties Generates convenience accessors to the above attributes.

Note that not all of the top-level annotations support all of the attribute annotations. However, if supported, they will always use the same member name within the top-level annotation.

Compilation and Generation

Annotation processing in the compiler

Windchill annotation processors generate an "_" source file for each of the files annotated with these annotations. Annotation processing is done within the compiler itself and the compiler will automatically compile all source files generated by these annotation processors. Compilation will, typically, result in three distinct annotation processor invocations (or rounds):
  • Round one generates the "_" classes for all top-level annotated files and also generates foreign key link classes (annotated with GenAsBinaryLink) for each GeneratedForeignKey
  • Round two generates the "_" classes for (any) foreign key link classes produced by round one
  • Round three (or round two, if no foreign key links were generated) simply reports that Windchill annotation processing is complete.
Each source file generated by Windchill's annotation processors will be listed (as a note), along with a one-line "complete" message at the end of each round. javac output will be similar to the following result (which compiles only wt/part/WTPart.java, a class with a single foreign key):
 wt/part/WTPart.java:211: cannot find symbol
 symbol: class _WTPart
 public class WTPart extends _WTPart {
                             ^
 Note: Creating /path/to/src_gen/wt/part/_WTPart.java
 Note: Creating /path/to/src_gen/wt/part/MasterIteration.java
 Annotation processing round 1 complete -- 2 file(s) generated.
 /path/to/src_gen/wt/part/MasterIteration.java:27: cannot find symbol
 symbol: class _MasterIteration
 public class MasterIteration extends _MasterIteration implements Externalizable {
                                      ^
 Note: Creating /path/to/src_gen/wt/part/_MasterIteration.java
 Annotation processing round 2 complete -- 1 file(s) generated.
 Annotation processing generation COMPLETE.  ALL ERRORS BEYOND THIS POINT ARE REAL!!!
 
javac reports two errors -- two cannot find symbol errors for _WTPart and _MasterIteration. Both of these errors are subsequently resolved by the generation of the respective source classes. That the compiler reports these (temporarily) missing classes as errors has been reported (as bugs) to Oracle and is one of the primary motivators of the "Annotation processing ..." messages. In particular, errors that are not resolved by generated source during annotation processing will follow the last message:
Annotation processing generation COMPLETE.  ALL ERRORS BEYOND THIS POINT ARE REAL!!!
This can be demonstrated by adding a real error to WTPart:
 wt/part/WTPart.java:211: cannot find symbol
 symbol: class _WTPart
 public class WTPart extends _WTPart {
                             ^
 wt/part/WTPart.java:212: cannot find symbol
 symbol  : class THIS_LINE
 location: class wt.part.WTPart
    public static THIS_LINE WILL_NOT_COMPILE;
                  ^
 Note: Creating /path/to/src_gen/wt/part/_WTPart.java
 Note: Creating /path/to/src_gen/wt/part/MasterIteration.java
 Annotation processing round 1 complete -- 2 file(s) generated.
 wt/part/WTPart.java:212: cannot find symbol
 symbol  : class THIS_LINE
 location: class wt.part.WTPart
    public static THIS_LINE WILL_NOT_COMPILE;
                  ^
 /path/to/src_gen/wt/part/MasterIteration.java:27: cannot find symbol
 symbol: class _MasterIteration
 public class MasterIteration extends _MasterIteration implements Externalizable {
                                      ^
 Note: Creating /path/to/src_gen/wt/part/_MasterIteration.java
 Annotation processing round 2 complete -- 1 file(s) generated.
 wt/part/WTPart.java:212: cannot find symbol
 symbol  : class THIS_LINE
 location: class wt.part.WTPart
    public static THIS_LINE WILL_NOT_COMPILE;
                  ^
 Annotation processing generation COMPLETE.  ALL ERRORS BEYOND THIS POINT ARE REAL!!!
 wt/part/WTPart.java:212: cannot find symbol
 symbol  : class THIS_LINE
 location: class wt.part.WTPart
    public static THIS_LINE WILL_NOT_COMPILE;
                  ^
 1 error
 
The error, as can be seen, is reported in every round. Importantly, it is the only "error" to be reported after the last annotation processing message. A couple of notes related to compilation:
  • It is possible, due to another reported issue, for the compiler to "quit" (with errors) prior to printing the last message. This may occur when using javac's sourcepath. To disable sourcepath in Ant, add sourcepath="" to the javac task used to compile.
  • It is also possible for the compiler to "quit" (without errors). This is because, within a round, the "cannot find symbol" messages for the not-yet-generated classes are treated as errors by the compiler (until they're resolved in the next round). As such, any true error may go unreported because "maxerrs" has been exceeded simply as a consequence of reporting these "missing" symbols. To see the actual errors you're interested in, bump maxerrs to a value sufficient to see the real errors. A value of 1000 should be sufficient for most cases, and can be passed to the compiler as -Xmaxerrs 1000 (if building using tools.xml, pass -Dclass.extra="-Xmaxerrs 1000").
  • If the "Note: Creating ..." messages are not appearing when compiling, it is because -nowarn has been passed to the compiler.
  • If the "round" messages aren't being printed, javac isn't doing annotation processing (annotation processing isn't enabled in the compiler).

Enabling annotation processing

All of the above assumes annotation processing has been properly enabled. To enable annotation processing, the following arguments (and their values) must be passed to javac:
  • -cp/-classpath must include, relative to the install location (/opt/ptc/Windchill, for example) srclib/tool/AnnotationProcessing.jar
  • -processorpath must include the following Jar files (again, relative to the install location):
    • srclib/tool/AnnotationProcessing.jar
    • srclib/tool/Annotations.jar
    • srclib/wnc/CommonCore.jar
    • srclib/jmxcore/WtLogR.jar
    • srclib/install/InstallUtil.jar
    • srclib/commons-collections.jar
    • srclib/log4j.jar
  • -s must be assigned to a path, typically a peer to src and named src_gen, to generate source to
  • -Awt.home=</path/to/install/location> (for example /opt/ptc/Windchill)
  • Optionally
    • -Agen_class_info=<true or false> (defaults to true)
    • -Adebug_ap_class_info=<true or false> (defaults to false)
    • -Adebug_ap_java=<true or false> (defaults to false)
Note that tools.xml's "class" target has annotation processing enabled. Also, note that srclib/tool/Annotations.jar must be in the compiler's classpath whenever compiling classes that refer to (will load) annotated class files, as the compiler requires access to these annotations when loading these classes.

Compilation/generation notes

The compiler, as seen, generates source based on the provided annotations and compiles the generated source. All of the annotations are assigned a retention policy of CLASS, making the annotations available when compiling classes based on annotated classes and potentially making them available at runtime (for reflection). The MethodServer, however, does not have Annotations.jar in its classpath, causing the class loader to discard them when the classes are loaded into the VM.

The wt.introspection package provides the APIs necessary for introspecting annotations (see, in particular, WTIntrospector) and is based on information stored in ClassInfo and LinkInfo files, which are generated as part of annotation processing (unless -Dgen_class_info=false is passed to the compiler).

While compilation automatically creates introspection files, javac does not generate SQL scripts for generating tables, indexes, and so on. tools.xml has a target, "sql_script" for generating these files.

Developing Using Annotations

Core to understanding these annotations is recognizing that the implementation of a (logical) class is split between two physical source files. The first of these files is the annotated, user-edited "business" file (View, for example) and the second is the compiler-generated "implementation" file (_View). The fact that there are two files building a single logical class is a consequence of a simple and reasonable restriction in javac's handling of generated source: the generated (implementation) files are not editable.

As demonstrated, annotation processing is being utilized to generate the fields, accessors, externalization logic, and so on for persistent classes. If the generated implementation is insufficient for any reason, simply override the method. There is one caveat to this: if the method being overwritten is an externalization method, a call to super.readExternal(...) or super.writeExternal(...) will result in an infinite loop. Replace these calls, instead, with calls to super_readExternal_<class> or super_writeExternal_<class> (these methods are generated into the "_" classes to serve as pass-throughs to the "true" super class).

Because implementation files are "read-only", it makes no sense to model, via annotations, methods that can't be implemented by the generator -- they would simply be empty methods for which the body would need to be provided by an overriding method. To implement methods, such as factory methods (for example, newView and its related initialize method), simply define and implement the methods in the business files themselves.

As demonstrated above (MasterIteration), the compiler generates a link source file for every GeneratedForeignKey. The name of the source/resultant class will be a concatenation of the role A & B names unless the foreign key is given an explicit name. Meanwhile, every annotated class gets "registered" during compilation, with entries in modelRegistry.properties and, possibly, associationRegistry.properties and descendentRegistry.properties. Keep in mind that if you rename a foreign key's role, the "new" class will be registered. The compiler has no way of knowing which previously-registered classes have been deleted (or renamed), and so the old class will remain registered, causing problems the next time the MethodServer starts. Fix this by removing the old class from these properties files.

  • Class
    Description
    Specifies the legitimate number of occurrences for the associated role.
    Specifies the change restrictions associated with a GeneratedProperty.
    Describes the database properties for the column corresponding to the GeneratedProperty this annotation describes.
    Specifies the relational storage type for the property.
    Represents a derived field/getter/setter for a class.
    A role descriptor for the aggregated (one/zero-to-one) side of a foreign key association.
    Indicates that the annotated class is to be treated as a link associating two Persistable objects.
    Indicates that the class is to be implemented as a DatastoreArray.
    Indicates that the class is to be implemented as a DatastoreSequence.
    Indicates that the class is to be implemented as a DatastoreStruct.
    Indicates that the annotated class is to be generated as an EnumeratedType.
    Indicates that the annotated class's GenAsObjectMappable.properties() are to be persisted as columns in any persistent class which includes it as a GeneratedProperty.
    Indicates that the annotated class is to be persisted as a database table with its properties persisted as columns in the table.
    Indicates that the annotated class can be reduced to a simple, primitive field for serialization.
    Provides basic generation support for properties.
    Represents an association for which the cardinality of one of the roles is either zero-to-one or one.
    Represents a typical field/getter/setter for a class.
    A role descriptor for the associated link.
    Specifies the access modifier to apply to the getter method for a property.
    Indicates the paths to display icons.
    A role descriptor for the non-aggregated -- non one/zero-to-one -- side of a foreign key association.
    Specifies the Oracle extent sizing for the table.
    Determines the accessibility and exceptions for the generated getter and setter methods for the associated GeneratedProperty.
    Specifies string case, lower and upper bounds (for strings and numbers), whether or not the associated property is required, and whether or not the associated property can be changed after the owning object has been persisted.
    Indicates the supported remote method invocation (RMI) serialization strategy.
    Specifies the access modifier to apply to the setter method for a property.
    Specifies the case to force the corresponding string to.
    Specifies Windchill's support for this property or role.
    The common table properties for a given class supporting table names, indexes, table spaces, and table sizes.