May 26, 2013

How to rename objects

You have probably noticed that once you have saved an object you are not allowed to change it's ID. This is basically because each object stored on the database needs an immutable unique identifier that can be used to reference that object from other tables. Maximo prevents updating such identifiers to avoid loosing links between records referencing each other.

However there is a little trick you can apply to update the ID of an object in the main table and all the related records. The technique consists in using the Data Dictionary stored in MAXATTRIBUTE table to find all the possible tables and fields where references to the renamed object could be stored.

Let's pretend we have to rename an object stored in the [TABLE] table and whose ID is stored in attribute [IDATTR]. The following SQL query will generate a set of update statements to update both the main record and all its potential references.

SELECT 'update ' || a.objectname ||
       ' set ' || a.attributename || '=''[NEWVALUE]''' ||
       ' where ' || a.attributename || '=''[OLDVALUE]'';'
FROM maxattribute a
JOIN maxobject o ON o.objectname=a.objectname
WHERE a.persistent=1 AND o.isview=0
  AND ((a.sameasobject='[TABLE]' AND a.sameasattribute='[IDATTR]') OR
       (a.objectname='[TABLE]' AND a.attributename='[IDATTR]'))
ORDER BY a.objectname, a.attributename;


Example

For example, if we need to rename a security group called 'MAINGRP1' to 'NEWGROUP' you will need to run the following query.

SELECT 'update ' || a.objectname ||
       ' set ' || a.attributename || '=''NEWGROUP''' ||
       ' where ' || a.attributename || '=''MAINGRP1'';'
FROM maxattribute a
JOIN maxobject o ON o.objectname=a.objectname
WHERE a.persistent=1 AND o.isview=0
  AND ((a.sameasobject='MAXGROUP' AND a.sameasattribute='GROUPNAME') OR
       (a.objectname='MAXGROUP' AND a.attributename='GROUPNAME'))
ORDER BY a.objectname, a.attributename;

The result of the select will be something similar to this.

update APPLICATIONAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update COLLECTIONAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update CTRLGROUP set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update GLAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update GROUPUSER set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update GRPREASSIGNAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update LABORAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update LIMITTOLERANCE set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update LOCAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update MAXGROUP set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update PMSCCATSEC set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update REPORTAPPAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update REPORTAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update SECURITYRESTRICT set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';
update SITEAUTH set GROUPNAME='NEWGROUP' where GROUPNAME='MAINGRP1';

Now run this set of update statements in a single transaction against your database and you are done.


Important notes

  1. It is better to execute the update statements after having stopped the Maximo application server.
  2. Store the update statements in a text file you can revert your changes back if something goes wrong.
  3. This is not a supported procedure so you must be very careful when using it. Test it in a dev/test environment before using it in production.
  4. Backup your database first.


May 24, 2013

Find Roles with assignments in a Workflow

Roles in Maximo are used to represent a specific person or a group of people that can be used in workflows, communication templates and escalations.

If you need to find which roles are used in a specific workflow you can use an SQL query like this.

SELECT wfnode.title, wfnode.description, wfa.roleid, r.description
FROM wfnode
JOIN wfassignment wfa ON wfa.nodeid=wfnode.nodeid AND wfa.processrev=wfnode.processrev AND wfa.assigncode IS NULL
JOIN maxrole r ON r.maxrole=wfa.roleid
WHERE wfnode.processname='[PNAME]' AND wfnode.processrev=[PREV] AND wfnode.nodetype='WFTASK'
ORDER BY wfnode.title;

Just replace the [PNAME] and [PVER] tags with the correct process name and revision.

May 19, 2013

Link Parent-Child records in Maximo

In a previous post I have described how to correctly manage a child table in an application using the Application Designer. However, there are better ways of achieving the same goals so I will analyze all the available options to correctly link records in a parent-child relationship.


If you are a creating custom objects and applications in TPAE you may need at some point to create a child table like the Subassemblies in the Assets application.


Let's pretend we have a parent table called TB1 and a child table called TB2.
  • TB1: Parent table
    • TB1ID: Identifier of records in TB1
    • ...
  • TB2: Child table
    • TB2ID: Identifier of records in TB2
    • TB1ID: Reference to the parent record in TB1 table
    • ...
The fundamental concept is to have a field in the child table that points to a specific row in the parent table. In our example such field is TB2.TB1ID.

In order to have the child table to behave correctly is to 'link' in some way the child rows to the parent object. If not doing this the child records will 'disappear' as soon as you save the record.

The known techniques for linking parent-child records are described in the following table.

Technique Pros Cons
Using Application Designer Very simpleNot working with MIF
Setting defaults in APPFIELDDEFAULTS table Quite simpleDo not manage deletes
Using Java Mbo Works perfectlyRequires Java customization
Using scriptingQuite simpleNone (BTW the example does not manage deletes)
Using Database Configuration Built in feature Do not manage deletes
Cannot be used once the tables are created


Parent-Child database relationship

Not all the techniques described hereafter requires this configuration but I think it is important to define a relationship from the TB1 table to the TB2 records. In Database Configuration define the following relationship in object TB1:
  • Relationship: TB2
  • Child Object: TB2
  • Where Clause: TB1ID=:TB1ID

Using Application Designer

In the Application Designer you have to use the TB2 relationship defined before to link the child table.
The last important step is to initialize the TB2.TB1ID field on child records. To achieve this, add a Default Value control with the following configuration:
  • Attribute: TB1ID
  • From Data Source ID: results_showlist
  • From Attribute: TB1ID
This will set the 'link' between parent and child tables.


Using APPFIELDDEFAULTS

Another possibility is to default the key values of your child table using the APPFIELDDEFAULTS table.
The following INSERT statement will put a default value in TB2.TB1ID field to link the child records to the parent one.

INSERT INTO APPFIELDDEFAULTS
(APP, DEFAULTVALUE, OBJECTNAME, ATTRIBUTENAME, APPFIELDDEFAULTSID)
('[APPNAME]', ':OWNEROBJECT.TB1ID', 'TB2', 'TB1ID', APPFIELDDEFAULTSSEQ.NEXTVAL );


Using Java

An alternative method is to initialize this link in the child Mbo using Java. Here is how the child Mbo class should look like.

public class Tb2Mbo extends Mbo implements MboRemote
{
  public Tb2Mbo(MboSet ms) throws MXException, RemoteException
  {
    super(ms);
  }

  public void add() throws MXException, RemoteException
  {
    super.add();

    MboRemote ownerMbo = getOwner();
    if(ownerMbo != null)
    {
      // retrieves the TB1ID value from the parent Mbo
      String tb1id = ownerMbo.getString("TB1ID");
      // sets the TB1ID value in the child Mbo
      setValue("TB1ID", tb1id, NOACCESSCHECK|NOVALIDATION_AND_NOACTION);
    }
  }
}

To correctly manage deletes of child records you should also override the delete method of the Tb1Mbo class.

public void delete(long accessModifier) throws MXException, RemoteException
{
  super.delete(accessModifier);

  (((Mbo)this).getMboSet("TB2")).deleteAll();
}

This method has only one limitation (as far as I know). If the primary key of the parent table is updated before saving the record, it will create zombie child records. This is because the changes of TB1.TB1ID fieald are not propagated to child records. This is also a problem when duplicating objects.
To solve this problem you can implement the action() method of the TB1 field class or setting the primary key of the parent table as described in the Using Database Configuration method.


Using Scripting

Adapted from John's comment (thank you)

Create a table and include attributes for: {OWNERTABLE, OWNERID}
Create a script with an Object Launch Point. The launch point needs to be set to fire on Initiate only.

from psdi.mbo import MboConstants
mbo.setValue('TB1ID', mbo.getOwner().getString("TB1ID"), MboConstants.NOACCESSCHECK))


Using Database Configuration

The last technique was suggested by Scott Dickerson. I haven't tested it but it should work.

All you have to do is make sure your parent and child records have the same attribute names, and a unique primary index defined on the child table that's column order matches the unique primary index on the parent record.

Make sure that the field names in the child table match the same field names from the key columns in the parent table. For instance, your child table MYASSETCHILD's field names must exactly match the field names of the parent table, in your case ASSETNUM,SITEID. The MYASSETCHILD field will also need it's own unique key field, let's say MYASSETCHILDID.
So the unique key of the MYASSETCHILD table is ASSETNUM,SITEID,MYASSETCHILDID right?
Now if you set the primarykeycolseq field in the maxattribute table in this order
  1. MYASSETCHILD:ASSETNUM: 1
  2. MYASSETCHILD:SITEID:2
  3. MYASSETCHILD:MYASSETCHILDID:3
(you can set the primarykeycolseq by creating a unique index on these 3 columns and flagging it as the primary index for that table).
As long as the order of the primarykeys in your child table match the order of the primary keys in the parent table, the TPAE framework will automatically set these child attributes whenever new child MBOs are added underneath the parent.

May 13, 2013

Java Logging and Tracing in Maximo

This entry is part of the Maximo Java Development series.

A good logging (tracing) is always a lifesaver when you have problems in a production environment. I will never stop telling to my fellow programmers how much is important to fill code with meaningful log calls.

Maximo has a good and flexible logging subsystem. This IBM TechNote describes in detail how logging works in Maximo. Let's now see hot to use Maximo logging in your custom Java code.

To be able to write entries to the standard Maximo log file you first have to get an instance of psdi.util.logging.MXLogger class. The most common way to achieve this is to use the MXLogger.getLogger(key) method.

The following example describes a typical use of this technique and how to write a log entry in Maximo logs.

public class MyClass extends Mbo implements MboRemote
{
  private MXLogger log = MXLoggerFactory.getLogger("maximo.service.WORKORDER");

  ...

  public void sampleMethod()
  {
    log.info("Log this to INFO level");
    ...
    log.debug("Log this to DEBUG level");
  }
}

As you can see, the log attribute can be initialized in the declaration and used in any method.

If you are extending an Mbo, you can retrieve a standard logger calling the psdi.mbo.Mbo.getMboLogger() method. The name of the returned logger be
maximo.service.[service name].[business object name].

public class MyClass extends Mbo implements MboRemote
{
  ...

  public void sampleMethod()
  {
    getMboLogger().info("This is a log");
  }
}


References

Logging in Maximo
Understand Maximo 7.5 logging (IBM Education Assistant)
Understand Maximo 7.5 logging (PDF)

May 8, 2013

TPAE Java coding standard

This entry is part of the Maximo Java Development series.

What is a coding standard and why it is important?
A coding standard is a set of guidelines, rules and regulations on how to write code which will help developers quickly read and understand source code conforming to the style as well as helping to avoid introducing faults and misunderstanding.
Coding standards are important because they provide greater consistency and uniformity in writing code between programmers. This ultimately leads to the code that is easier to understand and maintain which reduces the overall cost of the project.

In this article I will go through some best practices that should be adopted when writing Java code within the TPAE framework.


Java Packages

Package names should be single lowercase words and must follow the existing format.
All packages containing custom classes must start with a common package that is unique to that TPAE instance. I typically use 'cust' as a general prefix or a short name (no more than 6 letters) of the customer.


For example, if you are extending the psdi.app.workorder.WO class, your custom class you should be cust.psdi.app.workorder.WO

The positive effect of this is that you can easily find all the custom code in your classes tree.
Here are some common examples of packages:

Java class type Package Directory
Business Objects (MBOs) cust.app.xxx [SPMDIR]\applications\maximo\businessobjects\classes\cust\app
Interface processing cust.iface [SPMDIR]\applications\maximo\businessobjects\classes\cust\iface
Maximo UI AppBeans and DataBeans cust.webclient.beans [SPMDIR]\applications\maximo\maximouiweb\webmodule\WEBINF\classes\cust\webclient\beans


Class Names

Classes should use natural descriptive names, begin with a capital, and have mixed case.
If you extend an existing Mbo you can use the same name as the base class.

package cust.app.asset;

public class Asset extends psdi.app.asset.Asset implements psdi.app.asset.AssetRemote
{
  ...
}


Alternative standard

Always prefix the class name with Cust whether creating a new or extending an existing one.

package cust.app.asset;

public class CustAsset extends psdi.app.asset.Asset implements psdi.app.asset.AssetRemote
{
  ...
}

Examples
  • CustMyTable
  • CustMyTableSet
  • CustMyTableRemote
  • CustFldMyTableMyField
NOTE: I personally prefer the other naming standard (without the Cust prefix) because the custom classes are already clearly separated from the standard ones using the package naming convention described above.


Code formatting

You have many 'religions' here. The most important thing is to be consistent.
Here is a sample of my preferred code formatting rules.

public void add() throws MXException, RemoteException
{
  super.add();

  MboRemote ownerMbo = getOwner();
  if (ownerMbo != null)
  {
    setValue("CREWCALNUM", "newval", NOACCESSCHECK|NOVALIDATION_AND_NOACTION);
  }
}

Don't criticize me I know it is not the 'standard' one  :-)


Logging

Do not use SystemOut! Use the TPAE logging subsystem instead. Logger messages should have a well descriptive debug message.

MXLogger mxLogger = MXLoggerFactory.getLogger("maximo.application");
...
mxLogger.debug("This will be loged in SystemOut log file");


Comments

Class header
Insert a comment like this at the very beginning of all Java classes.

/*
 * Any copyright disclaimer
 *
 * Revision History
 * Change Date  Changed By        Request#      Comment
 * ----------------------------------------------------------------------------
 * YYYY-MM-DD   Bruno Portaluri   Defect 1234   Performance improvements
 *
 */


Class JavaDoc
Use JavaDocs comments and tags.

/**
* My version of Asset class.
*
* @author Bruno Portaluri
*/
public class Asset extends psdi.app.asset.Asset implements psdi.app.asset.AssetRemote
{
  ...
}


Method JavaDoc

/**
* Delete object and its childs.
*
* @param accessModifier Flag to bypass normal security checks.
* @return None
* @see #someOtherMethod
*/
public void delete(long accessModifier) throws MXException, RemoteException
{
  ...
}


Constants

Use constants in the psdi.mbo.MboConstants interface.
Since many base classes like psdi.mbo.Mbo and psdi.mbo.MboSet implements this interface, you can use this constantants without prefixing them with MboConstant.

The most important constants are listed below. Look at the complete list here.

  • DISCARDABLE - Bit for discardable mbos
  • NOACCESSCHECK - Suppress access control checks
  • NOACTION - Suppress action of a field
  • NOVALIDATION - Suppress validation of a field
  • REQUIRED - Set the field required
Example

setFieldFlag("siteid",READONLY, true);

Few other important constants are defined in psdi.webclient.system.beans.WebClientBean class.
  • EVENT_STOP_ALL - stops the current event and all other events in the EventQueue from being handled
  • EVENT_HANDLED - stops all other controls from handling the event
  • EVENT_CONTINUE - allows event to be handled by other controls


Annotations

@Override

Use it every time you override a method for two benefits.
  • Take advantage of the compiler checking to make sure you actually are overriding a method when you think you are. This way, if you make a common mistake of misspelling a method name or not correctly matching the parameters, you will be warned that you method does not actually override as you think it does.
  • Makes your code easier to understand because it is more obvious when methods are overwritten.

@Deprecated

If you want to remove an obsolete method that is called multiple times you could use the @deprecated annotation instead of deleting it. When you deprecate a method you should always explain in the annotation what is the new method to be called.


May 6, 2013

Sync data between Maximo and Excel

This article is outdated! Checkout MxLoader tool.

This is the last step toward a complete integration between Maximo and Excel. In the previous article we have learned how to use Integration Object Service and HTTP calls from Excel to populate a spreadsheet with data retrieved from a Maximo server.
In this new article I will show how to synchronize (create and update) data between a Maximo and Excel. First of all you have to complete all the configuration steps described in the previous article.

In the sample Excel spreadsheet you just have to click on the second sheet called "Sync" and click on the "Query" button. The spreadsheet will be automatically filled with all the people whose name starts with the letter A.
Now try to change one of the names listed and click on the "Sync" button. In my example I have just changed the display name of Allan Ball.



Now go to Maximo and you will see the updated entries.
You can also try to insert new rows and click on the "Sync" button to create new entries in the PERSON table.

May 2, 2013

Child table in Application Designer

This article is obsolete. Please refer to this one.

If you are a creating custom objects and applications in TPAE you may need at some point to create a child table like the Subassemblies in the Assets application.


Let's pretend we have a parent table called TB1 and a child table called TB2.
  • TB1: Parent table
    • TB1ID: Identifier of records in TB1
    • ...
  • TB2: Child table
    • TB2ID: Identifier of records in TB2
    • TB1ID: Reference to the parent record in TB1 table
    • ...
The fundamental concept is to have a field in the child table that points to a specific row in the parent table. In our example such field is TB2.TB1ID.

In order to have the child table to behave correctly is to 'link' in some way the child rows to the parent object. If not doing this the child records will disappear as soon as you save the record.

Basic method using Database Configuration and Application Designer

First of all you need a relationship from the TB1 table to the TB2 records. In Database Configuration define the following relationship in object TB1:
  • Relationship: TB2
  • Child Object: TB2
  • Where Clause: TB1ID=:TB1ID
In the Application Designer you have to use this relationship in the child table.
The last important step is to initialize the TB2.TB1ID field on child records. To achieve this, add a Default Value control with the following configuration:
  • Attribute: TB1ID
  • From Data Source ID: results_showlist
  • From Attribute: TB1ID
This will set the 'link' between parent and child tables.

Alternative method using Java

An alternative method is to initialize this link in the child Mbo using Java. Here is how the child Mbo class should look like.

public class Tb2Mbo extends Mbo implements MboRemote
{
  public Tb2Mbo(MboSet ms) throws MXException, RemoteException
  {
    super(ms);
  }

  public void add() throws MXException, RemoteException
  {
    super.add();

    MboRemote ownerMbo = getOwner();
    if(ownerMbo != null)
    {
      String tb1id = ownerMbo.getString("tb1id");
      setValue("tb1id", tb1id, NOACCESSCHECK|NOVALIDATION_AND_NOACTION);
    }
  }
}

May 1, 2013

Add a language pack to Maximo

Adding a language pack to Maximo after having installed it can be done by running the installer again. After few dialogs and some checks you will be prompted with a list of languages that can be added to the base language.
Alternatively you can follow the manual procedure described hereafter.

First of all you have to install the language pack using the Process Solution Installer.
  1. On the administrative workstation, launch the Process Solution Installer (PSI): Click Start > Programs > IBM Tivoli base services > Process Solution Installer.
  2. On the Choose PSI Package panel pick the desired language pack in [SMPDIR]\pmp directory. For example, to install italian language pack I have selected D:\IBM\SMP\pmp\PAE_Lang_Pkg_7.5.0.2_It.zip file.
  3. Provide the required middleware credentials and start the installation.
Now you can add the language for use with the product using the TDToolkit. Open a command line and move to [SMPDIR]\maximo\tools\maximo directory. Launch the following command specifying the correct locale you want to install.

TDToolkit.bat -ADDLANGlocale -maxmessfix

You may need to restart the Maximo server.

References

Manually deploying languages after database update deferral
Enabling multi-language support - a simplified set of instructions