Creating the Class
1. Begin by launching Eclipse (see previous section). The cust_Windchill_src project should be open in Eclipse, which should look similar to the following:
2. Create the Pet class.
a. Navigate to > > .
b. Set Package to com.acme
c. Set Name to Pet
d. Click Finish.
The result will be an empty class, as follows:
3. Make the Pet persistent.
a. Annotate the class declaration with @GenAsPersistable (note, Control + Space will complete and, as Eclipse supports camel-completion, it is possible to complete this by typing “@GAP” followed by Control + Space; using Eclipse completion has the advantage that, generally, an inability to complete is an indicator that some larger syntax error exists that needs to be corrected)
b. Save Pet (Control-s). Pet will immediately show two compile errors, as follows:
This is expected.
c. Resolve them by:
i. Making Pet extend _Pet.
ii. Adding the required declaration to Pet’s body
d. Save Pet again. Pet now extends _Pet, which implements wt.fc.Persistable, resulting in two new errors.
Rather than implement these directly, make WTObject the super class of _Pet by adding superClass=WTObject.class to the annotation above (both superClass and WTObject will complete, and Eclipse will automatically import WTObject as part of the completion).
e. Save Pet again. The result, which will compile cleanly, is as follows:
f. Create the no-argument factory method for Pet, which consists of the following:
public static Pet newPet() throws WTException {
final Pet instance = new Pet();
instance.initialize();
return instance;
}
Windchill persisted objects require the use of factory methods similar to the “newPet()” factory method over constructors. They are paired with initialize methods accepting the same arguments so child classes can inherit logic present in the parent class’s methods. In this case, an explicit initialize method is unnecessary as Pet does not contribute functionality; here, the implementation inherited from WTObject is sufficient.
4. Add ContentHolder and Ownable to Pet. Windchill utilizes Java interfaces to provide domain functionality to persistent business classes. ContentHolder gives the ability to store files (content) and Ownable the ability to take ownership of a business object.
a. Add “, interfaces={ContentHolder.class, Ownable.class}” after the superClass annotation member. Make sure to use Eclipse “Control + Space” completion auto-import the interfaces
b. Save Pet
5. Add name, dateOfBirth, and fixed as properties to Pet.
a. Declare the properties annotation member by adding a comma after the closing “}” of interfaces, then adding a newline, and then inserting the following (the annotation’s closing parethensis is shown): properties={ })
b. All properties will be included between the “{}”s for properties, each on their own line. First, add name as follows:
@GeneratedProperty(name="name", type=String.class,
columnProperties=@ColumnProperties(index=true),
constraints=@PropertyConstraints(required=true, upperLimit=60))
This property declares name, of type String. The name is required, must be 60 characters or less in length, and will be indexed in the database. Most of this can be code-completed by the IDE, reducing significantly the amount of effort needed to enter it
c. Next, add dateOfBirth immediately following name. It will be necessary to add a comma to the previous line to separate the two declarations:
@GeneratedProperty(name="dateOfBirth", type=Timestamp.class)
Here, dateOfBirth is a java.sql.Timestamp.
d. Finally, add fixed (a boolean): @GeneratedProperty(name="fixed", type=boolean.class)
6. Create PetKind, an enumerated type consisting of common pet kinds.
a. Navigate to > > .
b. Set Package to com.acme
c. Set Name to PetKind
d. Click Finish.
Add the GenAsEnumeratedType annotation to the class declaration and make the class extend _PetKind (ignore the warning). The result will look as follows:
7. Create PetKindRB.rbInfo, a resource bundle storing kinds of pets.
a. Select PetKind.java and press Ctrl + N.
b. Select > and click Next.
c. Give PetKindRB.rbInfo as the File name.
d. Click Finish.
e. Make the contents of PetKindRB.rbInfo as follows (and save it):
ResourceInfo.class=wt.tools.resource.EnumResourceInfo
dog.value=Dog
dog.order=10
cat.value=Cat
cat.order=20
gerbil.value=Gerbil
gerbil.order=30
8. Add kind to Pet.
a. Place kind between name and dateOfBirth: @GeneratedProperty(name="kind", type=PetKind.class, constraints=@PropertyConstraints(upperLimit=40))
The stored value of an enumerated type is the key value (dog, cat, gerbil) as opposed to the display value (Dog, Cat, Gerbil). An upper limit of 40 was specified as it’s a more than sufficient value to store the keys.
9. Verify the result. Note that, as a minor clean-up, all the separate annotations imports have been reduced to a single “*” import:
10. Compile PetKindRB.rbInfo.
a. Find or start a Windchill shell (command assumes shell is in Windchill directory).
b. Run the following command: ant -f bin/tools.xml bundle -Dbundle.input=com.acme.*
11. Ensure Pet, PetKind compile with Ant.
a. Reuse Windchill shell from previous command
b. ant -f bin/tools.xml class -Dclass.includes=com/acme/** -Dclass.force=true
12. Generate SQL scripts.
a. Reuse, again, Windchill shell
b. ant -f bin/tools.xml sql_script -Dgen.input=com.acme.*
13. Load schema for Pet.
a. Locate, under load point/db, Make_pkg_acme_Table.sql.
i. Oracle (single byte): db/sql/com/acme/Make_pkg_acme_Table.sql
ii. Oracle (multi-byte): db/sql3/com/acme/Make_pkg_acme_Table.sql
iii. SQLServer: db/sqlServer/com/acme/Make_pkg_acme_Table.sql
b. cd to the db/<xxx> directory from above (db/sql, db/sql3, or db/sqlServer)
c. Run the script
i. Oracle: sqlplus <database credentials> @com/acme/Make_pkg_acme_Table.sql
ii. SQLServer: …
d. Repeat for @com/acme/Make_pkg_acme_Index.sql
Verifying the customization
The customization is incomplete: no user interface (UI) exists to manage Pet objects. Nevertheless, the customization is complete enough to verify that CRUD operations against Pet objects are possible, as the following Jython code demonstrates. To invoke the example:
2. Start (or restart) the MethodServer
3. Start a Windchill shell
4. Run jython, which -- without arguments -- will start the Jython interpreter.
Jython is a Java implementation of the Python programming language and, while Jython uses Python’s syntax, it will be readily familiar to a Java programmer. Jython brings two important advantages of a Java class with a main method:
• Jython is dynamic: methods can be typed in the interpreter, are immediately invoked by the interpreter, and the result of the invocation is immediately available. This, sometimes refered to as a “REPL”, offers a level of interaction with code unparalleled by a static class file, even when debugged in a debugger. Furthermore, Jython’s dynamic nature precludes typing variables, casting, writing try/catch blocks (unless this is desired), and so on, resulting in significantly shorter code than Java’s equivalent.
• Jython interacts seamlessly with Java: Jython brings Python to Java without sacrificing Java. Jython is “pure Java” and can instantiate any Java class and call any Java method or access any Java field (within the restrictions of the Java security model). Beyond simply being able to interact with Java, Jython is notable in that the “impedance mismatch” between it (as a Python implementatiotn) and Java is very small, making interaction with Java simple, predictable, and easy and making conversion to straight-Java trivial.
Jython is not the only language on top of the Java virtual machine. Notably, there’s also Groovy, JRuby, BeanShell, and even JavaScript. All are worth exploring and all have different approaches and philosophies worth discovering, however, Jython excels in simplicity, interactivity, and in the quality of its interpreter.
The following code snippets should be executed in the Jython interpreter. While segmented, they should be invoked in the same interpreted session. The segments can also be combined into a single script that is then executed by Jython; if done, note that Jython won’t automatically print like it does in the interpreter.
1. Import the Java classes necessary to do CRUD against Pet objects:
from java.sql import Timestamp
from java.text import SimpleDateFormat
from com.acme import Pet, PetKind
from wt.fc import PersistenceHelper
from wt.ownership import OwnershipHelper
from wt.query import QuerySpec, SearchCondition
2. Create two pets:
dog = Pet.newPet()
dog.setName('Fergus')
dog.setKind(PetKind.toPetKind('dog'))
dog.setDateOfBirth(Timestamp(SimpleDateFormat('yyyy-MM-dd').
parse('1999-02-11').getTime()))
dog.setFixed(False)
dog = PersistenceHelper.manager.store(dog)
cat = Pet.newPet()
cat.setName('Stimpy')
cat.setKind(PetKind.toPetKind('cat'))
cat.setDateOfBirth(Timestamp(SimpleDateFormat('yyyy-MM-dd').
parse('1996-08-24').getTime()))
cat.setFixed(False)
cat = PersistenceHelper.manager.store(cat)
3. Update the cat (note that “#” starts a comment and is equivalent to Java’s “//”; the comment string need not be entered and is intended to highlight that the call will print the current principal):
cat.setFixed(True)
cat = PersistenceHelper.manager.modify(cat)
cat = OwnershipHelper.service.takeOwnership(cat)
OwnershipHelper.getOwner(cat) #Should print/return the current principal
4. Query (read) for the Pets named Fergus:
qs = QuerySpec(Pet)
qs.appendWhere(SearchCondition(Pet, Pet.NAME, SearchCondition.EQUAL,
'Fergus'))
qr = PersistenceHelper.manager.find(qs)
qr.size() #Should print/return 1
fergus = qr.nextElement()
fergus.getName() #Should print/return “Fergus”
5. Finally, delete the dog:
PersistenceHelper.manager.delete(dog)
Pet customization notes
As previously stated, this example serves more as a “Hello, World!” introduction to Windchill persistence and modeling than an example of a actual Windchill business object. As such, properties of various types (strings, enumerations, timestamps, and primitives) were modeled to demonstrate possibilities, but Pet was clearly insufficient as a class for storing information about pets.
Similarly, while Pet implemented Windchill interfaces, it was a particularly lightweight object from a Windchill perspective. Firstly, it is not AccessControlled (from wt.access), so anyone could create, read, update, or delete pets without any permission beyond the minimal requirement of authentication. Secondly, most Windchill business classes are WTContained (from wt.inf.container) because containership is a primary organization principle of Windchill.
A final, badly-needed correction to the customization is a helper/service along the lines of those already utilized in the verification (PersistenceManager from PersistenceHelper.manager and OwnershipService from OwnershipHelper.service).
The service makes it possible to provide APIs that encapsulate logical operations, such as adoption. These APIs can utilize transactions, addressing the major flaw in the combination of discrete APIs to accomplish adoption on the client: it is possible to modify the pet and fail to takeOwnership, producing a state that would likely be considered corrupt. A service API such as “public Pet adopt(Pet pet, WTPrincipalReference owner) throws WTException” could utilize a transaction to ensure the entire adoption proceeds smoothly or not at all.
Subsequent chapters in this guide will cover these and many more aspects of modeling and server-side customization.
For more information about modeling a WTObject with masterclass, see
Model a New Document Subclass.