Monday, November 10, 2014

Quering and fetching MBOs


This entry is part of the Maximo Java Development series.

To query a Maximo table using Java you need to perform the following steps:
  1. Get a reference to the MboSet.
  2. Specify a where clause.
  3. Loop through the MboSet and fetch Mbos.
The following example shows how to retrieve all the assets located in BEDFORD site.

MboSetRemote assetSet = getMboSet("ASSET");
assetSet.setWhere("LOCATION='BEDFORD'");
MboRemote asset=null;
for(int i=0; (asset=assetSet.getMbo(i))!=null; i++)
{
    ...
}

  • The getMboSet method gets a reference the ASSET table.
  • The setWhere method specifies the SQL where clause that must be applied to filter data. If the setWhere is not invoked, the entire table will be fetched.
  • The getMbo method returns a specific element of the MboSet collection. The first invocation of getMbo method automatically fetches data and initialize the MboSet.

How to debug Maximo/TPAE


This entry is part of the Maximo Java Development series.

Debugging is a great aid in developing Maximo/TPAE customizations in Java. This powerful technique, albeit simple, is too often ignored or neglected by less experienced developers. In this tutorial I will show how to connect Eclipse to a remote Maximo server in debug mode.
Here we go.

Enable debug mode in WebSphere

First thing to do is to enable debugging of the MXServer application server in WebSphere.
Open WAS console and find the MXServer application server under 'Servers' group.


Click on the MXServer link and search for the 'Process Definition' under the 'Java and Process Management' group on the right side.


Now click on 'Java Virtual Machine' on the right and search the 'Debug Mode' checkbox. Check the 'Debug Mode' flag and take note of the debug port on the text box below (7777).


Now restart MXServer and you are ready to debug.

Attach the Eclipse debugger to MXServer

Launch Eclipse and open your Maximo Java project. I hope you have one:-)
Click on the drop-down arrow next to the Debug Menu button and select Debug Configurations.


Select 'Remote Java Application' then click on the 'Add' button on the upper-left and fill the Connection properties with the hostname of the Maximo application server and the debug port as configured in WebSphere.


Click on Debug button.
Now the Debug perspective should open automatically. If not, select Window > Open Perspective > Other > Debug. You should see something like this in the Debug view.


Now you are ready to debug. Try to put a breakpoint in your custom code and see what happens.
A mayor advantage of debugging is that you can hot-swap your code changes into the application server. All you have to do is to save the Java class and Eclipse will automatically compile and replace it in Maximo as long as you are connected.
Unfortunately this not always work and sometimes you get the following error.


In such cases, you have to stop the application server, replace the class tree and start over.

This technique, paired with the Rapid Java class deployment on WebSphere, can greatly speedup your job leaving some free time to go on Facebook :-)

Rapid Java class deployment on WebSphere




In this post I will describe how to minimize the time needed to redeploy the custom Java code in Maximo running on WebSphere during the development phase.

Note: The techniques explained in this article (except the first one) are recommended only for development environments.

Here are the four methods I know to perform this task (the last is the fastest).


Rebuild and redeploy (30 minutes)
The standard procedure of updating custom Java classes in WebSphere is VERY time consuming and consists of the following steps:
  1. Copy the custom Java classes under [SMPDIR]\maximo\applications\maximo folder.
  2. Rebuild Maximo EAR.
  3. Redeploy the EAR.

Use sync-websphere-maximo.cmd script (10 minutes)
There is a well hidden script named sync-websphere-maximo.cmd under[SMPDIR]\applications\maximo directory that takes the classes in the businessobjects, maximouiweb, mboweb, meaweb and lib directories and roughly copies them to the WASMAXIMO.ear folder.
First of all you have to configure the script editing sync-websphere-maximo.xml file and set maximo.websphere.dir property.
  1. Copy the custom Java classes under [SMPDIR]\maximo\applications\maximo folder.
  2. Run the sync-websphere-maximo.cmd script.
  3. Restart Maximo application server.

Rapid approach (6 minutes)
A fast technique that I use is to directly copy java class files in the MAXIMO.ear directory under WebSphere tree. In a typical deployment it is located under[WSDIR]\AppServer\profiles\ctgAppSrv01\installedApps\ctgCell01\MAXIMO.ear.
The little trick is to use a good file archiver tool like 7-Zip to dynamically add/replace files in the businessobjects.jar file without having to extract and rebuild it.
This is the exact sequence to follow.
  1. Stop Maximo application server (do this before modifying businessobjects.jar file to avoid corrupting it).
  2. Copy the custom Java classes into MAXIMO.ear folder.
    1. If the class files belongs to maximouiweb module I just copy them in themaximouiweb.war\WEB-INF\classes directory.
    2. If the class files belongs to businessobjects module I open the businessobjects.jar file with 7-Zip (with a right click) and drag and drop the custom class files into the right folders.
  3. Start Maximo application server.

Super-fast approach (5 minutes)
The previous technique can be further improved with a little trick (thanks Diego for the tip).
First of all you have to force Websphere to load the unpacked businessobjects.jar:
  1. Stop Maximo application server.
  2. Go in MAXIMO.ear folder and unzip the businessobjects.jar file into a directory namedbusinessobjects.jar.
  3. Rename the businessobjects.jar file (e.g. businessobjects.orig.jar)
  4. Start Maximo application server and check everything is ok.

After having performed this configuration you can copy your class files directly in the 'exploded EAR' with this procedure.
  1. Stop Maximo application server.
  2. Copy the custom Java classes into businessobjects.jar or maximouiweb.war subdirectories of MAXIMO.ear folder.
  3. Start Maximo application server.
Note that step 2 can be automated setting the Eclipse destination directory directly to businessobjects.jar directory (unchecking the 'scrub output dir' in the project options under Java - Compiler - Building). However this may worth a separate article...

Extending Maximo Business Objects

Extending Maximo Business Objects

This entry is part of the Maximo Java Development series.

Sometimes it is necessary to override or extend the functionality in Maximo. This can be achieved by extending methods in the Maximo Business Objects.
This article outlines the process involved in extending the out-of-the-box psdi.app.item.Itemclass to initialize the description field to a specific value.

Create Java Classes
We will need to create two new files an Mbo and an MboSet. The cust.psdi.app.item.Item will extend the add() method in the psdi.app.item.Item class. It is a good common practice to use a well defined 1st level package name like 'cust' to group all the custom code.
Now open Eclipse and create a new Java class named cust.psdi.app.item.Item and paste the following code.
package cust.psdi.app.item;

import java.rmi.RemoteException;
import psdi.mbo.*;
import psdi.util.*;

public class Item extends psdi.app.item.Item implements psdi.app.item.ItemRemote
{
  public Item(MboSet ms) throws MXException, RemoteException
  {
    super(ms);
  }

  public void add() throws MXException, RemoteException
  {
    setValue("description", "TEST");
    super.add();
  }
}
Note how we have overridden the add() method of the psdi.app.item.Item class to insert the initialization of the description field.
Every time you override a Maximo method remember to call the super() method to preserve any preexisting behavior.

The ItemSet class does not contain any logic. It simply returns the reference of our new object.
package cust.psdi.app.item;

import java.rmi.RemoteException;
import psdi.mbo.*;
import psdi.util.*;

public class ItemSet extends psdi.app.item.ItemSet implements psdi.app.item.ItemSetRemote
{
  public ItemSet(MboServerInterface ms) throws MXException, RemoteException
  {
    super(ms);
  }

  protected Mbo getMboInstance(MboSet ms) throws MXException, RemoteException
  {
    return new Item(ms);
  }
}

Deploy Maximo EAR
After having compiled the two Java classes (should be done automatically by Eclipse), go inbin\cust\psdi\app\item folder and copy those two .class files into[SMPDIR]\maximo\applications\maximo\businessobjects\classes\cust\psdi\app\item.
Now rebuild the Maximo EAR file, redeploy it and restart your server (or use this fast method).

Update database
Now we have to tell Maximo to use the new classes. Open the Database Configuration application, select the ITEM object and put cust.psdi.app.item.ItemSet into the Class field.


Test
Launch Maximo and open the Item Master application. When the application launches, select insert from the toolbar. You will notice that the application will insert a new record with the description field defaulting to the value 'TEST'.

Navigating object relationship in MBO code


This entry is part of the Maximo Java Development series.

When you are in an Mbo class or you already have a reference to an Mbo object is it possible to retrieve the related records using Maximo object relationships as defined in the Database Configuration application.
For example the ASSET object has a relationship called ACTIVEASSETMETER to the ASSETMETERobject. This relationship allows to find all active asset meters for the current asset. The resulting set will contain zero or more objects.


If you are in a class that extend psdi.app.asset.Asset the this object already provides a reference to the current Mbo. Use the Mbo.getMboSet(String mboSetName) method to retrieve a relatedMboSet.
The following snippet prints to the system logs the list of the active meters for the current asset. You can extend the asset Mbo and try to put this code in the init() method to test it.

MboSetRemote meterSet = this.getMboSet("ACTIVEASSETMETER");
MboRemote meter;
for(int j=0; ((meter = meterSet.getMbo(j)) != null); j++)
{
  String name = meter.getString("METERNAME");
  String desc = meter.getString("METER.DESCRIPTION");
  System.out.println(name + " - " + desc);
}

In the example I have also used the ASSETMETER.METER relationship in the getString method to directly retrieve the description of the each meter.

Note that Mbos obtained via relationship are included in the same transaction as the parent MBO set so you don't have to explicitly call the save() method.

Stop and Restart Workflow through Action Class

There are several scenarios where a Maximo application object (workorder / jobplan / pm) which is currently in a workflow needs to be stopped and restarted. I faced one such instance recently where the workflow design was modified to fix a flaw and there were several workorders which were tagged to the old workflow version.  Hence I had to stop workorders in the old workflow and restart them in the new workflow. As the count was huge, it was impossible to do it manually using the Workflow administrator to stop the workflow and restart them using the Workorder application. Hence I wrote an action class and tagged it to an escalation which will have a condition to pull all the workorders that needs to be stopped and restarted.

I am explaining this example with WORKORDER as the object which is in the old workflow. The workorder needs to be stopped from the old workflow and re-routed to the newly revised workflow

Here is what you need to do....
1. Create a Relationship to find the active WFINSTANCE records for the workorder.
  
Name: ACTIVEWFINSTANCE
Child Object: WFINSTANCE
Where Clause: ownerid=:workorderid and ownertable=’WORKORDER’ and active=1
Remarks: Gets all the Active Workorders from workorder table which has pending assignments

2. Create a new Action Class and associate it to a Custom Action.
3. Here is the action class.

package custom.escalation.action;

import java.rmi.RemoteException;
import psdi.mbo.MboRemote;
import psdi.mbo.SqlFormat;
import psdi.util.MXException;
import psdi.workflow.WFInstanceSetRemote;
import psdi.workflow.WFInstance;
import psdi.util.logging.MXLogger;
import psdi.util.logging.MXLoggerFactory;
import psdi.workflow.WFProcess;
import psdi.workflow.WFProcessSetRemote;




public class StopWorkFlow implements
psdi.common.action.ActionCustomClass
{

     private static final MXLogger log = MXLoggerFactory.getLogger("maximo");
  
    public void applyCustomAction(MboRemote mbo, Object[] arg)
    throws MXException, RemoteException
    {
    log.debug("Entered applyCustomAction of StopWorkFlow");
    WFInstanceSetRemote wfInstanceSet=(WFInstanceSetRemote) mbo.getMboSet("ACTIVEWFINSTANCE");

  
        if(!wfInstanceSet.isEmpty())
        {
            log.debug("WfInstance is not empty");
            for(int wfInstance=0; wfInstance <wfInstanceSet.count();wfInstance++)
            {
            WFInstance wfInst=(WFInstance) wfInstanceSet.getMbo(wfInstance);
            String processName = wfInst.getString("processname");
            log.debug("processName: "+ processName);          
            WFProcessSetRemote wfProcessSet = (WFProcessSetRemote) mbo.getMboSet("WFPROCESS");
            SqlFormat sqf1 = new SqlFormat(mbo.getUserInfo(), "processname = :1 and active = 1");
             sqf1.setObject(1,"WFPROCESS","PROCESSNAME", processName);
             wfProcessSet.setWhere(sqf1.format());
            int matchingProcess = wfProcessSet.count();
            log.debug("matchingProcess: " + matchingProcess);
            WFProcess wfProcess = null;
           
            if (matchingProcess==1)
            {
                wfProcess = (WFProcess)wfProcessSet.getMbo(0);
            }
          
            // Enter memo for the closed transaction
            wfInst.stopWorkflow("Workflow stopped through escalation");
            log.debug("After stopping workflow");
            // Enter memo for restarted transaction
            wfInst.initiateWorkflow("Initiated workflow through escalation",wfProcess);
            log.debug("After initiating workflow");
            wfInstanceSet.save();
          


            } //end of For
        } //End of if(!wfInstanceSet.isEmpty())
    } //End of public void applyCustomAction
} //End of stopWorkFlow


 4. The action class gets the process name that the workflow is currently in and retrieves the active version of the process. It then stops the workflow of the existing workorder and then restarts it using the latest active version of the workflow.

5. Now create an escalation and associate a condition to pick the relevant workorders which needs to be stopped and restarted.

6. Associate an action to the escalation which has to be tagged to the action class created in Step 3.

Setting up custom loggers in Maximo


Using out of box loggers to debug Maximo customizations is not a good idea as developers end up leaving the debug on which will impact the overall system performance. On creation of a custom logger, we can only turn the debug on for the custom logger and leave the other loggers in their current state.

Here is a quick and easy way to create and use custom loggers for Maximo customizations.This logger can be used only for the custom Java code that developers write in Maximo.


1.Navigate to System Configuration -> Platform Configuration -> Logging
2.Click on New Row under Root Logger
3.Enter the following:   
           Logger: Customization
           Log Level: DEBUG
           Key: log4j.logger.maximo.Customization
           Inherited Appenders: Console,Rolling
           Enable Active checkbox.
4.Save the logging properties
5.Click on Select Action -> Apply Settings



Here is a sample of how to use the custom logger in a Custom class:

/** Import the following classes to use a custom logger */
import psdi.util.logging.MXLogger;
import psdi.util.logging.MXLoggerFactory;

public class ExPO extends PO implements PORemote
{

    /** Declare the logger as an instance of MXLogger */
    MXLogger myLogger;
   

    public ExPO (MboSet mboset) throws MXException, RemoteException
    {
            super(mboset);

            /**Get the instance of the custom logger using the key maximo.Customization which is            defined in Step 3 of the logger configuration */

            myLogger = MXLoggerFactory.getLogger("maximo.Customization");
            myLogger.debug("**Inside the constructor of custom.app.po.ExPO**");          
    }

}
     

Understanding Mbos and MboSets

This entry is part of the Maximo Java Development series.

Maximo Business Objects (MBOs) are a set of Java classes that implements the data persistence layer and business rules in Maximo/TPAE infrastructure. Those Java class files are stored in the[SMPDIR]\maximo\applications\maximo\businessobjects directory and are packaged in thebusinessobjects.jar file.
Roughly speaking there are two types of objects Mbo and MboSet.
  • An Mbo represents a record in a table. It has business intelligence and persistence methods. It can be compared to a session/entity bean in J2EE.
  • An MboSet is a collection of Mbo objects. It has methods to manipulate, iterate and query data.


To access data (Mbo) you first have to access the table (MboSet). The following example shows how to retrieve all the assets located in BEDFORD site.

MboSetRemote assetSet = getMboSet("ASSET");
assetSet.setWhere("LOCATION='BEDFORD'");
MboRemote asset=null;
for(int i=0; (asset=assetSet.getMbo(i))!=null; i++)
{
    ...
}

  • The getMboSet method gets a reference the ASSET table.
  • The setWhere method specifies the SQL where clause that must be applied to filter data. If the setWhere is not invoked, the entire table will be fetched.
  • The getMbo method returns a specific element of the MboSet collection. The first invocation of getMbo method automatically fetches data and initialize the MboSet.

Now that you have an Mbo object it is possible to read field values using the getXxxx methods.

String assetnum = asset.getString("ASSETNUM");
int assetid = asset.getInt("ASSETID");

To modify the value of a field the setValue methods can be used.

asset.setValue("DESCRIPTION", "New description");

In the previous examples we have used the basic psdi.mbo.Mbo and psdi.mbo.MboSet classes. However many Maximo objects have a specific handler class that is specified in the Class field of the object definition in the Database Configuration application. For the ASSET object it ispsdi.app.asset.AssetSet and it extends psdi.mbo.MboSet class. Associated topsdi.app.asset.AssetSet there is psdi.app.asset.Asset that extends psdi.mbo.Mbo class. Those two specialized classes provides specific methods and logic to manage assets.
The following code snippet shows how to fetch an asset from Maximo database and understand if it is a rotating asset or not using the isRotating() method.

AssetSetRemote assetSet = (AssetSetRemote)getMboSet("ASSET");
assetSet.setWhere("ASSETNUM='1000'");
AssetRemote asset = (AssetRemote)assetSet.getMbo(0);
System.out.println("Is rotating: " + asset.isRotating());

You have just walked the first step in the long journey of the Maximo/TPAE developer...
For more articles look at Maximo MBO Java Development page.