Creating Custom Configuration Specifications
A custom configuration specification expands the structure to the appropriate version using the attributes and the logic provided by the customizer.
Defining Attributes
To define global and standard attributes for the custom configuration specification, perform these steps:
1. Search for a type managed by the custom configuration in the Type Manager.
2. Create the required global or local attributes. Custom configuration specification type has also two MBA attributes, View and Effectivity Context, that you can use in your customization.
3. Add the attributes that you require for the customization in the Edit Filter layout.
4. Set the CUSTOM_CONFIG_SPEC_ENABLED property in the wt.properties file to True.
5. Save and restart Windchill to make your custom configuration specification visible in the Edit Filter widget.
6. (Optional) Change the display name of the custom configuration specification.
* 
You can use multivalued attributes while customizing configuration specifications. For more information, see Using MultiValued Attributes in Customizing Configuration Specifications.
Implementing Logic
To implement the logic, perform these steps:
1. Create a new class that implements wt.vc.config.custom.CustomConfigSpecDelegate.
2. Override the appendSearchCriteria method to provide the custom logic responsible for creating query that is used to resolve the structure to the version.
3. Override the process method to provide the custom logic responsible for processing the QueryResult of iterations, returning only those that match with the algorithm.
4. Override the setAttributesMap and getAttributesMap methods that are used to provide attributes values passed from the Edit Filter widget.
User input is passed to the implementation as a parameter of the setAttributesMap attribute. In this map, the name of the attribute is a key (for MBA and local attributes, the key is an internal name of the attribute, whereas in case of the global attributes, the key is an IBA field name of the attribute), and a value is a CustomConfigSpecAttribute that is a wrapper for your attribute value with additional data such as attribute type and a localized label.
Plugging in the Logic
1. Add service record in the *.xconf file, for example:
<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. Use your custom class as a serviceClass.
3. Propagate the changes by running the following command:
xconfmanager -vpf
Example 1: Global Attributes
Using this example, you can create a custom configuration specification that delegates the appendSearchCriteria logic to WTPartEffectivityConfigSpec.
Defining the Attributes
1. Search for the custom configuration statement managed type in the Type Manager.
2. Create the required global and local attributes.
3. In this example, the View, Effectivity Context, Effectivity Type, and Effectivity Unit attributes are created in the layout. The View and Effectivity Context are modeled attributes on the custom configuration specification type. For the Effectivity Type and Effectivity Unit, global attributes are used in this example:
Effectivity Type - IBA|CompanyName – Enumerated List
Effectivity Unit – IBA|PTC_DIM_DECIMALS – Long
* 
If you want to use different global attributes in your customization, ensure that you make appropriate changes in example customization in the following steps.
Effectivity Type:
Effectivity Unit:
4. Add the required attributes in your customization in the Edit Filter layout.
5. Set the CUSTOM_CONFIG_SPEC_ENABLED property in the wt.properties file to True. The wt.properties file is located in Windchill_Home\codbase\ directory. You can open this file using any text editor.
6. Save and restart Windchill to make your custom configuration specification available in the Edit Filter UI.
7. (Optional) Change the display name of the custom configuration statement that you just added.
Implementing the Logic
In this example, the user input is accessed from attributesMap and by delegating the work to WTPartEffectivityConfigSpec. This example uses the internal names of the MBA attributes and IBA field names for the global attributes.
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;
}
}
Now plug in the logic by performing the steps in the Plugging in the Logic section above.
Result
The custom configuration statement is visible in the Edit Filter layout and can resolve the structure using the custom logic that you have specified:
Example 2: Global Attributes
In this example, a revision is searched that has serial effectivity in the range provided as an input. If no revision is found, another revision is searched that has date effectivity with start range earlier than the one specified in the input.
Defining the Attributes
1. Search for the custom configuration statement managed type in the Type Manager.
2. Create the required global and local attributes.
3. In this example, the Effectivity Context, Effectivity Range, and Effectivity Date attributes are created in the layout. The Effectivity Context is a modeled attribute on the custom configuration specification type. For the Effectivity Range and Effectivity Date attributes, global attributes are used in this example:
Effectivity Range – “IBA|CompanyName” – String
Effectivity Date – “IBA|com.ptc.reql.integrity.CreationDate” – Timestamp
* 
If you want to use different global attributes in your customization, ensure that you make appropriate changes in example customization in the following steps.
4. Add the required attributes in the Edit Filter layout.
5. Set the CUSTOM_CONFIG_SPEC_ENABLED property in the wt.properties file to true. The wt.properties file is located in Windchill_Home\codbase\ directory. You can open this file using any text editor.
6. Save and restart Windchill to make your Custom Configuration Specification visible in the Edit Filter layout.
7. (Optional) Change the display name of the Custom Configuration Statement.
Implementing the Logic
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
*
*/

@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;
}

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
int effClassIndex = dateSelect.addClassList(WTDatedEffectivity.class, false);
ClassAttribute productDateTargetRef = new ClassAttribute(WTDatedEffectivity.class, TARGET_REF);
dateSelect.appendSelect(productDateTargetRef, new int[] {effClassIndex}, 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(), new int[] {effClassIndex});

clone.appendWhere(new ExistsExpression(dateSelect), new int[] {effClassIndex});
clone.appendCloseParen();
return clone;
}

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;
}

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
int serialNumberEffClassIndex = serialEffSelect.addClassList(ProductSerialNumberEffectivity.class, false);
ClassAttribute dTargetRef = new ClassAttribute(ProductSerialNumberEffectivity.class, TARGET_REF);
serialEffSelect.appendSelect(dTargetRef, new int[] {serialNumberEffClassIndex}, 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), new int[] {serialNumberEffClassIndex});
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);
}

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()), new int[] {0});
}
}

private void appendSerialEffRange(QuerySpec subSelect) throws QueryException {
subSelect.appendOpenParen();
subSelect.appendWhere(getEffSearchCondition(START,
SearchCondition.GREATER_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class), new int[] {0});
subSelect.appendAnd();
subSelect.appendWhere(getEffSearchCondition(END,
SearchCondition.LESS_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class), new int[] {0});
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();
}
}
In this example, the user input is accessed from the attributesMap and the query is built. The example uses internal names of the MBA attributes and standard attributes. For IBA attributes, use IBA global fields names.
Plugging in the Logic
1. Add service record in the *.xconf file:
<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. Use your custom class as serviceClass.
3. Propagate the changes by running the following command:
xconfmanager -vpf
Result
The Custom Configuration Statement is visible in the Edit Filter layout, and can resolve the structure according to the custom logic:
Example: Standard Attributes
In this example, a revision is searched, which has the serial effectivity in the range provided in the input. If no such revision is found, a revision is searched, which has the date effectivity with starting range earlier than the one given in the input.
Defining the Attributes
Use the AddColumns utility to add standard attributes that are used in this example:
AddColumns.sh wt.vc.config.custom.CustomConfigSpec String=1 Long=1 Double=1 Timestamp=1
1. Search for the Custom Configuration Statement managed type in the Type Manager.
2. Create the required global and local attributes.
3. Set up the Effectivity Context, Effectivity Range, and Effectivity Date attributes in the layout. Effectivity Context is a modeled attribute on the Custom Configuration Specification type. For the Effectivity Range and Effectivity Date, these standard attributes can be used:
Effectivity Range – “rangeStd” – String
Effectivity Date – “timeStd” – Timestamp
* 
If you use different internal names for the attributes in your customization, ensure that you make appropriate changes in the example customization in the following steps.
Effectivity Range
Effectivity Date
4. Add the required attributes in your customization in the Edit Filter layout.
5. Set the CUSTOM_CONFIG_SPEC_ENABLED property in the wt.properties file to true. The wt.properties file is located in Windchill_Home\codbase\ directory. This file can be modified using any text editor.
6. Save and restart Windchill to make your Custom Configuration Specification available in the Edit Filter UI.
7. (Optional) Change the display name of the Custom Configuration Statement.
Implementing the Logic
In this example, the user input is accessed from the attributesMap and the query is built. The example uses internal names of the MBA attributes and the standard attributes. For IBA attributes, use the IBA Global field names.
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
*
*/

@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;
}

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
int effClassIndex = dateSelect.addClassList(WTDatedEffectivity.class, false);
ClassAttribute productDateTargetRef = new ClassAttribute(WTDatedEffectivity.class, TARGET_REF);
dateSelect.appendSelect(productDateTargetRef, new int[] {effClassIndex}, 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(), new int[] {effClassIndex});

clone.appendWhere(new ExistsExpression(dateSelect), new int[] {effClassIndex});
clone.appendCloseParen();
return clone;
}

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;
}

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
int serialNumberEffClassIndex = serialEffSelect.addClassList(ProductSerialNumberEffectivity.class, false);
ClassAttribute dTargetRef = new ClassAttribute(ProductSerialNumberEffectivity.class, TARGET_REF);
serialEffSelect.appendSelect(dTargetRef, new int[] {serialNumberEffClassIndex}, 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), new int[] {serialNumberEffClassIndex});
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);
}

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()), new int[] {0});
}
}

private void appendSerialEffRange(QuerySpec subSelect) throws QueryException {
subSelect.appendOpenParen();
subSelect.appendWhere(getEffSearchCondition(START,
SearchCondition.GREATER_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class), new int[] {0});
subSelect.appendAnd();
subSelect.appendWhere(getEffSearchCondition(END,
SearchCondition.LESS_THAN_OR_EQUAL, ProductSerialNumberEffectivity.class), new int[] {0});
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();
}
}
Plugging In the Logic
1. Add service record in the *.xconf file:
<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. Use the custom class as serviceClass.
3. Propagate your changes by running the following command:
xconfmanager -vpf
Result
The Custom Configuration Statement is visible in the Edit Filter UI and can be used to resolve the structure according to the custom logic:
Handling Working and Original Versions
This section describes how to handle working and original versions in a custom configuration specification.
Handling Working Versions
If you want your customization to return the Working Version object, ensure that your query includes the Working Version in the results. In most use cases, the query returns both Working and Original versions of the object. During customization, choose only the Working version. To do so, you can use the LatestConfigSpecs process method that returns the Working version because Working version is more latest than the Original version.
@Override
public QueryResult process( QueryResult results )
throws WTException {
if (results == null || !results.hasMoreElements())
return results;

return (new LatestConfigSpec()).process(results);
}
This approach works if your query prepared in the appendSearchCriteria method does not exclude Working versions from the results. If you cannot create query that includes Working versions in the results, you can use the following API to fetch Working version giving Original Version:
/**
* 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;

}

Handling Original Versions
If you want your customization to return only the Original version, you can restrict your query in the appendSearchCriteria method of your customization to exclude Working version.
@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;
}
Alternatively, you can achieve the same in the process method using the WorkInProgressHelper.isWorkingCopy API to determine whether given iteration is a Working version. If it is not, you can exclude it from the result.
@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);
}
Was this helpful?