高级自定义 > 服务和基础结构自定义 > 系统生成 > 业务对象建模
  
业务对象建模
* 
为了进行演示,本章中的某些代码示例已重新格式化,因此其中可能包含行号、隐藏的编辑字符 (例如,制表符和行尾字符) 和额外的空格。如果从本手册剪切和粘贴代码,请检查这些字符,并在尝试于应用程序中使用示例前将其移除。
Windchill 持久化概述
Windchill 持久化架构将 Java 类和字段映射到数据库行和列 (架构)。建模即架构的描述方式,包括使用 PTC "GenAs" 注释对 Java 源文件的类声明进行注释,其中包含用于描述特性、关联和其他元数据的注释成员。
Java 注释处理器会在 javac 编译期间被调用,负责生成字段、访问器、RMI 和 PDS 外部化 API 以及其他方法 (如 equals()hashCode())。表示数据库架构 (表、列和索引) 的 SQL 脚本会在编译后单独生成。
Java 编译器不仅会生成和编译必要的构件以按注释所声明的方式来实现模型,而且还会在运行时生成模型自省所需的信息。它会注册类 (modelRegistry.properties)、关联 (associationRegistry.properties) 及其层次 (descendentRegistry.properties),并生成运行时自省所使用的 ClassInfo 文件。
Jython
Jython 是 Python 主流编程语言的 Java 实现。本章中使用 Jython 提供的工作代码用于在“现实世界”方案中演示示例。Jython 的动态特性使其极具吸引力 (超过 Java 程序),因为其解释器易于交互和探索,而这仅凭已编译的代码和调试器是不可能实现的。此外,尽管 Jython 是 Python 实现 (基于 Java),但其语法可即时转换,便于 Java 程序员上手。
可通过访问 http://jython.org 来获取 Jython。
* 
使用 Jython 的示例假定 Windchill CLASSPATH,如果从 Windchill shell (可通过调用 <加载点>\Windchill\bin shell 启动) 内部运行,则 CLASSPATH 将易于使用。
表建模
模型化类剖析 (GenAsPersistable)
首先,让我们来看一个简单的功能示例。本章中的其他部分将会参考本示例。
列表 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 }
已定义了一个用于扩展 _SimpleExample 的类 SimpleExample (第 16 行)。该类由 GenAsPersistable 注释 (第 11 行 - 第 15 行) 进行注释,这表示此类将作为表持久化到数据库中。该类有一个名为 "name" 的特性 (第 13 行 - 第 14 行),该特性为 (必需的) 字符串。此外,还实现了两种方法:静态工厂方法 (第 19 行 - 第 23 行),以及 checkAttributes() 方法的覆盖 (第 25 行 - 第 33 行)。
_SimpleExample 的源如下所示:
GenAsPersistable 注释的注释处理器会生成父类 (更准确地说,它会生成一个源文件,该源文件随后由编译器进行编译)。
父类为 "name" 特性以及外部化逻辑和其他所需方法提供了一个实现 (查询常量、字段、getter、setter 和 setter 验证)。
"_" 类由列表 2 中显示的字段和方法组成。
列表 2:_SimpleExample javap 结果
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 }
注释由声明构成,所述声明随后由编译器来实现。取代手动实现 "name" 的所有方面,它被声明为注释内的一个特性,而 "_" 父文件实现所有必需的组件。列表 3 包含一个有关 required=truenameValidate() 方法所产生影响的示例。
列表 3:nameValidate() 代码段
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));
superClassinterfaces (列表 1 的第 11 行) 的用途如下:
superClass 注释成员很简单;extends 由 "_" 类指派,因此模型化元素可生成为代码并合并到类中。因此,需要 superClass 才能指定 "true" (逻辑) 父项 (在本例中为 WTObject),随后,"_" 类 (_SimpleExample) 会自动扩展 (如上所示)。
存在类似用途的 interfaces 注释成员:旨在识别出要实现的接口。尽管 implements 并未以类似的方式指派,但是从各种技术角度来看,使用注释成员比直接使用实现更为有效和合乎逻辑。此示例可扩展 WTObject 并实现 WTContained。如果未使用此语法,则编译器将强制执行 "public class <X> extends _<X> { ... }" 语法并生成编译错误。
请注意工厂模式 (newSimpleExample() (列表 1 的第 19 行) 的使用。Windchill 对构造函数使用此模式,因为外部化需要使用 no-arg 构造函数来构造实例,并且调用它的开销较低。该工厂模式由 "public static <X> new<X>(<args...>) throws WTException { ... }" 形式的静态方法组成,且应该用于替代注释文件中的构造函数。工厂主体应遵循示例中所演示的形式。具体来说,工厂主体应:
1. 使用类的 (默认) no-arg 构造函数来构造 instance
2. 调用 (非静态) initialize 方法,并将提供的自变量传递给静态方法。静态工厂决不应自行处理自变量,否则任何子类都不能利用静态方法的工作 (这是 initialize 方法的主要用途:旨在继承行为)。您可能需要创建自己的 initialize 方法 (如果该方法不存在,或需要扩充行为),在这种情况下,它应采用形式 protected void initialize(args...) throws WTException 且应调用 super.initialize(...)
3. 返回 instance
以下工厂/初始化对演示了一种合适的方式来添加工厂方法,使其接受 name 以分配所需的必需特性。
列表 4:工厂/初始化对示例
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 }
此外,还需要 serialVersionUID (列表 1 的第 17 行),因为 Windchill 管理外部化逻辑 (根据需要,包括旧版本反序列化),系统分配的串行版本 UID 将破坏此逻辑。与之前一样,如果未提供,则编译器会生成编译错误。
最后,checkAttributes() 只是为了演示如何使用生成的字段和方法 (列表 1 的第 29 行)。特别是,namenameValidate() 专门使用默认访问而生成,以使其可供被注释的类访问。
编译
有关编译的内容在 com.ptc.windchill.annotations.metadata 包的 JavaDoc 中进行了广泛介绍。但是,SimpleExample 自定义可通过执行以下步骤进行编译和合并:
1. 执行 cd 命令以切换到加载点 (例如,/opt/ptc/Windchill)
2. 通过调用 bin/Windchill shell 启动 Windchill shell
3. 通过 mkdir -p src/com/acme/example 创建 src/com/acme/example 目录
4. 在此目录中创建 SimpleExample.java 并为其提供上面示例的内容
5. 使用 ant -f bin/tools.xml class -Dclass.includes=com/acme/example/* 编译示例 (请注意,必须在加载点目录中执行此命令)
6. 使用 ant -f bin/tools.xml sql_script -Dgen.input=com.acme.example.* 生成 SQL 脚本
7. 查找 create_SimpleExample_Table.sql SQL 脚本 (它将位于 db 中的某个位置) 并进行加载
8. 针对 create_SimpleExample_Index.sql 重复上述操作。
9. 启动/重新启动 MethodServer
Jython 可用于快速验证此示例,如列表 5 中所示。
列表 5:持久化 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)
出现系统提示时,以系统管理员的身份进行身份验证。该对象将在站点容器中创建,具体可通过 sn.getContainer().getName() 进行确认。请注意,我们已创建工厂/初始化方法,并将 name 作为自变量,这样可避免单独调用 setName(’test’)
将类映射到表和列
该示例对 SimpleExample 实例进行了持久化,因此在表中添加了一行。表的详细信息显示在列表 6 中。
列表 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
表的名称反映了类的名称 (这可以进行更改 - 有关详细信息,请参阅注释成员 "tableProperties" 和关联成员 "tableName" 的 JavaDoc)。"name" 列无疑是可识别的,尽管其余列可能无法识别。基于 WTContained 获得的 classnamekeycontainerReferenidA3containerReference 列是存储对容器的参考所必需的。其他列均来自于 Persistable,后者用于将 Windchill 类作为表进行持久化的顶级接口。
模型化关联 (GenAsBinaryLink)
GenAsPersistable 是将类映射到表的两个注释之一。另一个注释是 GenAsBinaryLink,表示关联,用于将两个持续对象 (两个表中的行) 链接在一起。每个二进制链接均由 "A" 和 "B" 两个角色组成。
我们可以创建 SimpleExamples 的图形,其中 SimpleExample 可以有多个父项和子项。
列表 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 }
以类似方式对其进行编译后,可以使用 Jython 对其进行验证,如列表 8 中所示。
列表 8:持久化 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))
SimpleExample 一样,我们有工厂方法 (第 14 行 - 第 17 行)。但是,链接始终关联两个对象,因此工厂方法 (和关联的 initialize 方法) 将接受 A/B 角色作为自变量。
列建模
列建模
GenAsPersistableGenAsBinaryLink 提供三种用于指定数据库列的机制:
1. properties (GeneratedProperty 数组) 表示字符串、数字、布尔值等。
2. foreignKeys (GeneratedForeignKey 数组) 参考其他持续对象 (并存储为类名/键对)
3. roleA/roleB (仅限 GenAsBinaryLink) 是关联中使用的一种特殊形式的外键
此外,derivedProperties (DerivedProperty 数组) 还为特性和外键提供了便捷的访问器。
有时,将一组特性收集到自己的类中是很有用的,然后可将其作为一个实体进行管理并将其聚合到一个持续对象中。GenAsObjectMappable 注释为此目的而存在,并为 Windchill "cookies" 所广泛使用。如果将 GenAsObjectMappable 注释类指定为 GeneratedProperty 的类型,则其所有特性都将成为所属类表中的列。为防止名称冲突,使用由神秘算法决定的字符/数字模式“破坏”这些名称;正是这种破坏行为,使得 idA3containerReference 中出现 "idA3",以及实现从 Persistable.thePersistInfo.theObjectIdentifier.ididA2A2 的映射。
GeneratedProperty
已显示 SimpleExample 中的 GeneratedProperty 示例 (name 是必需的,且具有 String) 类型)。GeneratedProperty 支持大量用于控制生成和行为的注释成员。有关详细信息,请参阅 Windchill JavaDoc。
GeneratedForeignKey
外键未建模,它是从 WTContained 继承而来的。
列表 9:容器定义代码段
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))
容器参考对 SimpleExample 架构的影响是,它会生成两列 (上面已经讨论过)。实际上,如果进一步探究,我们可以看到,我们已通过 ObjectReference 的方式从 WTContainerRef - GenAsObjectMappable 获得了这些字段。我们可以说,外键“只是”另一个特性 (实际上,您可以使用 ObjectReference 类型作为类型对特性进行建模,以获得“相同”结果 (从将要创建的列的角度来看),但 Windchill 持久化层会将外键识别为关联类型,并且可对这些外键进行管理。特别是:
可通过设置 owner=false 来防止 foreignKeyRole 对象被删除 (当其参与关联时)
可通过设置 cascade=true,在删除 foreignKeyRole 对象时删除 myRole 对象
在删除 cascade=false (默认值) 和 foreignKeyRole 角色时,将自动清除由 myRole 保留的参考
您不仅获得参考的访问器,而且还获得为您生成的参考对象的访问器
可通过设置 autoNavigate=true 来自动检索参考对象 (若 required=true),以防止在访问参考对象时命中其他数据库
您可以像导航任何其他二进制链接一样导航外键链接。
当给定的可持续对象关联到最多 一个其他持续对象时,将使用 GeneratedForeignKey。最好对 WTReference 的某些具体子类型的 GeneratedProperty 使用 GeneratedForeignKey,因为通过后者可让 Windchill 持久化架构将参考作为关联进行管理。但是,这种情况下也存在一些例外。特别是,与 WTPrincipals (用户) 的关联应建模为 WTPrincipalReference 类型的 GeneratedProperty,而非 GeneratedForeignKey。这是因为我们不需要关联管理 (不能删除用户),而我们 需要参考的访问器 (其中包含所有人都需要了解的用户相关内容)。
* 
GeneratedRoles 一样,您可以约束现有外键 (在父类/接口上建模),因为这样做通常是为了约束小版本的主数据。在约束外键时,只需提供与要从中继承的外键不同的值;而不需要切实地重新指定父项的所有特性。
GeneratedRole
GeneratedRole 注释用于描述 GenAsBinaryLink 中的角色 A 和角色 B,如前面的 SimpleExampleLink 中所示。可将角色视为将链接与两个可持续对象关联的特殊外键 (具有与外键相同的继承功能)。然而,仅当关联角色的基数为 0-1 且关联不需要其他数据 (特性) 时,外键才适用;而当关联为多对多、关联承载特性或关联在 PTC 业务对象上持久化时 (因为不能更改现有 PTC 类) 时,二进制链接适用。
DerivedProperties
衍生特性本身不是持久化的,但它们为持续特性提供了便捷的访问器 (getter、setter 和查询常量),因此,这些特性在其他情况下将被隐藏在 cookie 中或外键参考中。假设您的字段 c 被隐藏在 cookie b 内,而此 cookie 本身又隐藏在 cookie a (当前类的特性之一) 内。
我们可以使用 this.getA().getB().getC() 进行访问,如果 cookie 未初始化,则请慎重处理可能出现的 NullPointerExceptions。最好是,我们可以仅将此衍生特性添加到类中,以便为我们处理所有内容 (包括处理 NullPointerException)。
列表 10:a.b.c 衍生特性
@DerivedProperty(name="c", derivedFrom="a.b.c")
通过此衍生特性,我们现在只需调用 this.getC() 即可。
如需跨外键来访问关联对象中的字段,请使用外键名称后面的 ">" 分隔符代替 "."。要直接访问关联 masternameWTPart 需依赖以下衍生特性:
列表 11: master>name 衍生特性
@DerivedProperty (name="name " , derivedFrom="master>name " )
专业化持久化构造
GenAsUnPersistable“表”
所有被识别为映射到数据库表的类都必须扩展 wt.fc.PersistableGenAsPersistable 生成的 "_" 类会自动为您处理这一情况;GenAsBinaryLink- 生成的 "_" 类会通过实现 wt.fc.BinaryLink 来处理这一情况,并扩展 Persistable
对要由持续类实现的接口进行建模时,通常会使用这些注释来描述列 (特性、外键和角色),您最终还是希望将实现 (具体) 类表示为列。Windchill 的域接口采用此策略,使您只需实现一组接口便可获取重要的业务逻辑,比如工作进行中 (Workable) 生命周期管理 (LifeCycleManaged) 等。
如果要实现类以 (自动) 持久化接口的特性,则必须为接口加注释。但是,并非每个接口始终仅通过持续类来实现。在某些 (极少数) 情况下,接口可通过持续类 (需为接口加注释,以使其数据可实现持久化) 和非持续类 (问题在于 GenAsPersistableGenAsBinaryLink 是否能确保类最终是 Persistable) 来实现。在上述情况下,可以使用 GenAsUnPersistable 注释来弥补差距:任何持续实现器都将自动处理该接口,就好像该接口是持续的;而非持续实现器不会强制对其进行持久化。
需要注意的是,您可能需要使用 GenAsUnPersistable 为非持续类加注释,以获得与其持续表亲相同的生成优势,但这并不是必需的。此外,由于外部化逻辑可“免费”获得,因此您可能希望在将类作为 BLOB“持续”时使用此注释,因为在创建 BLOB 实例时会采用 RMI 外部化。此处,“作为 BLOB 持续”表示整个类将存储在持续类表内的 BLOB 列中。几乎不推荐创建 BLOB 对象,因为很容易忽略将 BLOB 读回的复杂性,尤其在插入实例后类发生更改的情况下更是如此。请注意,如果要将任何实现 wt.fc.NetFactor 的类存储为 BLOB,则必须生成类 wt.util.Evolvable
GenAsEnueratedType 列
Windchill 为离散的可本地化字符串集提供了建模支持。假设您想要将计算机模型存储在 Windchill 中,并且想将其归为 desktoplaptop、或 server 类别。您可以按如下方式执行此操作:
列表 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 }
列表 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
可使用 GeneratedProperty 将其合并到您的 Computer 类中,如下所示:
列表 14:计算机类型代码段
01 @GeneratedProperty(name="type", type=ComputerType.class, initialValue="ComputerType.getComputerTypeDefault()",
02 constraints=@PropertyConstraints(upperLimit=20, required=true))
该类遵循先前建立的常规格式:一个用于扩展其 (生成的) "_" 类的被注释类。由 GenAsEnumeratedType 注释的类最终会扩展 wt.fc.EnumeratedType,且可以看到,系统会为您生成许多方法。比如 to<X>(String)get<X>Default(),其中 X 是类的名称 (要查看完整的列表,请调用 javap com.acme.example._ComputerType)。
(列表 12 的) 第 7 行 - 第 9 行由常量声明组成,这些声明依赖 toComputerType(...) API 来生成枚举类型的实例。请注意,这些条目必须存在于相应的 rbInfo 文件中,此类文件被命名为 <X>RB.rbInfo 并驻留在同一目录下。此类 rbInfo 文件具有 ResourceInfo.class=wt.tools.resource.EnumResourceInfo 类型,同时支持本地化和值排序。
我们将此枚举类型合并为 GeneratedProperty。请注意使用 initialValue 和约束:必须将所有计算机型号都分配给三种类型中的一种,默认类型为 laptop。枚举类型以简单字符串的形式进行存储 (和序列化) (在这种情况下,存储值将为 desktoplaptopserver,三者取其一)。由于字符串的默认 upperLimit 为 200 个字符,此值相当大 (有关详细信息,请参阅 JavaDoc),于是提供了更合理的限制值。
要构建 rbInfo 文件,请运行 ant -f bin/tools.xml bundle -Dbundle.input=com.acme.example.*。扩展名必须为 "rbInfo",且区分大小写。如果忽略了大写字母 "I",则束目标将会忽略您的文件。
GenAsPrimitiveType 列
如果查看 wt.fc.EnumeratedType 的 JavaDoc,您将会看到已使用 GenAsPrimitiveTypeEnumeratedType 添加了注释。此外,注释的单个自变量为 String.class
GenAsPrimitiveType 注释为简单注释,需要单个值:被注释的类简化到的“基元”类型 (用于持久化和序列化)。您可能从来都不使用此注释,但如果想要构建一个类 (逻辑) 来扩展简单字段的功能,则会提供此注释。如果确实要使用此注释,则不仅需要指定将此类简化到的类型以作为注释的一部分,还需要提供一个用于接受基元类型的构造函数,以及一个用于返回当前值的 <type-in-lower-case>Value() 方法。
有关详细信息,请参阅注释的 JavaDoc。
GenAsDatastoreSequence 数据库序列
Windchill 预设会自动分配部件编号和文档编号。这是通过采用数据库序列来实现的。
列表 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 { }
与之前一样,您需要生成 SQL 并进行加载 (同样,只要对被注释的类进行更改,您就需要重新启动 MethodServer)。完成后,您可以如下所示获取值 (假定您尚未获得序列值,那么以下示例将打印 "1" 和 "2"):
列表 16:获取序列值
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 )
* 
GenAsDatastoreSequence 是四个 GenAsDatastore 注释之一。
服务
Windchill 服务提供了用于管理模型化业务对象的 API 和逻辑。这些服务由以下部分组成:
(可选) 帮助程序,由静态字段和方法组成,其中所述静态字段包括参考服务的静态字段 (通常称为 servicemanager)
服务接口,由可以远程调用的方法声明组成
标准服务,用于实现服务并注册为在方法服务器中作为服务运行。
相关服务在本文档的其他部分中进行了介绍。但是,这是基本模式。
列表 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 }
列表 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 }
列表 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 }
必须在 site.xconf 中注册此服务;编号 99999 必须是唯一的,这样才能替换现有服务。请注意,必须使用 xconfmanager 传播对 site.xconf 的更改。所有自定义服务都必须在 site.xconf 中进行注册。
列表 20:site.xconf 片段
01 <Property name="wt.services.service.99999"
02 targetFile="codebase/wt.properties"
03 value="com.acme.training.MyService/com.acme.
training.StandardMyService"/>
通过此服务,我们可以简化 Jython 示例以持久化 SimpleExample,如下所示:
列表 21:使用服务持久化 SimpleExample
01 from com. acme . example import
02
03 se = ExampleHelper.service.createSimpleExampleByName(’test’)
本地化文本
我们已看到一个本地化文本示例;ComputerTypeRB.rbInfo 包含的 ComputerType 的 (默认英语) 文本。除了提供枚举类型的本地化文本外,我们还需要对类和字段名称以及消息 (比如异常消息) 进行本地化。
类和属性名称
自省提供 API 来获取类和属性的 (本地化) 显示名称。如果未提供显式值,则会计算默认显示名称,如下面的示例所示,在此生成 "Simple Example.Name":
列表 22:显示 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))
假设我们想要将 SimpleName 显示为 "Simple",而将 SimpleName.name 显示为 "aka"。可通过在同一目录中创建 MetadataResourceInfo 来实现此操作,如下所示:
列表 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
这些束类似于用于枚举类型的束样式,并以相同的方式进行编译。编译完成后,我们的 Jython 脚本将产生 "Simple.aka"。
消息
本地化类名称和特性是不够的。应本地化要向用户传达的所有内容 (注意:开发人员不是用户)。rbInfo 格式用于枚举类型以及类及其特性。对于常规消息 (通常包括在服务报告的异常或状况消息中),将使用最终扩展 java.util.ListResourceBundle 的 Java 源/类文件。由于 ListResourceBundle 的合约晦涩难懂,我们将利用 WTListResourceBundle,因为它更具声明性。示例:
列表 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 }
这样,我们会生成一条消息或一个异常:
列表 25:利用 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'])
第一次调用将产生 This is the localized text with a single substitution: ‘‘substitution’’.,第二次调用将引发 (抛出) 包含以下消息的异常:wt.util.WTException: (com.acme.example.exampleResource/0) wt.util.WTException: This is the localized text with a single substitution: ‘‘substitution’.
有关详细信息,请参阅 wt.util.resource 包的 JavaDoc,尤其是 wt.util.resource.WTListResourceBundle
记录模型
每个 (受支持的) 类均被记录为 JavaDoc 的一部分,每个 (受支持的) GenAs 注释类均在其 JavaDoc 中包含 GenAs 注释。这意味着 Windchill 架构在 JavaDoc 中被完全记录下来,并且在对您自己的类进行建模时可供参考。此外,注释本身包含在 JavaDoc 中 (并驻留在 com.ptc.windchill.annotations.metadata 包中)。有关注释的信息和示例用法,可随时查阅 JavaDoc。
如需了解有关如何建模以及挖掘 Windchill 模型的相关示例,JavaDoc 尤其有用。但是,仅通过 JavaDoc 可能很难确定特定类参与的关联,因为角色成员通常不会在自己的 JavaDoc 中包含对其关联的参考。有关给定对象参与的关联的信息,可通过 Windchill 自省获得,并可通过两个实用程序进行访问。
第一个实用程序为 InfoReport。在 Windchill shell 中仅针对指定类运行时,InfoReport 将生成关于类的已知内容,包括特性、数据库信息、子代和 (尤其是) 关联。
第二个实用程序作为 Windchill 用户接口的一部分包括在内,但在默认情况下不启用。客户端会在启用后提供可导航接口以浏览自省信息。
要启用此实用程序,请按以下步骤进行操作:
1. 转至“站点”->“实用程序”->“首选项管理器”
2. “在树中查找”客户端自定义
3. 单击鼠标右键,选择“设置首选项”,然后为其分配值 "yes"。
4. 刷新浏览器
5. 浏览至工具图标 (位于“站点”旁)
6. 选择“工具”,然后选择 "Modeled Objects"
使用 Eclipse 进行开发
本章中的示例 (以及与之类似的自定义) 可以仅使用文本编辑器进行开发,以进行创作和 Ant 构建。也可以使用 Eclipse (http://eclipse.org) 进行开发,这是一个 IDE (tools.xml 的 "eclipse_project.help" 目标将帮助您入门)。
特别是,您需要安装 Eclipse、安装 FileSync 插件、(通过 eclipse_project target) 生成工作区和项目,并将 Eclipse 配置为同时使用工作区和项目。所有这些内容将会在 eclipse_project.help 中加以介绍。
生成的 Eclipse 项目配置为在 <加载点>\src 中处理源,就像 tools.xml 一样。该项目内置于由 Eclipse 托管的输出目录 (<加载点>/eclipse_codebase) 中,并利用 FileSync 将更改传播到 "real" 代码库 (load point/codebase),如此可确保获得的最终结果对与调用类目标所得结果相当。生成的项目不会直接编译到 <加载点>/codebase,因为 Eclipse 会将其删除。
Eclipse 支持编译和注释处理:每次保存源文件时,都会立即对其进行编译。编译将根据需要包括注释处理器的调用和代码的生成。Eclipse 的支持范围不会延伸到构建 rbInfo 文件并生成 SQL,但需要继续使用 tools.xml 来构建这些构件。
Eclipse 用户将需要利用 WTListResourceBundle 及其注释,而非 rbInfo 格式的旧 StringResourceInfo 束。ResourceInfo.class=wt.tools.resource.StringResourceInfo 类型的 rbInfo 文件是唯一要编译成 Java 类文件的 rbInfo 格式。Eclipse 只是不能识别以这种方式生成的类文件,也不会为其提供编译 (它将这些束中对常数的使用标记为错误)。相反,由 Eclipse 完全编译 WTListResourceBundle,且 Eclipse 完全了解它。如果您具有现有的 StringResourceInfo 文件,请考虑使用 bundle_convert 目标来转换这些文件。
生成的 Eclipse 工作区包括用于调试 MethodServer 的调试配置 (有关详细信息,请参阅相关帮助)。尽管 Windchill 不提供源,但您仍可对自己的代码进行调试。
显然,eclipse_target 是诸多选项中颇具优势的一个选项。为确保创建有效的工作区/项目,这种限制是有意的。粗略观察与 eclipse_project 相关的 tools.xml 目标以及 eclipse_project 自身的输出,使您能够更好地了解所进行的操作,并知晓如何定制结果才能更好地满足您的需求。
部署模型化自定义
本部分介绍了部署模型化自定义的指导方针。在此上下文中进行“部署”,即表示将自定义从一个系统复制到另一个系统 - 从源复制到目标。所使用的进程将取决于自定义的实际源和目标。
构建模型化自定义后,将以三种主要方式影响安装:
1. 1. 在位于 <loadpoint>/codebase 的以下文件中注册模型化 (已注释) 的类:
modelRegistry.properties
associationRegistry.properties
descendentRegistry.properties
2. 添加编译的文件和生成的文件:
Java 类 (*.class)
自省构件文件 (*.ClassInfo.ser)
已序列化的束文件 (*.RB.ser)
3. 更新 <loadpoint>/db 中的数据库脚本以合并任何其他架构 (包括索引)
数据库文件 (*.sql)
此外,您可能已将条目添加至 site.xconf 来注册服务。
在任何情况下,都必须:
1. 将自定义所引入的更改复制到目标系统中 (这些更改即上面列举的文件)
2. 通过调用创建表格和索引所需的 SQL 脚本来安装架构
3. 扩充目标系统的 site.xconf 以合并自定义的更改 (并运行 xconfmanager)
4. 重新启动目标系统
将模型化自定义从开发系统部署到生产系统
一个常见的情况是,源为开发系统,目标为生产系统。在这种情况下,我们可以做出简化部署过程的假设。最重要的是,我们假定开发系统和生产系统实质上都是克隆。
本节使用“开发系统”来指代正在开发自定义的源。“生产系统”是指当前正由客户用于生产工作的目标,或在新部署、迁移或升级过程中开发的新生产系统。您的环境可能包括需要维护的其他系统,如测试系统或暂存 (预生产) 系统。只要相同的假设适用,以下所有用于将自定义部署到生产系统的步骤也会应用到其他系统中。
在这种情况下,我们假定开发系统和生产系统都是克隆,并且在您对开发系统进行更改时,您可以将其复制到生产系统中。因此,重要的是生产系统和开发系统共享相同的基本状态 (安装了相同的产品和相同的修补程序,且这些产品和修补程序均处于相同的版本级别),或者您的类可能不兼容,您的注册表文件可能包含无关条目 (或删除重要条目),并且您的数据库架构可能不正确。
如果满足这些假设,则可遵循以下步骤中的指导方针:
1. 将自定义所引入的更改复制到生产系统中 (这些更改即上面列举的文件)
2. 将自定义所引入的更改复制到生产系统中。
a. 在开始自定义之前,将以下注册表文件复制到 C:\temp\original_registry_files
associationRegistry.properties
descendentRegistry.properties
modelRegistry.properties
moduleRegistry.properties
b. 创建新的模型化对象。有关详细信息,请参阅对新文档子类进行建模
c. 要创建要在 d:\Windchill\temp\_model_registry 内添加和删除的合并文件,请在 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. _model_registry 文件夹复制到新的 Windchill 目标系统,并将其粘贴到 d:\Windchill\temp
e. 在新目标系统的 Windchill shell 中运行以下命令:
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. 通过调用必要的 SQL 脚本来安装模式,以创建表格和索引
4. 扩充生产系统的 site.xconf 以合并自定义的更改 (并运行 xconfmanager)
5. 重新启动生产系统。
用于部署模型化自定义的其他策略
将自定义从源部署到目标时,需要在目标系统的源环境中重新执行您的操作。一种策略是只复制源并在目标系统上对其进行重建。但是,需要在目标系统上编译此策略,但如果此系统是您的生产系统,则可能是不可接受的。
另一种策略是使用版本控制系统,如 Git 或 Subversion,将整个加载点置于其下方,使版本控制系统能够告诉您自定义对安装有何影响以及如何合并更改。
合并模型化自定义
如果目标系统中包含不属于您正在部署的源系统的自定义,则需要创建可合并到目标系统中的注册表文件中的增量注册表文件。registry_diff 目标可针对源系统文件运行,以生成要与 registry_merge 目标一起合并到目标系统中的增量文件。
registry_diff 目标可从任一系统的 Windchill shell 运行,但前提是它有权访问所需的文件夹,如下所述。
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 - 在开发自定义之前,包含原始注册表文件的文件夹
理想情况下,在进行任何自定义之前,您会保留注册表文件的原始状态。如果未保留,则要访问原始注册表文件,可能需要取消注册自定义的类,将注册表文件保留为基础,然后重新构建自定义。例如,要取消注册以 "ext." 前缀开头的自定义类:
移除以 "ext." 开头或包含 "=ext." 的行:
ant -f <loadpoint>/bin/tools.xml model_uninstall
-Dmodel_uninstall.filter="\Aext\.|=ext\."
注意:这将与任何以 "ext." 开头的键或值相匹配。
有关其他用法示例,请执行:ant -f <loadpoint>/bin/tools.xml model_uninstall.help
registry_diff.modsdir 是其中包含开发注册表文件 (即开发代码库) 的文件夹,即您的自定义注册的注册表文件的版本。
registry_diff.diffdir 是将要输出 basedirmodsdir 之间的差异的文件夹。在此目录中,可以创建两个文件夹:reg_addsreg_removes。这两个文件夹将由目标系统中的 registry_merge 命令使用,如下所述。
registry_merge 目标必须从目标系统的 Windchill shell 中运行,并且必须具有对 registry_diff 目标所生成的文件的访问权限。
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 是其中包含要添加到目标系统中的自定义条目的文件夹
registry_merge.removes 是其中包含要从目标系统中移除的自定义条目的 (可选) 文件夹