高度なカスタマイズ > ビジネスロジックのカスタマイズ > Windchill ProjectLink のカスタマイズ > バリエーションベースラインのメンバーのためのカスタム計画属性アルゴリズムの作成 > 期限と進行状況のカスタムアルゴリズムの作成
  
期限と進行状況のカスタムアルゴリズムの作成
FloatingBaselineMember オブジェクトの「期限」属性と「進行状況」属性を、これらの属性や関連アクティビティでの変更に基づいて更新するカスタムアルゴリズムを作成するには、以下のステップを実行します。
1. 「期限」「進行状況」の表示制約を「読み取り専用」に設定します。
2. カスタムハンドラクラスを作成します。
3. カスタムハンドラクラスを設定します。
* 
「期限」属性と「進行状況」属性のアルゴリズムが適切に設定されていないかシステムに存在しない場合、バリエーションベースラインオブジェクトのこれらの属性の値は空白になります。
前提条件
カスタムアルゴリズムを作成する前に、以下のステップを実行します。
1. ベースラインオブジェクトを作成します。
2. 「ベースラインオブジェクト」または「バリエーションベースライン」テーブルに、「期限」列と「進行状況」列を含むカスタムビューを作成します。詳細については、テーブルビューのカスタマイズを参照してください。
3. 「タイプおよび属性の管理」ユーティリティを使用して「浮動ベースラインメンバー」タイプに「進行状況」属性と「期限」属性を追加することで、これらの属性を Matrix Editor のバリエーションベースラインメンバー属性枠に表示します。詳細については、属性レイアウトの編集を参照してください。
「期限」と「進行状況」の表示制約を「読み取り専用」に設定
ユーザーインタフェースからの手動更新を制限するため、「タイプおよび属性の管理」ユーティリティから表示制約 wt.vc.baseline.FloatingBaselineMember(Floating Baseline Member) を使用してこれらの計画属性を読み取り専用に設定します。詳細については、属性表示設定の表示と設定を参照してください。
これらの属性の値を計算するためのカスタマイズが不要な場合、該当するユーザーインタフェースでこれらの属性を手動で更新できるように、管理者は表示制約を「読み取り/書き込み」に設定できます。
ただし、カスタマイズが行われた後で管理者が表示制約を「読み取り/書き込み」に設定した場合、ユーザーインタフェースでこれらの属性の値を修正した後、設定済みのカスタムハンドラが呼び出された場合にはそれに基づいて値が再計算されます。そうしない場合、これらの属性の値を計算する実装済みのカスタマイズを除去できます。
管理者はシステムセットアップ時にこの決定を行うことができます。
カスタムハンドラクラスの作成
FBMLPlannableAttributesHandler インタフェースを使用してカスタムアルゴリズムを作成できます。このインタフェースは以下のように定義されています。
updateFloatingBaselineMemberLinksFromActivity(<Collection of activity objects>);updateFloatingBaselineMemberLinks(<Collection of Floating Baseline Member link objects>);
このインタフェースおよびサポートされている API を実装することでカスタムアルゴリズムをプラグインできます。以下の例に示すサンプルカスタムハンドラは、関連付けられている計画アクティビティの期限と進行状況に基づいて期限と進行状況を計算するカスタムアルゴリズムの一例です。
public class VWUpdatePlannableAttributesOnFBML implements FBMLPlannableAttributesHandler, Serializable {
private static final long serialVersionUID = 1L;
private static final Logger log = LogR.getLogger(VWUpdatePlannableAttributesOnFBML.class.getName());
private static boolean isDebugEnabled = log.isDebugEnabled();

@Override
public void updateFloatingBaselineMemberLinksFromActivity(WTCollection activityList)
throws WTException, WTPropertyVetoException {
isDebugEnabled = log.isDebugEnabled();
if(isDebugEnabled){log.debug(" IN Custom handler: updateFloatingBaselineMemberLinksFromActivity()");}
try{
updateFBMLlogic(activityList);
}catch(WTException e){
log.error("Execption in updateFloatingBaselineMemberLinksFromActivity.."+e);
throw new WTException(e);
}

if(isDebugEnabled){log.debug(" OUT Custom handler: updateFloatingBaselineMemberLinksFromActivity()");}
}
/**
* Method to update FBML objects
* @param activityList
* @throws WTException
* @throws WTPropertyVetoException
*/
private void updateFBMLlogic(WTCollection activityList) throws WTException, WTPropertyVetoException {

WTKeyedHashMap activitySubjectsMap = DeliverableHelper.service.getActivitySubjects(activityList);
if(isDebugEnabled){log.debug(" updateFBMLlogic: got ActivitySubjects list... ");}
WTKeyedHashMap activityToParentMap = new WTKeyedHashMap();
Map<PlanActivity, WTHashSet> actToFBMLmap = new HashMap<PlanActivity, WTHashSet>();
//create activity To fbml List map
createActToFBMLlistMap(activitySubjectsMap, activityToParentMap, actToFBMLmap);
if(isDebugEnabled){log.debug(" updateFBMLlogic: created Activity To FBMLlist Map ");}
WTArrayList ancestorsList = new WTArrayList();

for(Object key : activityToParentMap.keySet()){
ancestorsList.addAll((WTArrayList) activityToParentMap.get(key));
}
//get ancestorsSubjectsMap - SUPPORTED API
WTKeyedHashMap ancestorsSubjectsMap = DeliverableHelper.service.getActivitySubjects(ancestorsList);
if(isDebugEnabled){log.debug(" updateFBMLlogic: got ActivitySubjects for ancestorsList... ");}
Map<FloatingBaselineMember, Set> fbmlToActmap = new HashMap<FloatingBaselineMember, Set>();
//create fbml To ActList map
createFbmlToActListMap(activityToParentMap, actToFBMLmap, ancestorsSubjectsMap, fbmlToActmap);
if(isDebugEnabled){log.debug(" updateFBMLlogic: created FBML To Activity list Map ");}
//get resulting activity and update its status to the FBML
WTCollection coll = new WTArrayList();
for(FloatingBaselineMember fbml :fbmlToActmap.keySet()){
Set plaActList = fbmlToActmap.get(fbml);
WTCollection ignoreActivityCollection = (WTCollection) MethodContext.getContext().get(PlannableCommands.ACTIVITY_IGNORE_LIST);if(ignoreActivityCollection!=null && !ignoreActivityCollection.isEmpty()){
if(isDebugEnabled){log.debug("Removing the activities that need to be ignored while updating fbml: no. of activities -"+ignoreActivityCollection.size());}
//These are the activities that are either being deleted or being removed from association with subject
Iterator iter = ignoreActivityCollection.persistableIterator();
while(iter.hasNext()){
Object ignoreActivity = iter.next();
plaActList.remove(ignoreActivity);
}
}
PlanActivity planActivity = getResultingActivity(plaActList);if(planActivity!=null){
if(isDebugEnabled){log.debug(" updateFBMLlogic: Got resulting activity: "+planActivity+" \n for the FBML:"+fbml);}

HealthStatusType health = planActivity.getHealthStatusType();
fbml.setHealthStatusType(health);
fbml.setDeadline(planActivity.getDeadline());
}else{
if(isDebugEnabled){log.debug(" updateFBMLlogic: No resulting activity for the FBML:"+fbml+", hence setting planning attributes to null");}
fbml.setHealthStatusType(null);
fbml.setDeadline(null);
}
coll.add(fbml);
}
PersistenceHelper.manager.save(coll);
if(isDebugEnabled){log.debug(" updateFBMLlogic: updated the collection of FloatingBaselineMember links");}
}
/**
* create activity To fbml List map
* @param activitySubjectsMap
* @param activityToParentMap
* @param actToFBMLmap
* @throws WTException
*/
public void createActToFBMLlistMap(WTKeyedHashMap activitySubjectsMap, WTKeyedHashMap activityToParentMap,
Map<PlanActivity, WTHashSet> actToFBMLmap) throws WTException {
if(isDebugEnabled){log.debug(" createActToFBMLlistMap: creating FBML To activity list Map ");}
for(Object key : activitySubjectsMap.keySet()){
ArrayList subjectList = (ArrayList)activitySubjectsMap.get(key);
WTArrayList list = new WTArrayList();
list.addAll(subjectList);
//TODO: when same part is added to 2 or more activities
WTArrayList subjectListCollection = new WTArrayList();
subjectListCollection.addAll(subjectList);
//- SUPPORTED API
WTKeyedHashMap associatedActivities = DeliverableHelper.service.getAssociatedActivities(subjectListCollection);
//- SUPPORTED API
Map map= EPPCustomUtils.getFloatingBaselineMemberLinks(list, null, true); for(Object obj : subjectList){
if(obj instanceof SubjectOfDeliverable){
SubjectOfDeliverable subject = (SubjectOfDeliverable) obj;

ObjectIdentifier objectIdentifier = subject.getPersistInfo().getObjectIdentifier();
if(map.get(subject)!=null && associatedActivities.get(objectIdentifier)!=null){
// add all activities associated with Subject and FBML to actToFBMLmap map
WTHashSet fbmlList = new WTHashSet();
fbmlList.addAll((Collection) map.get(subject));
WTArrayList actList = (WTArrayList) associatedActivities.get(objectIdentifier);
//get All Ancestors Of Activities - - SUPPORTED API
activityToParentMap.putAll(PlannableHelper.service.getAllAncestorsOfActivities(actList));
Iterator actIterator = actList.persistableIterator();
while(actIterator.hasNext()){
//need to create new reference of WTHashSet for adding fbmlList to map
WTHashSet fbmlListForAddition = new WTHashSet();
fbmlListForAddition.addAll(fbmlList);
PlanActivity planActivity = (PlanActivity)actIterator.next();
if(actToFBMLmap.get(planActivity)!=null){
WTHashSet set = actToFBMLmap.get(planActivity);
set.addAll(fbmlListForAddition);
actToFBMLmap.put(planActivity, set);
}else{
actToFBMLmap.put( planActivity, fbmlListForAddition);
}
}//while
}// if(fbmlList!=null)
}// if(obj instanceof SubjectOfDeliverable){
}//for(Object obj : subjectList){
}
}
**
* create fbml To ActList map
* @param activityToParentMap
* @param actToFBMLmap
* @param ancestorsSubjectsMap
* @param fbmlToActmap
* @throws WTException
*/
public void createFbmlToActListMap(WTKeyedHashMap activityToParentMap, Map<PlanActivity, WTHashSet> actToFBMLmap,
WTKeyedHashMap ancestorsSubjectsMap, Map<FloatingBaselineMember, Set> fbmlToActmap) throws WTException {
if(isDebugEnabled){log.debug(" createFbmlToActListMap: creating Activity To FBMLlist Map ");}
for(PlanActivity planActivity :actToFBMLmap.keySet()){

WTHashSet fbmlList = new WTHashSet();
if(actToFBMLmap.get(planActivity)!=null){
fbmlList = (WTHashSet) actToFBMLmap.get(planActivity);
}
Iterator itr = fbmlList.iterator();
while(itr.hasNext()){
Set planActList = new HashSet();
Object fbmlObj = itr.next();
if(fbmlObj instanceof FloatingBaselineMember){
FloatingBaselineMember fbml = (FloatingBaselineMember) fbmlObj;
Baseline baseline = (Baseline) fbml.getRoleAObject();
boolean isBaselineSubjectOfToAncestor = false;
if(activityToParentMap.get(planActivity)!=null){
WTArrayList activityAncestors = (WTArrayList) activityToParentMap.get(planActivity);
if(isDebugEnabled){log.debug(" createFbmlToActListMap: checking if the baseline is added as subject to activity's parents, activity: "+planActivity);}
isBaselineSubjectOfToAncestor=isBaselineSubjectOfActivityAncestors(baseline,ancestorsSubjectsMap,activityAncestors);

} if(isBaselineSubjectOfToAncestor){
if(fbmlToActmap.get(fbml)!=null){
fbmlToActmap.get(fbml).add(planActivity);
}else{
planActList.add(planActivity);
fbmlToActmap.put(fbml, planActList);
}
}
}
}
}
}
/**
* return get activity with max duration and %work complete less than 100
* @param actList
* @return
*/
private PlanActivity getResultingActivity(Set actList) {
if(isDebugEnabled){log.debug(" getResultingActivity: getting resulting activity from the actList: "+actList);}
//get activity with max duration;
PlanActivity resultingAct = null;
for(Object actObj : actList){
if(actObj instanceof PlanActivity){
PlanActivity activity = (PlanActivity)actObj;
if(resultingAct == null && activity.getPercentWorkComplete()!=100){
resultingAct = activity;
}else{
if(resultingAct!=null && resultingAct.getDuration().getMillis()<activity.getDuration().getMillis()){
if(activity.getPercentWorkComplete()!=100){
resultingAct = activity;
}
}
}
}
}
if(isDebugEnabled){log.debug(" getResultingActivity: resultingAct "+resultingAct);}
return resultingAct;
}
/**
* check if baseline is added as subject to any of summary activity
* @param baseline
* @param ancestorsSubjectsMap
* @param activityAncestors
* @return
* @throws WTException
*/
private boolean isBaselineSubjectOfActivityAncestors(Baseline baseline, WTKeyedHashMap ancestorsSubjectsMap, WTArrayList activityAncestors) throws WTException {
Iterator itr = activityAncestors.persistableIterator();
while(itr.hasNext()){
Object obj = itr.next();
PlanActivity ancestorActivity = (PlanActivity)obj;
ArrayList list = new ArrayList();
ObjectIdentifier objectIdentifier = ancestorActivity.getPersistInfo().getObjectIdentifier();
if(ancestorsSubjectsMap.get(objectIdentifier)!=null){
list = (ArrayList) ancestorsSubjectsMap.get(objectIdentifier);
}
if(list!=null){
if(list.contains(baseline))
{
if(isDebugEnabled){log.debug(" isBaselineSubjectOfActivityAncestors:true , baseline: "+baseline+" activityAncestors:"+activityAncestors);}
return true;
}
}
}
if(isDebugEnabled){log.debug(" isBaselineSubjectOfActivityAncestors:false , baseline: "+baseline+" activityAncestors:"+activityAncestors);}
return false;
}@Override
public void updateFloatingBaselineMemberLinks(WTCollection floatingBaselineMemberList)
throws WTException, WTPropertyVetoException {
isDebugEnabled = log.isDebugEnabled();
if(isDebugEnabled){log.debug(" IN Custom handler: updateFloatingBaselineMemberLinks()");}
Iterator itr = floatingBaselineMemberList.persistableIterator();
WTArrayList subjectListCollection = new WTArrayList();
while(itr.hasNext()){
Object fbmlObj = itr.next();
if(fbmlObj instanceof FloatingBaselineMember){
FloatingBaselineMember fbml = (FloatingBaselineMember) fbmlObj;
Baseline baseline = (Baseline) fbml.getRoleAObject();
subjectListCollection.add(fbml.getMemberVersion().getObject());

} }
//Supported API
WTKeyedHashMap associatedActivities = DeliverableHelper.service.getAssociatedActivities(subjectListCollection);
if(isDebugEnabled){log.debug(" IN Custom handler: updateFloatingBaselineMemberLinks() - got the assocaited activities with baseline members");}
WTHashSet activitySet = new WTHashSet();
for(Object obj : associatedActivities.keySet()){
if(obj instanceof ObjectReference){
ObjectReference objectRef = (ObjectReference)obj;
if(associatedActivities.get(objectRef)!=null){
activitySet.addAll((Collection) associatedActivities.get(objectRef));
}
}
}
WTArrayList actCollection = new WTArrayList();
actCollection.addAll(activitySet);
updateFBMLlogic(actCollection);
if(isDebugEnabled){log.debug(" OUT Custom handler: updateFloatingBaselineMemberLinks()");}
}
}
カスタムハンドラクラスの設定
カスタムハンドラを作成した後、xconf ファイルでこれを設定します。
1. ファイル ${WT_HOME}/Windchill/codebase/com/ptc/projectmanagement/projectmanagement.service.properties.xconf に以下のエントリを追加します。
<Service context="default" name="com.ptc.projectmanagement.plannable.fbml.FBMLPlannableAttributesHandler">
<Option cardinality="singleton" requestor= "null"
selector = "<internal type name of FloatingBaselineMember>"
serviceClass="<fully qualified handler class name>"/>
</Service>
上記の例で実装されるハンドラのエントリは以下のとおりです。
<!-- Handler for Deadline and Health Status reporting for FloatingBaselineMember -->
<Service context="default" name="com.ptc.projectmanagement.plannable.fbml.FBMLPlannableAttributesHandler">
<Option cardinality="singleton" requestor= "null" selector = "wt.vc.baseline.FloatingBaselineMember"
serviceClass="com.ptc.projectmanagement.plannable.fbml.VWUpdatePlannableAttributesOnFBML"/>
</Service>
2. Windchill シェルで xconfmanager -p を実行し、メソッドサーバーを再起動します。
カスタムハンドラのガイドライン
カスタムハンドラを正確に作成するため、以下のガイドラインに従います。
適切な場所にログ作成文を必ず追加してください。
可能な場合には必ず、マルチオブジェクト API (引数として個々のオブジェクトではなくコレクションをとる API) を使用します。これにより、API の呼び出しがループになることを回避できます。
カスタムハンドラは修正後に必ずバリエーションベースラインオブジェクトを保存する必要があります。ハンドラは「期限」「進行状況」のみを修正し、バリエーションベースラインメンバーリンクのその他の属性を修正してはなりません。
バリエーションベースラインオブジェクトのコレクションを保存するには、以下のマルチオブジェクト API を使用します。
PersistenceHelper.manager.save(fbmlObjects)
単一のバリエーションベースラインオブジェクトを保存するには、以下の API を使用します。
PersistenceHelper.manager.save(fbmlObject)
バリエーションベースラインメンバーオブジェクトに対してカスタムハンドラを呼び出すスケジュールキューを実行する際の遅延をカスタマイズする場合、以下のエントリを ${WT_HOME}/Windchill/codebase/wt.properties.xconf に挿入します。
<Property name="com.ptc.projectmanagement.plannable.fbml.timeDelayForQueueExecution" default="10"/>
* 
デフォルトの遅延時間は 10 秒です。Matrix Editor から呼び出された処理は同期化されるので、Matrix Editor の処理でこのプロパティは考慮されません。
Windchill シェルで xconfmanager -p を実行し、メソッドサーバーを再起動します。
オブジェクトに対して更新操作を実行する前に、以下の API を使用してアクティビティのコレクションを更新します。
fbmlObjects = CollectionsHelper.manager.refresh(fbmlObjects)