高度なカスタマイズ > ビジネスロジックのカスタマイズ > コンフィギュレーション仕様とフィルタのカスタマイズ > カスタムコンフィギュレーション仕様の作成
  
カスタムコンフィギュレーション仕様の作成
カスタムコンフィギュレーション仕様は、カスタマイザによって指定される属性とロジックを使用して、構造を適切なバージョンに展開します。
属性の定義
カスタムコンフィギュレーション仕様のグローバル属性と標準属性を定義するには、次の手順を実行します。
1. タイプマネージャで、カスタムコンフィギュレーションによって管理されているタイプをサーチします。
2. 必要なグローバルまたはローカル属性を作成します。カスタムコンフィギュレーション仕様タイプには、カスタマイズで使用できる「ビュー」と「エフェクティビティのコンテキスト」の 2 つの MBA 属性があります。
3. 「フィルタを編集」レイアウトで、カスタマイズに必要な属性を追加します。
4. wt.properties ファイルの CUSTOM_CONFIG_SPEC_ENABLED プロパティを True に設定します。
5. 保存して Windchill を再起動すると、「フィルタを編集」ウィジェットにカスタムコンフィギュレーション仕様が表示されるようになります。
6. (オプション) カスタムコンフィギュレーション仕様の表示名を変更します。
* 
コンフィギュレーション仕様をカスタマイズする際、MultiValued 属性を使用できます。詳細については、コンフィギュレーション仕様のカスタマイズでの MultiValued 属性の使用を参照してください。
ロジックの実装
ロジックを実装するには、次の手順を実行します。
1. wt.vc.config.custom.CustomConfigSpecDelegate を実装する新規クラスを作成します。
2. appendSearchCriteria メソッドをオーバーライドし、構造をバージョンに解決するために使用される照会を作成するカスタムロジックを指定します。
3. プロセスメソッドをオーバーライドし、作業版数の QueryResult を処理してアルゴリズムと一致するものだけを返すカスタムロジックを指定します。
4. 「フィルタを編集」ウィジェットから渡される属性値を指定するために使用される setAttributesMap および getAttributesMap メソッドをオーバーライドします。
ユーザー入力は、setAttributesMap 属性のパラメータとして実装に渡されます。このマップでは、属性の名前はキーであり (MBA 属性とローカル属性の場合、キーは属性の内部名ですが、グローバル属性の場合、キーは属性の 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 プロパティを True に設定します。wt.properties ファイルは <Windchill_ホーム>\codbase\ ディレクトリにあります。このファイルは任意のテキストエディタを使用して開くことができます。
6. 保存して Windchill を再起動すると、「フィルタを編集」の UI でカスタムコンフィギュレーション仕様が使用可能になります。
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 プロパティを true に設定します。wt.properties ファイルは <Windchill_ホーム>\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
* 
カスタマイズで属性に異なる内部名を使用する場合は、次の手順のカスタマイズ例で適切な変更を行ってください。
エフェクティビティの範囲
Effectivity Date
4. 「フィルタを編集」レイアウトで、カスタマイズに必要な属性を追加します。
5. wt.properties ファイルの CUSTOM_CONFIG_SPEC_ENABLED プロパティを true に設定します。wt.properties ファイルは <Windchill_ホーム>\codbase\ ディレクトリにあります。このファイルは、任意のテキストエディタを使用して修正できます。
6. それを保存して Windchill を再起動すると、「フィルタを編集」の UI でカスタムコンフィギュレーション仕様が使用可能になります。
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);
}