Technical Blog Post
Abstract
Object Level Configurative Action and Exception Structure similar to Automation Scripts (v7.1)
Body
Maximo/Control Desk product introduced Automation Scripts instead of Java customizations on version 7.5 and enhanced the scripting functionality on version 7.6. Yet, there are many customer environments which have not been upgraded to latest versions. The following customization had been created for a customer environment which was in version 7.1.
The aim of this development is to present a generic and configurative structure to replace and prevent continuous Java customization.
1. Adding the custom business objects to hold restriction definitions
First of all, we add two custom objects (MXM_MAINRULES, MXM_SUBRULES) via Database Configuration application which will be used to store the rules for actions or exceptions. MXM_MAINRULES should be defined as a main object. Indeed only one table is enough to store the rules but considering redundancy and performance issues the rules are split into two tables. We filter the objects and common conditions with main rule table and then filter the action/exception specific conditions with sub rule table. The attribute list for the custom objects can be seen in Table 1 and Table 2 below.
Field ID | Description | Type | Length | Required? | Usage |
ACTIVE | Is Active? | YORN | 1 | Y | Field default value is 1. It is set to 0 for inactive rules. |
MAINRULE | Main Rule Identifier | UPPER | 20 | Y | Identification field for main rule. |
SEQUENCE | Main Rule Sequence | SMALLINT | 10 | Y | Ordering of main rules for execution. |
OBJECTNAME | Object Name | UPPER | 30 | Y | Same as MAXOBJECT.OBJECTNAME field. It defines the object used for the main rule. |
DESCRIPTION | Description | ALN | 100 | N | The description or comment for the main rule record. |
MXM_MAINRULESID | Unique Identifier | BIGINT | 19 | Y | System set unique identifier for the main rule record. |
MAINCONDITION | Main Condition Identifier | ALN | 12 | Y | Same as the CONDITION.CONDITIONNUM field. It defines the condition for the main rule. |
TYPE | Operation Type | ALN | 10 | Y | Type of the operation: INSERT, UPDATE, ALL. |
Table 1 - Attribute List of Main Rules Object
Field ID | Description | Type | Length | Required? | Usage |
ACTIVE | Is Active? | YORN | 1 | Y | Field default value is 1. It is set to 0 for inactive rules. |
MAINRULE | Main Rule Identifier | UPPER | 20 | Y | Identification field for main rule. |
SUBRULE | Sub Rule Identifier | UPPER | 20 | Y | Identification field for sub rule. |
SEQUENCE | Main Rule Sequence | SMALLINT | 10 | Y | Ordering of main rules for execution. |
OBJECTNAME | Object Name | UPPER | 30 | Y | Same as MAXOBJECT.OBJECTNAME field. It defines the object used for the main rule. |
DESCRIPTION | Description | ALN | 100 | N | The description or comment for the sub rule record. |
MXM_SUBRULESID | Unique Identifier | BIGINT | 19 | Y | System set unique identifier for the sub rule record. |
SUBCONDITION | Sub Condition Identifier | ALN | 12 | Y | Same as the CONDITION.CONDITIONNUM field. It defines the condition for the sub rule. |
TYPE | Operation Type | ALN | 10 | Y | Type of the operation: ACTION, ERROR. |
ACTION | Action Identifier | ALN | 30 | N | Same as the ACTION.ACTION field. It defines the action used for the sub rule. |
MSGGROUP | Message Group | ALN | 25 | N | Same as the MAXMESSAGES.MSGGROUP field. It defines the message key used for the sub rule. |
MSGKEY | Message Key | ALN | 40 | N | Same as the MAXMESSAGES.MSGKEY field. It defines the message key used for the sub rule. |
Table 2 - Attribute List of Sub Rules Object
After that we turn on Admin mode, apply Database configurations and turn Admin mode off. Then we add relationships, seen in Table 3 below, to/from our new custom objects, which will be used in the custom Java class and application to access remote objects.
Relation Name | Parent Object | Child Object | Where Clause |
MXM_MAINRULES | WORKORDER | MXM_MAINRULES | active = 1 and objectname = 'WORKORDER' |
MXM_MAINRULES_INSERT | WORKORDER | MXM_MAINRULES | active = 1 and objectname = 'WORKORDER' and type in ('INSERT', 'ALL') order by sequence asc |
MXM_MAINRULES_UPDATE | WORKORDER | MXM_MAINRULES | active = 1 and objectname = 'WORKORDER' and type in ('UPDATE', 'ALL') order by sequence asc |
MXM_SUBRULES | MXM_MAINRULES | MXM_SUBRULES | 1 = 1 |
MXM_SUBRULES_ERRORS | MXM_MAINRULES | MXM_SUBRULES | active = 1 and mainrule = :mainrule and type = 'ERROR' |
MXM_SUBRULES_ACTIONS | MXM_MAINRULES | MXM_SUBRULES | active = 1 and mainrule = :mainrule and type = 'ACTION' |
ACTION | MXM_SUBRULES | ACTION | action = :action |
Table 3 - Relationship Definitions related to Main Rules and Sub Rules
With the relations defined our custom main rule and sub rule tables are ready to use. We also need to add the proper domains to the elective fields, this part is left to the implementer. In the next steps we will implement the user interface customizations and write the custom Java classes.
NOTE: If we intend to use this structure for more objects like SR, INCIDENT, ASSET etc. then we also add the proper relations for these objects as we did for WORKORDER in Table 3.
2. Adding Custom Rules Application which will be used to view and edit rules
In this section, we will add the user interface for custom rules. Using Application Designer application, we create a single page application, i.e. customrule, for MXM_MAINRULES object under any module we want. Then, we import customrule.xml into the system which can be seen below:
<?xml version="1.0" encoding="UTF-8"?> <presentation id="customrule" ismobile="false" mboname="MXM_MAINRULES" version="6.0.0" viewport="1222x704"> <page id="mainrec" scroll="false"> <include controltoclone="single_pageHeader" id="INCLUDE-single_pageHeader"/> <clientarea id="clientarea"> <datasrc id="mxm_subrules" relationship="MXM_SUBRULES"/> <tabgroup format="carddeck" id="maintabs" style="form"> <tab default="true" id="results"> <section id="section_0"> <table id="resultsTable" label="Main Rules"> <tablebody displayrowsperpage="10" filterable="true" filterexpanded="true" id="resultsTablebody"> <tablecol filterable="false" id="results_col_details" mxevent="toggledetailstate" sortable="false" type="event"/> <tablecol dataattribute="mainrule" id="results_showlist_column1" label="Main Rule"/> <tablecol dataattribute="objectname" id="results_showlist_column2" label="Object Name" lookup="valuelist"/> <tablecol dataattribute="description" id="results_showlist_column3" label="Description"/> <tablecol dataattribute="maincondition" id="results_showlist_column4" label="Main Condition" lookup="valuelist"/> <tablecol dataattribute="type" id="results_showlist_column5" label="Type" lookup="valuelist"/> <tablecol dataattribute="sequence" id="results_showlist_column6" label="Sequence"/> <tablecol dataattribute="active" id="results_showlist_column7" label="Active?" lookup="valuelist"/> <tablecol filterable="false" id="results_col_delete" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" sortable="false" type="event"/> </tablebody> <tabledetails id="resultsTabledetails"> <section id="resultsTabledetails_tabledetails_1" label="Details"> <sectionrow id="resultsTabledetails_tabledetails_1_1"> <sectioncol id="resultsTabledetails_tabledetails_1_1_1"> <section id="resultsTabledetails_tabledetails_1_1_1_1"> <textbox dataattribute="mainrule" id="resultsTabledetails_tabledetails_1_1_1_1_1"/> <textbox dataattribute="objectname" id="resultsTabledetails_tabledetails_1_1_1_1_2" lookup="valuelist"/> <textbox dataattribute="description" id="resultsTabledetails_tabledetails_1_1_1_1_3"/> <textbox dataattribute="maincondition" id="resultsTabledetails_tabledetails_1_1_1_1_4" lookup="valuelist"/> </section> </sectioncol> <sectioncol id="resultsTabledetails_tabledetails_1_1_2"> <section id="resultsTabledetails_tabledetails_1_1_2_1"> <textbox dataattribute="type" id="resultsTabledetails_tabledetails_1_1_2_1_1"/> <textbox dataattribute="sequence" id="resultsTabledetails_tabledetails_1_1_2_1_2"/> <checkbox dataattribute="active" id="resultsTabledetails_tabledetails_1_1_2_1_3"/> </section> </sectioncol> </sectionrow> </section> </tabledetails> <buttongroup id="resultsButtongroup"> <pushbutton default="true" id="results_button_1" label="New Main Rule" mxevent="addrow"/> </buttongroup> </table> <blankline id="blank_line_1"/> <table id="resultsTable2" label="Sub Rules"> <tablebody displayrowsperpage="20" filterable="true" filterexpanded="true" id="resultsTablebody2" datasrc="mxm_subrules"> <tablecol filterable="false" id="results_col_details2" mxevent="toggledetailstate" sortable="false" type="event"/> <tablecol dataattribute="subrule" id="results_showlist2_column0" label="Sub Rule"/> <tablecol dataattribute="mainrule" id="results_showlist2_column1" label="Main Rule"/> <tablecol dataattribute="objectname" id="results_showlist2_column2" label="Object Name" lookup="valuelist"/> <tablecol dataattribute="description" id="results_showlist2_column3" label="Description"/> <tablecol dataattribute="subcondition" id="results_showlist2_column4" label="Sub Condition" lookup="valuelist"/> <tablecol dataattribute="type" id="results_showlist2_column5" label="Type" lookup="valuelist"/> <tablecol dataattribute="action" id="results_showlist2_column6" label="Action" lookup="valuelist"/> <tablecol dataattribute="msggroup" id="results_showlist2_column7" label="Message Group" lookup="valuelist"/> <tablecol dataattribute="msgkey" id="results_showlist2_column8" label="Message Key" lookup="valuelist"/> <tablecol dataattribute="sequence" id="results_showlist2_column9" label="Sequence"/> <tablecol dataattribute="active" id="results_showlist2_column10" label="Active?" lookup="valuelist"/> <tablecol filterable="false" id="results_col_delete2" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" sortable="false" type="event"/> </tablebody> <tabledetails id="resultsTabledetails2"> <section id="resultsTabledetails2_tabledetails_1" label="Details"> <sectionrow id="resultsTabledetails2_tabledetails_1_1"> <sectioncol id="resultsTabledetails2_tabledetails_1_1_1"> <section id="resultsTabledetails2_tabledetails_1_1_1_1"> <textbox dataattribute="subrule" id="resultsTabledetails2_tabledetails_1_1_1_1_0"/> <textbox dataattribute="objectname" id="resultsTabledetails2_tabledetails_1_1_1_1_1" lookup="valuelist"/> <textbox dataattribute="description" id="resultsTabledetails2_tabledetails_1_1_1_1_2"/> <textbox dataattribute="subcondition" id="resultsTabledetails2_tabledetails_1_1_1_1_3" lookup="valuelist"/> <textbox dataattribute="action" id="resultsTabledetails_tabledetails2_1_1_1_1_4" lookup="valuelist"/> <textbox dataattribute="msggroup" id="resultsTabledetails_tabledetails2_1_1_1_1_5" lookup="valuelist"/> <textbox dataattribute="msgkey" id="resultsTabledetails_tabledetails2_1_1_1_1_6" lookup="valuelist"/> </section> </sectioncol> <sectioncol id="resultsTabledetails2_tabledetails_1_1_2"> <section id="resultsTabledetails2_tabledetails_1_1_2_1" inputmode="readonly"> <textbox dataattribute="mainrule" id="resultsTabledetails2_tabledetails_1_1_2_1_1" lookup="valuelist"/> <textbox dataattribute="type" id="resultsTabledetails2_tabledetails_1_1_2_1_2" lookup="valuelist"/> <textbox dataattribute="sequence" id="resultsTabledetails2_tabledetails_1_1_2_1_3"/> <checkbox dataattribute="active" id="resultsTabledetails2_tabledetails_1_1_3_1_4"/> </section> </sectioncol> </sectionrow> </section> </tabledetails> <buttongroup id="resultsButtongroup2"> <pushbutton default="true" id="results_button2_1" label="New Sub Rule" mxevent="addrow"/> </buttongroup> </table> </section> </tab> </tabgroup> </clientarea> <include controltoclone="pageFooter" id="INCLUDE-pageFooter"/> </page> <configurationblock id="datastore_configurationblock"> </configurationblock> </presentation>
After importing customrule.xml into the system, we can edit the user interface further (change labels or give input modes etc.) using the Application Designer canvas.
3. Development of Java Classes in order to process the custom rules
The creation of custom objects and application modifications were for defining and viewing the custom rules. After that, we need to add the logic to the system which will process the conditions, actions and errors defined in main and sub rules. Here, we create a new custom class (CustomRules.java) which will be used in Maximo objects in common:
package com.custom.common; import java.rmi.RemoteException; import psdi.common.action.ActionRemote; import psdi.common.action.ActionSetRemote; import psdi.mbo.MboRemote; import psdi.mbo.MboSetRemote; import psdi.util.MXApplicationException; import psdi.util.MXException; public class CustomRules { // Method to be called within MBOs like WO, SR, etc. public static void processRules(MboRemote mbo) throws RemoteException, MXException { // if the record is new then we get all main rules except UPDATE only, otherwise all except INSERT only String mainRelation = mbo.isNew() ? "MXM_MAINRULES_INSERT" : "MXM_MAINRULES_UPDATE"; MboSetRemote mainRules = mbo.getMboSet(mainRelation); for (MboRemote mainRule = mainRules.moveFirst(); mainRule != null; mainRule = mainRules.moveNext()) { // we evaluate the condition of main rule and if it returns true we check its sub rules if(mbo.evaluateCondition(mainRule.getString("MAINCONDITION"))){ // we first check sub rules of errors MboSetRemote subErrors = mainRule.getMboSet("MXM_SUBRULES_ERRORS"); for (MboRemote subError = subErrors.moveFirst(); subError != null; subError = subErrors.moveNext()) { // we evaluate the condition of sub rule and throw error if it returns true if (mbo.evaluateCondition(subError.getString("SUBCONDITION"))) { String msgGroup = subError.getString("MSGGROUP"); String msgKey = subError.getString("MSGKEY"); throw new MXApplicationException(msgGroup, msgKey); } } subErrors.close(); // secondly we check sub rules of actions MboSetRemote subActions = mainRule.getMboSet("MXM_SUBRULES_ACTIONS"); for (MboRemote subAction = subActions.moveFirst(); subAction != null; subAction = subActions.moveNext()) { // we evaluate the condition of sub rule and run the actions if it returns true if(mbo.evaluateCondition(subAction.getString("SUBCONDITION"))){ runAction(subAction, mbo); } } subActions.close(); } } mainRules.close(); } // Method to be called in order to run Maximo actions or group actions private static void runAction(MboRemote rule, MboRemote mbo) throws MXException, RemoteException { ActionSetRemote actSet = (ActionSetRemote) rule.getMboSet("ACTION"); if (!actSet.isEmpty()) { ActionRemote action = (ActionRemote) actSet.getMbo(0); if(action != null){ action.executeAction(mbo); } } actSet.close(); } }
The new custom class should be put into maximo.ear/businessobjects.jar/com/custom/common folder. Then, we inject our logic to the MBO classes by extending MBO classes and overriding save method:
package com.custom.app.workorder; import java.rmi.RemoteException; import psdi.mbo.MboSet; import psdi.plusp.app.workorder.PlusPWO; import psdi.plusp.app.workorder.PlusPWORemote; import psdi.util.MXException; public class CustomWO extends PlusPWO implements PlusPWORemote{ public CustomWO(MboSet woSet) throws MXException, RemoteException { super(woSet); } @Override protected void save() throws MXException, RemoteException { // Method called in order to process custom rules CustomRules.processRules(this); super.save(); } }
In this example, we worked on the WORKORDER object, so we extended PlusPWO.java. We process our custom rules before saving the object, so we have overridden save method. More information on extending MBO classes can be found in this developerworks link. We create custom MBO and MBOSet classes, put them in maximo.ear/businessobjects.jar/com/custom/app/workorder folder and update the Class field for WORKORDER object from Database Configuration application. It needs application server to be restarted and Database to be configured.
All in all, this solution covers a simple, one-time development to prevent continuous customization and presents configurations for standard Maximo actions and conditions or error messages as if they are used in Workflow designer or Escalations etc.
UID
ibm11130427