高级自定义 > 业务逻辑自定义 > 自定义配置规范和筛选器 > 创建自定义配置规范
  
创建自定义配置规范
自定义配置规范使用由定制器提供的属性和逻辑将结构扩展到相应的版本。
定义属性
要定义自定义配置规范的全局和标准属性,请执行以下步骤:
1. 在“类型管理器”中搜索由自定义配置管理的类型。
2. 创建所需的全局或局部属性。自定义配置规范类型也具有可在自定义中使用的两个 MBA 属性、视图和有效性上下文。
3. 在“编辑筛选器”布局中,为自定义添加所需的属性。
4. wt.properties 文件中的 CUSTOM_CONFIG_SPEC_ENABLED 特性设置为 True
5. 保存并重新启动 Windchill,以使得自定义配置规范在“编辑筛选器”小组件中可见。
6. (可选) 更改自定义配置规范的显示名称。
* 
自定义配置规范时可以使用多值属性。有关详细信息,请参阅在自定义配置规范中使用多值属性
实现逻辑
要实现逻辑,请执行以下步骤:
1. 创建用于实现 wt.vc.config.custom.CustomConfigSpecDelegate 的新类。
2. 覆盖 appendSearchCriteria 方法以提供一个可创建查询的自定义逻辑,其中的查询用于将结构解析为版本。
3. 覆盖处理方法,以提供负责处理 QueryResult 迭代的自定义逻辑,仅返回与算法匹配的操作。
4. 覆盖 setAttributesMapgetAttributesMap 用于提供从“编辑筛选器”小组件传递的属性值的方法。
用户输入作为 setAttributesMap 属性的参数传递到实现。在此映射中,属性的名称为 key (对于 MBA 和局部属性,key 是属性的内部名称,而对于全局属性,key 是属性的 IBA 字段名称),值 CustomConfigSpecAttribute 是属性值的封套元素,它具有其他数据,如属性类型和本地化标签。
插入逻辑
1. *.xconf 文件中添加服务记录,例如:
<Service context="default" name="wt.vc.config.custom.CustomConfigSpecDelegate">
<Option cardinality="singleton" requestor="null"
serviceClass="com.example.EffCustomConfigSpecDelegate"
selector="WCTYPE|wt.vc.config.custom.CustomConfigSpec"/>
</Service>
2. 将自定义类用作 serviceClass
3. 通过运行以下命令传播所做更改:
xconfmanager -vpf
示例 1:全局属性
使用此示例,您可以创建一个将 appendSearchCriteria 逻辑委派到 WTPartEffectivityConfigSpec 的自定义配置规范。
定义属性
1. 在“类型管理器”中搜索自定义配置语句管理的类型。
2. 创建所需的全局和局部属性。
3. 在此示例中,将在布局中创建“视图”、“有效性上下文”、“有效性类型”和“有效性单位”属性。“视图”和“有效性上下文”是自定义配置规范类型上的模型化属性。对于“有效性类型”和“有效性单位”,本示例中将使用全局属性:
Effectivity Type - IBA|CompanyName – Enumerated List
Effectivity Unit – IBA|PTC_DIM_DECIMALS – Long
* 
如果要在自定义中使用不同全局属性,请确保在下列步骤中的自定义示例中进行相应的更改。
有效性类型:
有效性单位:
4. 在“编辑筛选器”布局中,为自定义添加所需的属性。
5. wt.properties 文件中的 CUSTOM_CONFIG_SPEC_ENABLED 特性设置为 Truewt.properties 文件位于 Windchill_Home\codbase\ 目录中。可以使用任何文本编辑器打开此文件。
6. 保存并重新启动 Windchill,以使得自定义配置规范在“编辑筛选器”用户界面中可用。
7. (可选) 更改刚添加的自定义配置规范的显示名称。
实现逻辑
在此示例中,用户输入可从 attributesMap 进行访问,方法是将工作委派给 WTPartEffectivityConfigSpec。本示例使用 MBA 属性的内部名称和全局属性的 IBA 字段名称。
package com.example;
import java.util.Map;
import wt.fc.ObjectReference;
import wt.fc.QueryResult;
import wt.part.WTPartEffectivityConfigSpec;
import wt.query.QueryException;
import wt.query.QuerySpec;
import wt.util.WTException;
import wt.util.WTPropertyVetoException;
import wt.vc.config.custom.CustomConfigSpecAttribute;
import wt.vc.config.custom.CustomConfigSpecDelegate;
import wt.vc.views.View;
public class EffCustomConfigSpecDelegate implements CustomConfigSpecDelegate {

static final long serialVersionUID = 1;
private Map<String, CustomConfigSpecAttribute> attributesMap;

private static final String VIEW_REF = "viewRef";
private static final String EFF_CONTEXT = "effectiveContextRef";
private static final String EFF_TYPE = "IBA|CompanyName";
private static final String EFF_UNIT = "IBA|PTC_DIM_DECIMALS";

@Override
public QuerySpec appendSearchCriteria(QuerySpec querySpec) throws WTException, QueryException {
QuerySpec clone = (QuerySpec) querySpec.clone();
View view = (View) ((ObjectReference)getAttributesMap().get(VIEW_REF).getValue()).getObject();
ObjectReference context = (ObjectReference) getAttributesMap().get(EFF_CONTEXT).getValue();
String effType = (String) getAttributesMap().get(EFF_TYPE).getValue();
String effUnit = (String) getAttributesMap().get(EFF_UNIT).getValue();
WTPartEffectivityConfigSpec effConfigSpec = WTPartEffectivityConfigSpec.newWTPartEffectivityConfigSpec();
try {
effConfigSpec.setView(view);
effConfigSpec.setEffectiveContextRef(context);
effConfigSpec.setEffType(effType);
effConfigSpec.setEffectiveUnit(effUnit);
} catch (WTPropertyVetoException e) {
e.printStackTrace();
}
return effConfigSpec.appendSearchCriteria(clone);
}
@Override
public QueryResult process(QueryResult results) throws WTException {
return results;
}
@Override
public void setAttributesMap(Map<String, CustomConfigSpecAttribute> attributesMap) {
this.attributesMap = attributesMap;
}
@Override
public Map<String, CustomConfigSpecAttribute> getAttributesMap() {
return attributesMap;
}
}
现在,通过执行上文中“插入逻辑”一节中的步骤来插入逻辑。
结果
自定义配置语句在“编辑筛选器”布局中可见,并且可以使用已指定的自定义逻辑来解析结构:
示例 2:全局属性
在本示例中,将在作为输入提供的范围内搜索具有序列有效性的修订版本。如果未找到任何修订版本,则会搜索日期有效性的开始范围早于输入中所指定日期的另一个修订版本。
定义属性
1. 在“类型管理器”中搜索自定义配置语句管理的类型。
2. 创建所需的全局和局部属性。
3. 在此示例中,将在布局中创建“有效性上下文”、“有效性范围”和“有效日期”属性。“有效性上下文”是自定义配置规范类型上的模型化属性。对于“有效性范围”和“有效性期”属性,本示例中将使用全局属性:
Effectivity Range – “IBA|CompanyName” – String
Effectivity Date – “IBA|com.ptc.reql.integrity.CreationDate” – Timestamp
* 
如果要在自定义中使用不同全局属性,请确保在下列步骤中的自定义示例中进行相应的更改。
4. 在“编辑筛选器”布局中添加所需的属性。
5. wt.properties 文件中的 CUSTOM_CONFIG_SPEC_ENABLED 特性设置为 truewt.properties 文件位于 Windchill_Home\codbase\ 目录中。 可以使用任何文本编辑器打开此文件。
6. 保存并重新启动 Windchill,以使得自定义配置规范在“编辑筛选器”布局中可见。
7. (可选) 更改自定义配置语句的显示名称。
实现逻辑
package wt.configspec.custom;
import java.lang.reflect.Constructor;
import java.sql.Timestamp;
import java.util.Map;
import wt.eff.Eff;
import wt.eff.EffContext;
import wt.eff.EffManagedVersion;
import wt.eff.EffRange;
import wt.eff.LeftFilledStringEffRange;
import wt.eff.QueryHelper;
import wt.effectivity.WTDatedEffectivity;
import wt.fc.ObjectIdentifier;
import wt.fc.ObjectNoLongerExistsException;
import wt.fc.ObjectReference;
import wt.fc.QueryResult;
import wt.part.ProductSerialNumberEffectivity;
import wt.query.ClassAttribute;
import wt.query.ExistsExpression;
import wt.query.QueryException;
import wt.query.QuerySpec;
import wt.query.SearchCondition;
import wt.query.TableExpression;
import wt.query.WhereExpression;
import wt.util.WTException;
import wt.util.WTPropertyVetoException;
import wt.vc.IterationInfo;
import wt.vc.VersionControlException;
import wt.vc.VersionControlHelper;
import wt.vc.VersionForeignKey;
import wt.vc.VersionReference;
import wt.vc.config.custom.CustomConfigSpecAttribute;
import wt.vc.config.custom.CustomConfigSpecDelegate;
public class EffCustomConfigSpecDelegate implements CustomConfigSpecDelegate {

static final long serialVersionUID = 1;
private static final String EFF_CONTEXT = "effectiveContextRef";
private static final String EFF_DATE = "timeStd";
private static final String EFF_RANGE = "rangeStd";
private static final String BRANCH_ID = EffManagedVersion.ITERATION_INFO
+ "." + IterationInfo.BRANCH_ID;
private static final String TARGET_REF = Eff.TARGET_REFERENCE + "." +
VersionReference.KEY + "." + VersionForeignKey.BRANCH_ID;
private static final String EFF_CONTEXT_REF = Eff.EFF_CONTEXT_REFERENCE
+ "." + ObjectReference.KEY;
private static final String START = Eff.RANGE + "." + EffRange.START;
private static final String END = Eff.RANGE + "." + EffRange.END;
private final int ZERO = 0;
private ObjectReference effContextRef;
private Timestamp inputTimestamp;
private String effRangeStr = "";

/**
* This example will search for the revision which has serial effectivity in range given as an input.
* If no revision is found it will look for revision which has date effectivity which starts range is earlier
* than the one given in the input.
*
* We want to end up with this kind of query in querySpec:
*
* SELECT classnameA2A2,
* idA2A2
* FROM wt.part.WTPart A0
* WHERE (A0.idA3masterReference IN (109490,
* 109189,
* 110167,
* 109654,
* 110136,
* 108859,
* 109886))
* AND (A0.classnamekeycontainerReferen <> 'wt.projmgmt.admin.Project2')
* AND (A0.latestiterationInfo = 1)
* AND ((EXISTS
* (SELECT E10.branchIdA3targetReference
* FROM ProductSNEffectivity E10
* WHERE (((E10.idA3deletionReference IS NULL)
* OR (E10.idA3deletionReference = 0))
* AND (E10.branchIdA3targetReference = A0.branchIditerationInfo)
* AND (E10.idA3effContextReference = 108617)
* AND ((E10.startrange >= 100)
* AND (E10.endrange <= 800)))))
* OR (EXISTS
* (SELECT E20.branchIdA3targetReference
* FROM WTDatedEffectivity E20
* WHERE (((E20.idA3deletionReference IS NULL)
* OR (E20.idA3deletionReference = 0))
* AND (E20.branchIdA3targetReference = A0.branchIditerationInfo)
* AND (E20.startrange <= TO_DATE('2019:03:25:23:00:00', 'YYYY:MM:DD:HH24:MI:SS')))))) *
*
* In this example we assume that the part revision and serial effectivity range are always align
* in this way that earlier revision has no lower serial effectivity that revision which comes after it.
*
* More complex use-cases will require additional result processing in the process method or
* additional conditions/ordering in the query
*
*/
@SuppressWarnings("deprecation")
@Override
public QuerySpec appendSearchCriteria(QuerySpec querySpec) throws WTException, QueryException {

QuerySpec clone = (QuerySpec) querySpec.clone();
ClassAttribute branchId = new ClassAttribute(clone.getClassAt(0), BRANCH_ID);
clone = appendLatestIteration(clone);
clone.appendAnd();
clone.appendOpenParen();
clone = appendSerialEffSubSelect(clone, branchId);
clone.appendOr();
clone = appendDateEffSubSelect(clone, branchId);
clone.appendCloseParen();

return clone;
}
@SuppressWarnings("deprecation")
private QuerySpec appendDateEffSubSelect(QuerySpec clone, ClassAttribute branchId) throws QueryException, WTException {
clone.appendOpenParen();
QuerySpec dateSelect = new QuerySpec();
try {
dateSelect.getFromClause().setAliasPrefix("E2");
} catch (WTPropertyVetoException wtpve) {
// Do nothing...
}

// SELECT branchIdA3targetReference
// FROM wt.effectivity.WTDatedEffectivity E20
dateSelect.addClassList(WTDatedEffectivity.class, false);
ClassAttribute productDateTargetRef = new ClassAttribute(WTDatedEffectivity.class, TARGET_REF);
dateSelect.appendSelect(productDateTargetRef, 0, false);

// WHERE ((E20.idA3deletionReference IS NULL ) OR (E20.idA3deletionReference = 0))
WhereExpression currentEff = QueryHelper.newCurrentEffCondition();
dateSelect.appendWhere(currentEff, currentEff.getFromIndicies());

// AND (E20.branchIdA3targetReference = A0.branchIditerationInfo)
dateSelect.appendAnd();
appendBranchId(clone, branchId, dateSelect, productDateTargetRef);

// AND ((E20.startrange <= TO_DATE('2019:03:31:22:00:00','YYYY:MM:DD:HH24:MI:SS'))
dateSelect.appendAnd();
dateSelect.appendWhere(getEffDateSearchCondition());

clone.appendWhere(new ExistsExpression(dateSelect));
clone.appendCloseParen();
return clone;
}
@SuppressWarnings("deprecation")
private QuerySpec appendLatestIteration(QuerySpec clone) throws QueryException, VersionControlException {
clone.appendAnd();
clone.setAdvancedQueryEnabled(true);
Class latestClass = clone.getClassAt(0);
clone.appendWhere(VersionControlHelper.getSearchCondition(latestClass, true));
return clone;
}
@SuppressWarnings("deprecation")
private QuerySpec appendSerialEffSubSelect(QuerySpec clone, ClassAttribute branchId)
throws QueryException, WTException, ObjectNoLongerExistsException {
clone.appendOpenParen();
QuerySpec serialEffSelect = new QuerySpec();
try {
serialEffSelect.getFromClause().setAliasPrefix("E1");
} catch (WTPropertyVetoException wtpve) {
// Do nothing...
}
// SELECT E10.branchIdA3targetReference
// FROM wt.part.ProductSerialNumberEffectivity E10
serialEffSelect.addClassList(ProductSerialNumberEffectivity.class, false);
ClassAttribute dTargetRef = new ClassAttribute(ProductSerialNumberEffectivity.class, TARGET_REF);
serialEffSelect.appendSelect(dTargetRef, 0, false);

// WHERE ((E10.idA3deletionReference IS NULL ) OR (E10.idA3deletionReference = 0))
WhereExpression currentEff = QueryHelper.newCurrentEffCondition();
serialEffSelect.appendWhere(currentEff, currentEff.getFromIndicies());

// AND ((E10.branchIdA3targetReference = A0.branchIditerationInfo)
serialEffSelect.appendAnd();
appendBranchId(clone, branchId, serialEffSelect, dTargetRef);
// AND ((E10.idA3effContextReference = 108617))
serialEffSelect.appendAnd();
appendEffContext(serialEffSelect);

// AND ((E10.startrange >= 100) AND (E10.endrange <= 800))
serialEffSelect.appendAnd();
appendSerialEffRange(serialEffSelect);

clone.appendWhere(new ExistsExpression(serialEffSelect));
clone.appendCloseParen();
return clone;
}
private void appendBranchId(QuerySpec clone, ClassAttribute branchId, QuerySpec subSelect,
ClassAttribute dTargetRef) throws WTException, QueryException {
TableExpression tables[] = new TableExpression[2];
String aliases[] = new String[2];
tables[0] = subSelect.getFromClause().getTableExpressionAt(0);
tables[1] = clone.getFromClause().getTableExpressionAt(0);
aliases[0] = subSelect.getFromClause().getAliasAt(0);
aliases[1] = clone.getFromClause().getAliasAt(getClassIndex(clone));
subSelect.appendWhere(new SearchCondition(dTargetRef,
SearchCondition.EQUAL, branchId), tables, aliases);
}
@SuppressWarnings("deprecation")
private void appendEffContext(QuerySpec subSelect) throws ObjectNoLongerExistsException, QueryException {
if (getEffContext() != null) {
subSelect.appendWhere(new SearchCondition(ProductSerialNumberEffectivity.class,
EFF_CONTEXT_REF, SearchCondition.EQUAL, (ObjectIdentifier) effContextRef.getKey()));
}
}
@SuppressWarnings("deprecation")
private void appendSerialEffRange(QuerySpec subSelect) throws QueryException {
subSelect.appendOpenParen();
subSelect.appendWhere(getEffSearchCondition(START,
SearchCondition.GREATER_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class));
subSelect.appendAnd();
subSelect.appendWhere(getEffSearchCondition(END,
SearchCondition.LESS_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class));
subSelect.appendCloseParen();
}
private WhereExpression getEffDateSearchCondition() throws QueryException {
try {
Class[] classes = { Class.class, String.class, String.class, Timestamp.class };
Constructor sCC = SearchCondition.class.getConstructor(classes);
Timestamp timestamp = this.inputTimestamp;
Object[] objects = new Object[] { WTDatedEffectivity.class, "range.start", SearchCondition.LESS_THAN_OR_EQUAL, timestamp};
return (SearchCondition) sCC.newInstance(objects);
} catch (Exception e) {

}
return null;
}
private SearchCondition getEffSearchCondition(String attr, String oper, Class effType)
throws QueryException {
try {
Class[] classes = { Class.class, String.class, String.class, String.class };
Constructor sCC = SearchCondition.class.getConstructor(classes);
String range = this.effRangeStr;
int idx = range.indexOf('-');
String startRange = range.substring(0, idx);
String endRange = range.substring(idx+1,range.length());
if(attr.equals("range.start")) {
range = startRange;
}
else if(attr.equals("range.end")) {
range = endRange;
}
range = LeftFilledStringEffRange.leftFill(range.trim());
Object[] objects = new Object[] { effType, attr, oper, range };
return (SearchCondition) sCC.newInstance(objects);
} catch (Exception e) {
// do nothing
}
return null;
}

public EffContext getEffContext()
throws ObjectNoLongerExistsException {
return (effContextRef == null) ? null : (EffContext) effContextRef.getObject();
}
protected int getClassIndex(QuerySpec querySpec) throws WTException {
return ZERO;
}

@Override
public QueryResult process(QueryResult results) throws WTException {
return results;
}
@Override
public void setAttributesMap(Map<String, CustomConfigSpecAttribute> attributesMap) {
effContextRef = (ObjectReference) attributesMap.get(EFF_CONTEXT).getValue();
inputTimestamp = (Timestamp) attributesMap.get(EFF_DATE).getValue();
effRangeStr = (String) attributesMap.get(EFF_RANGE).getValue();
}
}
在此示例中,用户输入可从 attributesMap 进行访问,并会构建查询。该示例将使用 MBA 属性和标准属性的内部名称。对于 IBA 属性,将使用 IBA 全局字段名称。
插入逻辑
1. *.xconf 文件中添加服务记录:
<Service context="default" name="wt.vc.config.custom.CustomConfigSpecDelegate">
<Option cardinality="singleton" requestor="null" serviceClass="com.example.EffCustomConfigSpecDelegate" selector="WCTYPE|wt.vc.config.custom.CustomConfigSpec"/>
</Service>
2. 将自定义类用作 serviceClass
3. 通过运行以下命令传播所做更改:
xconfmanager -vpf
结果
自定义配置语句在“编辑筛选器”布局中可见,并且可以根据自定义逻辑来解析结构:
示例:标准属性
在本示例中,将在作为输入提供的范围内搜索具有序列有效性的修订版本。如果未找到任何修订版本,则会搜索日期有效性的开始范围早于输入中所指定日期的修订版本。
定义属性
使用 AddColumns 实用程序添加在此示例中使用的标准属性:
AddColumns.sh wt.vc.config.custom.CustomConfigSpec String=1 Long=1 Double=1 Timestamp=1
1. 在“类型管理器”中搜索自定义配置语句管理的类型。
2. 创建所需的全局和局部属性。
3. 在布局中设置“有效性上下文”、“有效性范围”和“有效日期”属性。“有效性上下文”是“自定义配置规范”类型上的模型化属性。对于“有效性范围”和“有效日期”,可使用以下标准属性:
Effectivity Range – “rangeStd” – String
Effectivity Date – “timeStd” – Timestamp
* 
如果要对自定义中的属性使用不同内部名称,请确保在自定义示例的后续步骤中进行相应的更改。
有效性范围
有效日期
4. 在“编辑筛选器”布局中,为自定义添加所需的属性。
5. wt.properties 文件中的 CUSTOM_CONFIG_SPEC_ENABLED 特性设置为 truewt.properties 文件位于 Windchill_Home\codbase\ 目录中。 此文件可使用任何文本编辑器进行修改。
6. 保存并重新启动 Windchill,以使得自定义配置规范在“编辑筛选器”用户界面中可用。
7. (可选) 更改自定义配置语句的显示名称。
实现逻辑
在此示例中,用户输入可从 attributesMap 进行访问,并会构建查询。该示例将使用 MBA 属性和标准属性的内部名称。对于 IBA 属性,将使用 IBA 全局字段名称。
package wt.configspec.custom;
import java.lang.reflect.Constructor;
import java.sql.Timestamp;
import java.util.Map;
import wt.eff.Eff;
import wt.eff.EffContext;
import wt.eff.EffManagedVersion;
import wt.eff.EffRange;
import wt.eff.LeftFilledStringEffRange;
import wt.eff.QueryHelper;
import wt.effectivity.WTDatedEffectivity;
import wt.fc.ObjectIdentifier;
import wt.fc.ObjectNoLongerExistsException;
import wt.fc.ObjectReference;
import wt.fc.QueryResult;
import wt.part.ProductSerialNumberEffectivity;
import wt.query.ClassAttribute;
import wt.query.ExistsExpression;
import wt.query.QueryException;
import wt.query.QuerySpec;
import wt.query.SearchCondition;
import wt.query.TableExpression;
import wt.query.WhereExpression;
import wt.util.WTException;
import wt.util.WTPropertyVetoException;
import wt.vc.IterationInfo;
import wt.vc.VersionControlException;
import wt.vc.VersionControlHelper;
import wt.vc.VersionForeignKey;
import wt.vc.VersionReference;
import wt.vc.config.custom.CustomConfigSpecAttribute;
import wt.vc.config.custom.CustomConfigSpecDelegate;
public class EffCustomConfigSpecDelegate implements CustomConfigSpecDelegate {

static final long serialVersionUID = 1;
private static final String EFF_CONTEXT = "effectiveContextRef";
private static final String EFF_DATE = "timeStd";
private static final String EFF_RANGE = "rangeStd";
private static final String BRANCH_ID = EffManagedVersion.ITERATION_INFO
+ "." + IterationInfo.BRANCH_ID;
private static final String TARGET_REF = Eff.TARGET_REFERENCE + "." +
VersionReference.KEY + "." + VersionForeignKey.BRANCH_ID;
private static final String EFF_CONTEXT_REF = Eff.EFF_CONTEXT_REFERENCE
+ "." + ObjectReference.KEY;
private static final String START = Eff.RANGE + "." + EffRange.START;
private static final String END = Eff.RANGE + "." + EffRange.END;
private final int ZERO = 0;
private ObjectReference effContextRef;
private Timestamp inputTimestamp;
private String effRangeStr = "";

/**
* This example will search for the revision which has serial effectivity in range given as an input.
* If no revision is found it will look for revision which has date effectivity which starts range is eariler
* than the one given in the input.
*
* We want to end up with this kind of query in querySpec:
*
* SELECT classnameA2A2,
* idA2A2
* FROM wt.part.WTPart A0
* WHERE (A0.idA3masterReference IN (109490,
* 109189,
* 110167,
* 109654,
* 110136,
* 108859,
* 109886))
* AND (A0.classnamekeycontainerReferen <> 'wt.projmgmt.admin.Project2')
* AND (A0.latestiterationInfo = 1)
* AND ((EXISTS
* (SELECT E10.branchIdA3targetReference
* FROM ProductSNEffectivity E10
* WHERE (((E10.idA3deletionReference IS NULL)
* OR (E10.idA3deletionReference = 0))
* AND (E10.branchIdA3targetReference = A0.branchIditerationInfo)
* AND (E10.idA3effContextReference = 108617)
* AND ((E10.startrange >= 100)
* AND (E10.endrange <= 800)))))
* OR (EXISTS
* (SELECT E20.branchIdA3targetReference
* FROM WTDatedEffectivity E20
* WHERE (((E20.idA3deletionReference IS NULL)
* OR (E20.idA3deletionReference = 0))
* AND (E20.branchIdA3targetReference = A0.branchIditerationInfo)
* AND (E20.startrange <= TO_DATE('2019:03:25:23:00:00', 'YYYY:MM:DD:HH24:MI:SS')))))) *
*
* In this example we assume that the part revision and serial effectivity range are always align
* in this way that earlier revision has no lower serial effectivity that revision which comes after it.
*
* More complex use-cases will require additional result processing in the process method or
* additional conditions/ordering in the query
*
*/
@SuppressWarnings("deprecation")
@Override
public QuerySpec appendSearchCriteria(QuerySpec querySpec) throws WTException, QueryException {

QuerySpec clone = (QuerySpec) querySpec.clone();
ClassAttribute branchId = new ClassAttribute(clone.getClassAt(0), BRANCH_ID);
clone = appendLatestIteration(clone);
clone.appendAnd();
clone.appendOpenParen();
clone = appendSerialEffSubSelect(clone, branchId);
clone.appendOr();
clone = appendDateEffSubSelect(clone, branchId);
clone.appendCloseParen();

return clone;
}
@SuppressWarnings("deprecation")
private QuerySpec appendDateEffSubSelect(QuerySpec clone, ClassAttribute branchId) throws QueryException, WTException {
clone.appendOpenParen();
QuerySpec dateSelect = new QuerySpec();
try {
dateSelect.getFromClause().setAliasPrefix("E2");
} catch (WTPropertyVetoException wtpve) {
// Do nothing...
}
// SELECT branchIdA3targetReference
// FROM wt.effectivity.WTDatedEffectivity E20
dateSelect.addClassList(WTDatedEffectivity.class, false);
ClassAttribute productDateTargetRef = new ClassAttribute(WTDatedEffectivity.class, TARGET_REF);
dateSelect.appendSelect(productDateTargetRef, 0, false);

// WHERE ((E20.idA3deletionReference IS NULL ) OR (E20.idA3deletionReference = 0))
WhereExpression currentEff = QueryHelper.newCurrentEffCondition();
dateSelect.appendWhere(currentEff, currentEff.getFromIndicies());

// AND (E20.branchIdA3targetReference = A0.branchIditerationInfo)
dateSelect.appendAnd();
appendBranchId(clone, branchId, dateSelect, productDateTargetRef);

// AND ((E20.startrange <= TO_DATE('2019:03:31:22:00:00','YYYY:MM:DD:HH24:MI:SS'))
dateSelect.appendAnd();
dateSelect.appendWhere(getEffDateSearchCondition());

clone.appendWhere(new ExistsExpression(dateSelect));
clone.appendCloseParen();
return clone;
}

@SuppressWarnings("deprecation")
private QuerySpec appendLatestIteration(QuerySpec clone) throws QueryException, VersionControlException {
clone.appendAnd();
clone.setAdvancedQueryEnabled(true);
Class latestClass = clone.getClassAt(0);
clone.appendWhere(VersionControlHelper.getSearchCondition(latestClass, true));
return clone;
}

@SuppressWarnings("deprecation")
private QuerySpec appendSerialEffSubSelect(QuerySpec clone, ClassAttribute branchId)
throws QueryException, WTException, ObjectNoLongerExistsException {
clone.appendOpenParen();
QuerySpec serialEffSelect = new QuerySpec();
try {
serialEffSelect.getFromClause().setAliasPrefix("E1");
} catch (WTPropertyVetoException wtpve) {
// Do nothing...
}
// SELECT E10.branchIdA3targetReference
// FROM wt.part.ProductSerialNumberEffectivity E10
serialEffSelect.addClassList(ProductSerialNumberEffectivity.class, false);
ClassAttribute dTargetRef = new ClassAttribute(ProductSerialNumberEffectivity.class, TARGET_REF);
serialEffSelect.appendSelect(dTargetRef, 0, false);

// WHERE ((E10.idA3deletionReference IS NULL ) OR (E10.idA3deletionReference = 0))
WhereExpression currentEff = QueryHelper.newCurrentEffCondition();
serialEffSelect.appendWhere(currentEff, currentEff.getFromIndicies());

// AND ((E10.branchIdA3targetReference = A0.branchIditerationInfo)
serialEffSelect.appendAnd();
appendBranchId(clone, branchId, serialEffSelect, dTargetRef);
// AND ((E10.idA3effContextReference = 108617))
serialEffSelect.appendAnd();
appendEffContext(serialEffSelect);

// AND ((E10.startrange >= 100) AND (E10.endrange <= 800))
serialEffSelect.appendAnd();
appendSerialEffRange(serialEffSelect);

clone.appendWhere(new ExistsExpression(serialEffSelect));
clone.appendCloseParen();
return clone;
}
private void appendBranchId(QuerySpec clone, ClassAttribute branchId, QuerySpec subSelect,
ClassAttribute dTargetRef) throws WTException, QueryException {
TableExpression tables[] = new TableExpression[2];
String aliases[] = new String[2];
tables[0] = subSelect.getFromClause().getTableExpressionAt(0);
tables[1] = clone.getFromClause().getTableExpressionAt(0);
aliases[0] = subSelect.getFromClause().getAliasAt(0);
aliases[1] = clone.getFromClause().getAliasAt(getClassIndex(clone));
subSelect.appendWhere(new SearchCondition(dTargetRef,
SearchCondition.EQUAL, branchId), tables, aliases);
}
@SuppressWarnings("deprecation")
private void appendEffContext(QuerySpec subSelect) throws ObjectNoLongerExistsException, QueryException {
if (getEffContext() != null) {
subSelect.appendWhere(new SearchCondition(ProductSerialNumberEffectivity.class,
EFF_CONTEXT_REF, SearchCondition.EQUAL, (ObjectIdentifier) effContextRef.getKey()));
}
}
@SuppressWarnings("deprecation")
private void appendSerialEffRange(QuerySpec subSelect) throws QueryException {
subSelect.appendOpenParen();
subSelect.appendWhere(getEffSearchCondition(START,
SearchCondition.GREATER_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class));
subSelect.appendAnd();
subSelect.appendWhere(getEffSearchCondition(END,
SearchCondition.LESS_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class));
subSelect.appendCloseParen();
}
private WhereExpression getEffDateSearchCondition() throws QueryException {
try {
Class[] classes = { Class.class, String.class, String.class, Timestamp.class };
Constructor sCC = SearchCondition.class.getConstructor(classes);
Timestamp timestamp = this.inputTimestamp;
Object[] objects = new Object[] { WTDatedEffectivity.class, "range.start", SearchCondition.LESS_THAN_OR_EQUAL, timestamp};
return (SearchCondition) sCC.newInstance(objects);
} catch (Exception e) {

}
return null;
}
private SearchCondition getEffSearchCondition(String attr, String oper, Class effType)
throws QueryException {
try {
Class[] classes = { Class.class, String.class, String.class, String.class };
Constructor sCC = SearchCondition.class.getConstructor(classes);
String range = this.effRangeStr;
int idx = range.indexOf('-');
String startRange = range.substring(0, idx);
String endRange = range.substring(idx+1,range.length());
if(attr.equals("range.start")) {
range = startRange;
}
else if(attr.equals("range.end")) {
range = endRange;
}
range = LeftFilledStringEffRange.leftFill(range.trim());
Object[] objects = new Object[] { effType, attr, oper, range };
return (SearchCondition) sCC.newInstance(objects);
} catch (Exception e) {
// do nothing
}
return null;
}

public EffContext getEffContext()
throws ObjectNoLongerExistsException {
return (effContextRef == null) ? null : (EffContext) effContextRef.getObject();
}
protected int getClassIndex(QuerySpec querySpec) throws WTException {
return ZERO;
}

@Override
public QueryResult process(QueryResult results) throws WTException {
return results;
}
@Override
public void setAttributesMap(Map<String, CustomConfigSpecAttribute> attributesMap) {
effContextRef = (ObjectReference) attributesMap.get(EFF_CONTEXT).getValue();
inputTimestamp = (Timestamp) attributesMap.get(EFF_DATE).getValue();
effRangeStr = (String) attributesMap.get(EFF_RANGE).getValue();
}
}
插入逻辑
1. *.xconf 文件中添加服务记录:
<Service context="default" name="wt.vc.config.custom.CustomConfigSpecDelegate">
<Option cardinality="singleton" requestor="null" serviceClass="com.example.EffCustomConfigSpecDelegate" selector="WCTYPE|wt.vc.config.custom.CustomConfigSpec"/>
</Service>
2. 将自定义类用作 serviceClass
3. 通过运行以下命令传播所做更改:
xconfmanager -vpf
结果
自定义配置语句在“编辑筛选器”UI 中可见,并且可用于根据自定义逻辑来解析结构:
处理工作和原始版本
本节介绍如何处理自定义配置规范中的工作和原始版本。
处理工作版本
如果希望您的自定义返回“工作版本”对象,则请确保您的查询结果中包括“工作版本”。在大多数用例中,查询会同时返回对象的工作和原始版本。在自定义期间,仅选取工作版本。为此,您可以使用将返回工作版本的 LatestConfigSpecs 处理方法,因为工作版本比原始版本更新。
@Override
public QueryResult process( QueryResult results )
throws WTException {
if (results == null || !results.hasMoreElements())
return results;
return (new LatestConfigSpec()).process(results);
}
如果在 appendSearchCriteria 方法中准备的查询未排除结果中的工作版本,则此方法会起作用。如果无法创建在结果中包括工作版本的查询,则可以使用以下 API 来获取提供原始版本的工作版本:
/**
* Handles replacing of original copy with working copy in Query result.
*/
@Override
public QueryResult process(QueryResult results) throws WTException {
final ObjectVectorIfc validIterations = handleWorkingCopies(results);
return new QueryResult(validIterations);
}
/**
* 1) Get Original to working copies map for all the checked out objects in QueryResult
* 2) Inflate the Value - so that we have inflated working copies
* 3) Iterate over keySet i.e. Original copies.
* 3.1) Check if the session user has checked out the object
* - if yes, then get its working copy from the map.
* - Remove the original copy from the query result.
* - Add the working copy to query result.
*
* @param results - containing working copies of checkout out workables for which
* user has access.
* @return
* @throws WorkInProgressException
* @throws WTException
*/
private ObjectVectorIfc handleWorkingCopies(QueryResult results) throws WorkInProgressException, WTException {
ObjectVectorIfc resultVector = results.getObjectVectorIfc();
WTValuedMap orgToWrkMap = WorkInProgressHelper.service.getCheckedOutToWorkingMap(new WTArrayList(results));
boolean isAccessEnforced = SessionServerHelper.manager.setAccessEnforced(false);
try {
orgToWrkMap.wtValues().inflate();
} finally {
SessionServerHelper.manager.setAccessEnforced(isAccessEnforced);
}
for (Object item : orgToWrkMap.wtKeySet()) {
Workable org = (Workable) ((ObjectReference) item).getObject();
if (WorkInProgressHelper.isCheckedOut(org, SessionHelper.getPrincipal())) {
if (logger.isDebugEnabled()) {
logger.debug("Working copy added of : " + org.getIdentity());
}
resultVector.removeElement(org);
resultVector.addElement(orgToWrkMap.getPersistable(org));
}
}
return resultVector;
}
处理原始版本
如果希望自定义仅返回“原始”版本,则可以在自定义的 appendSearchCriteria 方法中限制查询以排除“工作”版本。
@Override
public QuerySpec appendSearchCriteria( QuerySpec querySpec )
throws WTException, QueryException {
QuerySpec qs = (QuerySpec) querySpec.clone();
qs.appendAnd();
qs.appendWhere(new SearchCondition(Workable.class, Workable.CHECKOUT_INFO+"."+CheckoutInfo.STATE,
SearchCondition.NOT_EQUAL, WorkInProgressState.WORKING), new int[]{0});
return qs;
}
或者,也可以在使用 WorkInProgressHelper.isWorkingCopy API 来确定给定迭代是否为工作版本的处理方法中实现此目的。如果设置为否,则可将其从结果中排除。
@Override
public QueryResult process(QueryResult results) throws WTException {
final ObjectVector validIterations = new ObjectVector();
final Enumeration resultEnum = results.getEnumeration();
while (resultEnum.hasMoreElements()) {
Object iteration = resultEnum.nextElement();
iteration = VersionControlHelper.service.getLatestIteration((Iterated) iteration, false);
if (WorkInProgressHelper.isWorkingCopy((Workable) iteration))
continue;
validIterations.addElement(iteration);
}
return new QueryResult(validIterations);
}