Learn How to Insert Approver Information on Drawings

Requirement: Publish 3 approvers' names from Route Tasks onto a SW Drawing automatically upon Release.

With help of Brad Porter, this was accomplished by using custom code with a trigger on the policy. The contents of the package are as follows: updateEnv.mql and ecmUtil_mxJPO.java.

To deploy, run the following from an MQL window:

1. run updateEnv.mql

The Schema is as follows:

  • Attributes
    1. SW_User1_ApprovalDate
    2. SW_User1_Assignee
    3. SW_User2_ApprovalDate
    4. SW_User2_Assignee
    5. SW_User3_ApprovalDate
    6. SW_User3_Assignee
  • Triggers
    1. eService Trigger Program Parameters PolicyChangeActionStateInApprovalPromoteAction UpdateSWMappedAttributes
  • JPO
    1. ecmUtil

Configuration Details on Trigger (PolicyChangeActionStateInApprovalPromoteAction UpdateSWMappedAttributes)

  • Argument 1 – don’t change
  • Argument 2 – Target type to update when trigger fires
    1. Default: SW Drawing
    2. Or multiple: SW Drawing|ProE Drawing (separated by pipe)
  • Argument 3 – Symbolic name of Route blocking state (the state the Route was created at)
    • Default: state_InApproval
  • Argument 4-9 – TaskName|TaskAttribute=SWTargetAttribute
    1. These attributes need to match the route template task names. Default, my task names were: Checker,Engineer and Approver. So Argument 4-9 looked like this:
    2. Argument4: Checker|owner=SW_User1_Assignee
    3. Argument5: Engineer|owner=SW_User2_Assignee
    4. Argument6:Approver|owner=SW_User3_Assignee
    5. Argument7:Checker|attribute[Actual Completion Date]=SW_User1_ApprovalDate
    6. Argument8:Engineer|attribute[Actual Completion Date]=SW_User2_ApprovalDate
    7. Argument9:Approver|attribute[Actual Completion Date]=SW_User3_ApprovalDate

To finalize this behavior:

1. you will need to update the GCO for your SolidWorks installation in Matrix and update MCADInteg-MxToCADRelAttribMapping to include the following:

  • SW Drawing,SW_User1_Assignee|drawing,SW_User1_Assignee
  • SW Drawing,SW_User2_Assignee|drawing,SW_User2_Assignee
  • SW Drawing,SW_User3_Assignee|drawing,SW_User3_Assignee
  • SW Drawing,SW_User1_ApprovalDate|drawing,SW_User1_ApprovalDate
  • SW Drawing,SW_User2_ApprovalDate|drawing,SW_User2_ApprovalDate
  • SW Drawing,SW_User3_ApprovalDate|drawing,SW_User3_ApprovalDate

2. You will need to update your SW Drawing Template to take into account new attributes created above.


Two Files for reference. Create both files and place in same directory when running MQL for updateEnv.mql.

1. updateEnv.mql

# Add program ecmUtil
insert prog "ecmUtil_mxJPO.java";

# Add attribute SW_User1_ApprovalDate
add attr "SW_User1_ApprovalDate" type string default "" notmultiline resetonclone resetonrevision nothidden;
add property "attribute_SW_User1_ApprovalDate" on program "eServiceSchemaVariableMapping.tcl" to attribute "SW_User1_ApprovalDate";

# Add attribute SW_User1_Assignee
add attr "SW_User1_Assignee" type string default "" notmultiline resetonclone resetonrevision nothidden;
add property "attribute_SW_User1_Assignee" on program "eServiceSchemaVariableMapping.tcl" to attribute "SW_User1_Assignee";

# Add attribute SW_User2_ApprovalDate
add attr "SW_User2_ApprovalDate" type string default "" notmultiline resetonclone resetonrevision nothidden;
add property "attribute_SW_User2_ApprovalDate" on program "eServiceSchemaVariableMapping.tcl" to attribute "SW_User2_ApprovalDate";

# Add attribute SW_User2_Assignee
add attr "SW_User2_Assignee" type string default "" notmultiline resetonclone resetonrevision nothidden;
add property "attribute_SW_User2_Assignee" on program "eServiceSchemaVariableMapping.tcl" to attribute "SW_User2_Assignee";

# Add attribute SW_User3_ApprovalDate
add attr "SW_User3_ApprovalDate" type string default "" notmultiline resetonclone resetonrevision nothidden;
add property "attribute_SW_User3_ApprovalDate" on program "eServiceSchemaVariableMapping.tcl" to attribute "SW_User3_ApprovalDate";

# Add attribute SW_User3_Assignee
add attr "SW_User3_Assignee" type string default "" notmultiline resetonclone resetonrevision nothidden;
add property "attribute_SW_User3_Assignee" on program "eServiceSchemaVariableMapping.tcl" to attribute "SW_User3_Assignee";

# Modify type SW Drawing
mod type "SW Drawing" add attribute "SW_User1_Assignee" add attribute "SW_User2_Assignee" add attribute "SW_User3_Assignee" add attribute "SW_User1_ApprovalDate" add attribute "SW_User2_ApprovalDate" add attribute "SW_User3_ApprovalDate";

# Update Trigger
add businessobject "eService Trigger Program Parameters" "PolicyChangeActionStateInApprovalPromoteAction" "UpdateSWMappedAttributes" policy "eService Trigger Program Policy" vault "eService Administration" owner "creator" "eService Sequence Number" "99" "eService Program Name" "ecmUtil" "eService Method Name" "syncAttributesFromRouteTasks" "eService Program Argument 1" "\\\${OBJECTID}" "eService Program Argument 2" "SW Drawing" "eService Program Argument 3" "state_InApproval" "eService Program Argument 4" "Checker|owner=SW_User1_Assignee" "eService Program Argument 5" "Engineer|owner=SW_User2_Assignee" "eService Program Argument 6" "Approver|owner=SW_User3_Assignee" "eService Program Argument 7" "Checker|attribute[Actual Completion Date]=SW_User1_ApprovalDate" "eService Program Argument 8" "Engineer|attribute[Actual Completion Date]=SW_User2_ApprovalDate" "eService Program Argument 9" "Approver|attribute[Actual Completion Date]=SW_User3_ApprovalDate" "eService Error Type" "Error";
promote bus "eService Trigger Program Parameters" "PolicyChangeActionStateInApprovalPromoteAction" "UpdateSWMappedAttributes";

2. ecmUtil_mxJPO.java

import com.dassault_systemes.enovia.enterprisechangemgt.common.ChangeAction;
import com.matrixone.apps.common.Route;
import com.matrixone.apps.domain.DomainConstants;
import com.matrixone.apps.domain.DomainObject;
import com.matrixone.apps.domain.util.*;
import matrix.db.*;
import matrix.util.StringList;

import java.util.*;

public class ecmUtil_mxJPO
{
public static final String TARGET_ATTR_NAME = "target";
public static final String ROUTE_TASK_NAME = "routetaskname";
public static final String SOURCE_ATTRIBUTE = "routetaskattr";


public static final String DUMP_CHAR = "|";
public static final String RECORDSEP = "~";

public static final char[] _ch = new char[]{(char)0x7};
public static final String CTL_CHAR = new String(_ch);

/**
* Constructor.
*
* @param context the eMatrix Context object
* @param args the Java String[] object
*
* @throws Exception if the operation fails
*/
public ecmUtil_mxJPO(Context context, String[] args) throws Exception
{
}

/**
* Test to determine if the string is null or empty
*
* @param s
* @return
* @throws Exception
*/
private boolean isNullOrEmpty(String s) throws Exception
{
if (s != null && s.length() > 0)
return false;
else
return true;
}

/**
* This method is executed if a specific method is not specified.
*
* @param context the eMatrix Context object
* @param args the Java String[] object
* @return int
* @throws Exception if the operation fails
*/
public int mxMain (Context context, String[] args) throws Exception
{
try
{
return 0;
}
catch (Exception ex)
{
throw ex;
}
}

/**
* Parse the mapping string and return Map containing the following:
*
* Target Attribute Name: attribute name on target object to set
* Route Task Name: The name of the route task to retrieve the attribute
* Source Attribute: The name of the attribute to sync to target attribute name
*
* @param mapping
* @return
* @throws Exception
*/
private Map parseMapping(String mapping) throws Exception
{
Map map = new HashMap();
try
{
StringList slMappingTokens = FrameworkUtil.split(mapping, "=");
if (slMappingTokens.size() == 2)
{
String sLHMapping = ((String)slMappingTokens.get(0)).trim();
String sTargetAttribute = ((String)slMappingTokens.get(1)).trim();

if (!isNullOrEmpty(sLHMapping))
{
StringList slRouteTaskMapping = FrameworkUtil.split(sLHMapping, "|");
if (slRouteTaskMapping.size() == 2)
{
String sRouteTaskName = ((String)slRouteTaskMapping.get(0)).trim();
String sRouteTaskAttribute = ((String)slRouteTaskMapping.get(1)).trim();

if (!isNullOrEmpty(sRouteTaskAttribute) && !isNullOrEmpty(sRouteTaskName))
{
map.put(TARGET_ATTR_NAME, sTargetAttribute);
map.put(SOURCE_ATTRIBUTE, sRouteTaskAttribute);
map.put(ROUTE_TASK_NAME, sRouteTaskName);
}
}
else
{
map.put(TARGET_ATTR_NAME, sTargetAttribute);
map.put(SOURCE_ATTRIBUTE, sLHMapping);
}
}
}

return map;
}
catch (Exception ex)
{
throw ex;
}
}

/**
* Set each attr defined in attribute mapping from the route task list on the object id passed in
*
* @param context
* @param doObj
* @param mlRouteTasks
* @param mlAttrMap
* @throws Exception
*/
private void setMappedAttrsOnObject(Context context, DomainObject doObj, MapList mlRouteTasks, MapList mlAttrMap) throws Exception
{
try
{
// Track update map for object
Map attrUpdateMap = new HashMap();

// Process each mapping entry
Iterator attrItr = mlAttrMap.iterator();
while (attrItr.hasNext())
{
Map attrMap = (Map)attrItr.next();
String sRouteTaskTargetAttr = (String)attrMap.get(SOURCE_ATTRIBUTE);
String sRouteTaskTargetName = (String)attrMap.get(ROUTE_TASK_NAME);
String sTargetAttrName = (String)attrMap.get(TARGET_ATTR_NAME);

// Search route task list for task name that matches mapping
Iterator routeTaskItr = mlRouteTasks.iterator();
while (routeTaskItr.hasNext())
{
Map routeMap = (Map)routeTaskItr.next();

// Get task info
String sRouteTaskName = (String)routeMap.get("attribute[Title]");
String sRouteTaskAttrValue = (String)routeMap.get(sRouteTaskTargetAttr);

// if task name matches mapping and attribute is not blank
if (sRouteTaskName.equalsIgnoreCase(sRouteTaskTargetName) && !isNullOrEmpty(sRouteTaskAttrValue))
{
// if the current attr select is a user then convert to first/last name
if (sRouteTaskTargetAttr.contains("owner") || sRouteTaskTargetAttr.contains("Originator"))
{
try
{
StringList slPersonSelects = new StringList();
slPersonSelects.addElement(DomainConstants.SELECT_ATTRIBUTE_FIRSTNAME);
slPersonSelects.addElement(DomainConstants.SELECT_ATTRIBUTE_LASTNAME);
com.matrixone.apps.common.Person person = com.matrixone.apps.common.Person.getPerson(context, sRouteTaskAttrValue);
Map personMap = person.getInfo(context, slPersonSelects);
String sPersonFullName = personMap.get(DomainConstants.SELECT_ATTRIBUTE_FIRSTNAME) + " " + personMap.get(DomainConstants.SELECT_ATTRIBUTE_LASTNAME);
if (!isNullOrEmpty(sPersonFullName))
{
sRouteTaskAttrValue = sPersonFullName;
}
}
catch (Exception personEx)
{
}
}
attrUpdateMap.put(sTargetAttrName, sRouteTaskAttrValue);
break;
}
}
}

// If attributes have been added to the map then update object
if (attrUpdateMap.size() > 0)
{
doObj.setAttributeValues(context, attrUpdateMap);
}

}
catch (Exception ex)
{
throw ex;
}
}

/**
* Set each attr defined in attribute mapping from the route task list on the object id passed in
*
* @param context
* @param doObj
* @param objectMap
* @param mlAttrMap
* @throws Exception
*/
private void setMappedAttrsOnObject(Context context, DomainObject doObj, Map objectMap, MapList mlAttrMap) throws Exception
{
try
{
// Track update map for object
Map attrUpdateMap = new HashMap();

// Process each mapping entry
Iterator attrItr = mlAttrMap.iterator();
while (attrItr.hasNext())
{
Map attrMap = (Map)attrItr.next();
String sSourceAttr = (String)attrMap.get(SOURCE_ATTRIBUTE);
String sTargetAttrName = (String)attrMap.get(TARGET_ATTR_NAME);

String sObjAttrValue = (String)objectMap.get(sSourceAttr);

if (!isNullOrEmpty(sObjAttrValue))
{
// if the current attr select is a user then convert to first/last name
if (sSourceAttr.contains("owner") || sSourceAttr.contains("Originator"))
{
try
{
StringList slPersonSelects = new StringList();
slPersonSelects.addElement(DomainConstants.SELECT_ATTRIBUTE_FIRSTNAME);
slPersonSelects.addElement(DomainConstants.SELECT_ATTRIBUTE_LASTNAME);
com.matrixone.apps.common.Person person = com.matrixone.apps.common.Person.getPerson(context, sObjAttrValue);
Map personMap = person.getInfo(context, slPersonSelects);
String sPersonFullName = personMap.get(DomainConstants.SELECT_ATTRIBUTE_FIRSTNAME) + " " + personMap.get(DomainConstants.SELECT_ATTRIBUTE_LASTNAME);
if (!isNullOrEmpty(sPersonFullName))
{
sObjAttrValue = sPersonFullName;
}
}
catch (Exception personEx)
{
}
}
attrUpdateMap.put(sTargetAttrName, sObjAttrValue);
}
}

// If attributes have been added to the map then update object
if (attrUpdateMap.size() > 0)
{
doObj.setAttributeValues(context, attrUpdateMap);
}

}
catch (Exception ex)
{
throw ex;
}
}

/**
* Sync attributes from route tasks connected to CA object.
*
* Trigger parameters are mapped as follows:
*
* args[0] - objectid (of CA)
* args[1] - type (target type connected to CA to sync)
* args[2] - symbolic name of CA state containing target route
* args[3..10] - attributemap
*
* attributemap format: Route Task Name|Attribute(on task)=Attribute(on target type)
*
* Example: Checker|Originator=Checker_Name (this would copy the approver name from the Route Task named
* “Checker” to the attribute “Checker_Name” on the SW Drawing object)
* Checker|Actual Completion Date=Checker_CompletionDate (this would copy the approved date from
* the Route Task named “Checker” to the attribute “Checker_CompletionDate”
* on the SW Drawing object)
*
* @param context
* @param args
* @return
* @throws Exception
*/
public int syncAttributesFromRouteTasks(Context context, String[] args) throws Exception
{
StringList slBusSelects = new StringList();
slBusSelects.addElement(DomainConstants.SELECT_ID);
slBusSelects.addElement(DomainConstants.SELECT_TYPE);
slBusSelects.addElement(DomainConstants.SELECT_NAME);
slBusSelects.addElement(DomainConstants.SELECT_REVISION);

StringList slRelSelects = new StringList();
slRelSelects.addElement(DomainConstants.SELECT_ATTRIBUTE_QUANTITY);

DomainObject doRealizedChange = DomainObject.newInstance(context);
DomainObject doProposedChange = DomainObject.newInstance(context);

try
{
// Push context to avoid permissions issues.
ContextUtil.pushContext(context);

String objectId = args[0];
String targetType = args[1];
String targetState = args[2];

// Convert list of target types to StringList
StringList slTargetTypes = FrameworkUtil.split(targetType, "|");

// Initialize the CA object and get any realized changes
ChangeAction doCA = new ChangeAction(objectId);
MapList mlRealizedChanges = doCA.getRealizedChanges(context);
MapList mlProposedChanges = doCA.getAffectedItems(context);

// Consolidate list of Objects that need to get updated.
// If same TN exists in both proposed/realized list then use the realized revision
// Need to do this since on initial release the "Realized Change" is not created until
// after the CA is completed.
MapList mlConsolidatedItems = new MapList();
Iterator itemItr = mlProposedChanges.iterator();
while (itemItr.hasNext())
{
Map itemMap = (Map)itemItr.next();
String id = (String)itemMap.get(DomainConstants.SELECT_ID);
doProposedChange.setId(id);

Map proposeMap = doProposedChange.getInfo(context, slBusSelects);

String type = (String)proposeMap.get(DomainConstants.SELECT_TYPE);
String name = (String)proposeMap.get(DomainConstants.SELECT_NAME);
String revision = (String)proposeMap.get(DomainConstants.SELECT_REVISION);

// Search realized items for same TN and different R
boolean foundRealizedChange = false;
Iterator realItr = mlRealizedChanges.iterator();
while (realItr.hasNext())
{
Map realizedMap = (Map)realItr.next();
String realId = (String)realizedMap.get(DomainConstants.SELECT_ID);
doRealizedChange.setId(realId);

Map realMap = doRealizedChange.getInfo(context, slBusSelects);

String realType = (String)realMap.get(DomainConstants.SELECT_TYPE);
String realName = (String)realMap.get(DomainConstants.SELECT_NAME);
String realRev = (String)realMap.get(DomainConstants.SELECT_REVISION);

if (type.equals(realType) && name.equals(realName) && !revision.equals(realRev))
{
mlConsolidatedItems.add(realizedMap);
foundRealizedChange = true;
break;
}
}

if (!foundRealizedChange)
{
mlConsolidatedItems.add(itemMap);
}
}

// Get any attribute mappings. These are stored in each program parameter on the trigger
MapList mlAttributeMappings = new MapList();
for (int i = 3; i < args.length; i++)
{
String sMapping = args[i];
if (!isNullOrEmpty(sMapping))
{
mlAttributeMappings.add(parseMapping(sMapping));
}
}

// Only need to process if realized changes found
if (mlConsolidatedItems.size() > 0)
{
// Get the completed route on the CA for the target state
MapList mlRoutes = doCA.getRelatedObjects(context,
DomainConstants.RELATIONSHIP_OBJECT_ROUTE,
DomainConstants.QUERY_WILDCARD,
slBusSelects,
slRelSelects,
true,
true,
(short)1,
DomainConstants.EMPTY_STRING,
"attribute["+DomainConstants.ATTRIBUTE_ROUTE_BASE_STATE+"] == '"+targetState+"'");

if (mlRoutes.size() > 0)
{
// Should only ever be one route.
Map routeMap = (Map)mlRoutes.get(0);
String sRouteId = (String)routeMap.get(DomainConstants.SELECT_ID);

StringList slRouteTaskSelects = new StringList();

// Add select to for Route Task Name
slRouteTaskSelects.addElement("attribute[Title]");

// Add any additional selects specified in attribute mappings
Iterator routeSelectItr = mlAttributeMappings.iterator();
while (routeSelectItr.hasNext())
{
Map routeSelectMap = (Map)routeSelectItr.next();
String sRouteTaskAttr = (String)routeSelectMap.get(SOURCE_ATTRIBUTE);
if (!isNullOrEmpty(sRouteTaskAttr))
{
slRouteTaskSelects.addElement(sRouteTaskAttr);
}
}

// Get all the Inbox Task objects connected to Route
Route route = new Route(sRouteId);
MapList mlRouteTaskList = route.getRouteTasks(context,
slRouteTaskSelects, // bus selects
slRelSelects, // rel selects
DomainConstants.EMPTY_STRING, // where clause
false); // use cache

Iterator realItr = mlConsolidatedItems.iterator();
while (realItr.hasNext())
{
Map realizedMap = (Map)realItr.next();
String realId = (String)realizedMap.get(DomainConstants.SELECT_ID);
doRealizedChange.setId(realId);

Map infoMap = doRealizedChange.getInfo(context, slBusSelects);
String sType = (String)infoMap.get(DomainConstants.SELECT_TYPE);

// Only process those objects that match the target type
if (slTargetTypes.contains(sType))
{
// Update object with mapped attributes
setMappedAttrsOnObject(context, doRealizedChange, mlRouteTaskList, mlAttributeMappings);
}
}
}
}
return 0;
}
catch (Exception ex)
{
ex.printStackTrace();
throw ex;
}
finally
{
ContextUtil.popContext(context);
}
}

/**
*
* Sync attributes relative to current object to defined mapped attributes
*
* Trigger parameters are mapped as follows:
*
* args[0] - objectid (of object)
* args[1] - target type (object types to update)
* args[1..10] - attributemap
*
* attributemap format: Attribute(on object)=Attribute(on object)
*
* Example: to[Associated Drawing].from.owner=Drawer_Name (this would copy the owner name from related drawing
* to the attribute “Drawer_Name” on the SW Drawing object)
* @param context
* @param args
* @return
* @throws Exception
*/
public int syncAttributesFromObject(Context context, String[] args) throws Exception
{
StringList slBusSelects = new StringList();
slBusSelects.addElement(DomainConstants.SELECT_ID);
slBusSelects.addElement(DomainConstants.SELECT_TYPE);
slBusSelects.addElement(DomainConstants.SELECT_NAME);
slBusSelects.addElement(DomainConstants.SELECT_REVISION);

StringList slRelSelects = new StringList();
slRelSelects.addElement(DomainConstants.SELECT_ATTRIBUTE_QUANTITY);

DomainObject doObj = DomainObject.newInstance(context);

try
{
// Push context to avoid permissions issues.
ContextUtil.pushContext(context);

String objectId = args[0];
String targetType = args[1];
doObj.setId(objectId);

// Only process objects of target type
if (doObj.isKindOf(context, targetType))
{

// Get any attribute mappings. These are stored in each program parameter on the trigger
MapList mlAttributeMappings = new MapList();
for (int i = 2; i < args.length; i++)
{
String sMapping = args[i];
if (!isNullOrEmpty(sMapping))
{
mlAttributeMappings.add(parseMapping(sMapping));
}
}

// Add any additional selects specified in attribute mappings
Iterator selectItr = mlAttributeMappings.iterator();
while (selectItr.hasNext())
{
Map routeSelectMap = (Map)selectItr.next();
String sObjectAttr = (String)routeSelectMap.get(SOURCE_ATTRIBUTE);
if (!isNullOrEmpty(sObjectAttr))
{
slBusSelects.addElement(sObjectAttr);
}
}

// Get the attributes from object
Map attrMap = doObj.getInfo(context, slBusSelects);

// Update object with mapped attributes
setMappedAttrsOnObject(context, doObj, attrMap, mlAttributeMappings);
}


return 0;
}
catch (Exception ex)
{
ex.printStackTrace();
throw ex;
}
finally
{
ContextUtil.popContext(context);
}
}
}