高级自定义 > 业务逻辑自定义 > Windchill ProjectLink 自定义 > 为变型基线的成员编写自定义计划属性算法 > 为最后期限和健康状况创建自定义算法
  
为最后期限和健康状况创建自定义算法
执行以下步骤可创建自定义算法,以根据 FloatingBaselineMember 对象的“健康状况”“最后期限”属性或相关活动中发生的更改来更新这些属性:
1. “最后期限”“健康状况”的可见性约束设置为只读。
2. 编写自定义处理程序类。
3. 配置自定义处理程序类。
* 
如果“最后期限”“健康状况”属性的算法配置不正确或在系统中不存在,则对于变型基线对象,这些属性的值为空白。
先决条件
在创建自定义算法之前,请执行以下步骤:
1. 创建基线对象。
2. 为具有“最后期限”“健康状况”列的“基线对象”“变型基线”表格创建自定义视图。有关详细信息,请参阅自定义表格视图
3. 使用“类型和属性管理”实用程序为“浮动基线成员”类型添加“健康状况”“最后期限”属性,以便在矩阵编辑器的变型基线成员属性窗格中查看这些属性。有关详细信息,请参阅编辑属性布局
将最后期限和健康状况的可见性约束设置为只读
“类型和属性管理”实用程序中,使用 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 shell 中运行 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 秒。从矩阵编辑器中调用的操作将同步,而矩阵编辑器操作不会考虑此特性。
在 Windchill shell 中运行 xconfmanager -p,然后重新启动方法服务器。
在针对对象执行任何更新操作之前,请使用以下 API 刷新活动集合:
fbmlObjects = CollectionsHelper.manager.refresh(fbmlObjects)