Advanced Customization > Business Logic Customization > Windchill ProjectLink Customization > Writing Custom Plan Attributes Algorithms for Members of the Variant Baseline > Creating Custom Algorithms for Deadline and Health Status
  
Creating Custom Algorithms for Deadline and Health Status
Perform the following steps to create custom algorithms to update the Health Status and Deadline attributes of FloatingBaselineMember objects on the basis of the changes in these attributes or in the related activities:
1. Make the visibility constraint of Deadline and Health Status to read-only.
2. Author a custom handler class.
3. Configure the custom handler class.
* 
If algorithms for the Deadline and Health Status attributes are not configured properly or not present in the system, the values of these attributes are blank for variant baseline objects.
Prerequisites
Before you create custom algorithms, perform the following steps:
1. Create baseline objects.
2. Create a customize view for Baselines Objects or Variant Baselines table with the Deadline and Health Status columns. For more information, see Customizing Table Views.
3. Add Health Status and Deadline attributes for Floating Baseline Member type using Type and Attribute Management utility to view these attributes on Variant baseline member attribute pane in Matrix Editor. For more information, see Editing Attribute Layouts.
Setting the Visibility Constraint of Deadline and Health Status to Read-Only
Configure the plan attributes as read-only from the Type and Attribute Management utility using the wt.vc.baseline.FloatingBaselineMember(Floating Baseline Member) visibility constraint to restrict the manual updates through the user interface. For more information, see Viewing and Setting Attribute Visibility.
The administrator can set the visibility constraint to Read/Write in case no customization is required to calculate the values of these attributes, so that these attributes can be updated manually on the corresponding user interfaces.
However, if there is a customization in place and the administrator has set the visibility constraint to Read/Write, then after modifying the values of these attributes on the user interface, the values will be recalculated based on the configured custom handlers when invoked. Otherwise, you can remove the implemented customization to calculate the values of these attributes.
Administrator can take this decision as part of the System Setup.
Authoring a Custom Handler Class
You can use the FBMLPlannableAttributesHandler interface to create the custom algorithm. This interface is defined as follows:
updateFloatingBaselineMemberLinksFromActivity(<Collection of activity objects>);updateFloatingBaselineMemberLinks(<Collection of Floating Baseline Member link objects>);
This interface and the supported APIs can be implemented to plug in the custom algorithms. A sample custom handler in the following example can be a possible custom algorithm that calculates the Deadline and Health Status based on the Deadline and Health Status of the associated Plan Activities:
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()");}
}
}
Configuring the Custom Handler Class
After you author the custom handler, configure it in the xconf file:
1. Add the following entry in this file ${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>
Entry for the handler implemented in the above example can be:
<!-- 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. Run xconfmanager -p in Windchill shell and restart the Method Server.
Custom Handler Guidelines
The following guidelines will help ensure that you author custom handler correctly:
Always add logging statements at the appropriate locations.
Whenever possible, use multi-object APIs (APIs that take a collection as an argument rather than an individual object). This helps you avoid calling an API in a loop.
Custom Handlers should always save variant baseline objects after modification. Handlers should modify Deadline and Health Status only and should not modify any other attribute of Variant Baseline Member Link.
To save the collection of variant baseline objects, use the following multi-object API:
PersistenceHelper.manager.save(fbmlObjects)
To save single variant baseline objects, use following API:
PersistenceHelper.manager.save(fbmlObject)
Insert below entry in ${WT_HOME}/Windchill/codebase/wt.properties.xconf when you want to customize the delay for executing Schedule Queues to call custom handler for variant baseline member Objects:
<Property name="com.ptc.projectmanagement.plannable.fbml.timeDelayForQueueExecution" default="10"/>
* 
Default time delay is 10 seconds. The actions invoked from matrix editor would be in sync and this property will not be considered for the matrix editor actions.
Run xconfmanager -p in Windchill shell and restart the Method Server.
Use the following API to refresh activities collection before performing any update operation on the object:
fbmlObjects = CollectionsHelper.manager.refresh(fbmlObjects)