How to make any Maximo object contain information from a Service Address record
Author: Marcelo Blechner, Staff Software Engineer.
In the article Making your Maximo objects available on the Map you learned how to make the necessary changes so that Maximo objects that have no mapping support out-of-the box can be displayed on a custom Map component. That procedure included the creation of 3 new database fields to the Maximo object which would allow for the creation of an “on the fly” address for it. However, you might prefer to leverage the existing Maximo Service Address object, rather than just filling in ad-hoc latitude and longitude fields.
Here are the advantages and disadvantages:
Advantages:
-
More information on the MBO's location (use of all Service Address fields rather than just ad-hoc latitude, longitude and address string);
-
It is possible to leverage existing service addresses just by associating them to the MBO records (the map marker will reflect the Service Address location).
Disadvantages:
Implementing AddressableMbo interface on MBOs
In order to be able to associate a Service Address record to the MBO, you must implement the provided interface com.ibm.tivoli.maximo.srvad.app.common.AddressableMbo. Add this interface to your MBORemote interface and implement the required methods. These methods are used for loading the corresponding Service Address data:
- boolean hasServiceAddress() - returns true if there is a Service Address associated with the MBO.
- MboRemote getServiceAddress() - returns the Service Address associated with the MBO.
By implementing these methods on a MBO and by making some changes to the existing GISable methods (in case the GISable interface has been implemented for this MBO), the Service Address geo location will show up on the MBO's map tab.
The next section is an easy step-by-step example with code samples to show how to make the Maximo Inventory object an Addressable Maximo Object.
Code Sample: Making the Inventory Maximo Object Addressable
As described in the previous section, in order to make a Maximo Object contain Service Address information, you have to implement the AddressableMbo interface. Here is a code sample demonstrating how to implement it on Maximo Inventory object.
First of all we need to add the object attribute that is going to store the reference to the Service Address record and the relationship that will bring back the Service Address record that is associated with the object. In this case only 1 new attribute is added to the Inventory object: SADDRESSCODE. Note that if you have already followed the instructions on how to implement the GISable interface for this object, the ad-hoc Latitude, Longitude and Address attributes will no longer be used and might as well be deleted.
The database configuration application can be used, or you can use the following DBC script to add the new attribute and create the relationship:
<add_attributes object="INVENTORY">
<attrdef attribute="SADDRESSCODE" classname="com.ibm.tivoli.maximo.srvad.app.common.FldServiceAddress" title="Service Address" remarks="The address code identifies a service address. It must be unique by site for each service address." sameasobject="SERVICEADDRESS" sameasattribute="ADDRESSCODE" persistent="true" haslongdesc="false" required="false" ispositive="false" canautonum="false" />
</add_attributes>
<create_relationship parent="INVENTORY" name="SERVICEADDRESS" child="SERVICEADDRESS" whereclause="addresscode = :saddresscode and siteid = :siteid" remarks="Service Address for inventory" />
Once the attribute and relationship are added to the Maximo table (after having run the script that updates the database) it is time to add the AddressableMbo interface to the InventoryRemote interface and implement the following on the Inventory class:
import com.ibm.tivoli.maximo.srvad.app.common.AddressableMbo;
@Override
public ServiceAddressRemote getServiceAddress() throws MXException, RemoteException
{
ServiceAddressRemote mbo = (ServiceAddressRemote)getMboSet(SERVICEADDRESS_NATIVE_RELATIONSHIPNAME).getMbo(0);
return mbo;
}
@Override
public boolean hasServiceAddress() throws MXException, RemoteException
{
return !isNull("SADDRESSCODE");
}
Note that, SERVICEADDDRESS_NATIVE_RELATIONSHIPNAME is defined in the AddressableMbo interface as the “SERVICEADDRESS” string so, if the relationship created in the previous step is not called “SERVICEADDRESS”, make sure that the relationship name used in the getServiceAddress() method matches the actual relationship name. Also, it is not mandatory that the new MBO attribute be called exactly SADDRESSCODE, just make sure that the relationship's where clause matches the attribute name.
If the GISable interface has been implemented according to the article “Making your Maximo objects available on the Map”, change the implementation of these GISable methods as follows:
@Override
public String getAddressString() throws MXException, RemoteException {
ServiceAddressRemote srvad = getServiceAddress();
if (srvad != null)
{
return srvad.getAddressString();
}
else
{
return null;
}
}
@Override
public Double getLatitudeY() throws MXException, RemoteException {
ServiceAddressRemote srvad = getServiceAddress();
if (srvad != null)
{
return srvad.getLatitudeY();
}
else
{
return null;
}
}
@Override
public Double getLongitudeX() throws MXException, RemoteException {
ServiceAddressRemote srvad = getServiceAddress();
if (srvad != null)
{
return srvad.getLongitudeX();
}
else
{
return null;
}
}
@Override
public Boolean hasCoords() throws MXException, RemoteException {
if (hasServiceAddress())
{
ServiceAddressRemote mbo = getServiceAddress();
if (mbo != null)
{
return mbo.hasCoords();
}
}
return false;
}
Note that the idea behind these changes is only to forward the GISable method calls to the Service Address MBO because, from now on, the Service Address MBO will be the one handling the Inventory GIS information.
Keep in mind that Maximo, by default, does now allow Service Address records to be changed outside of its own application. It is possible, however, to change a maxvar so that applications such as Asset and Location can create (or update) Service Address records using the map's “Set Record Location” action. The code that handles this feature is rather complex and will not be explained here. Reference on how to implement this feature can be found in the Asset and Location classes, in the implementation of the isGISDataReadonly() and saveGISData() methods.
Because the purpose of this article is only to explain how to associate an existing Service Address record to any MBO record, the aforementioned methods will have a stub-like implementation, just to disallow modifications on the Service Address table.
@Override
public boolean isGISDataReadonly() throws MXException, RemoteException
{
return true;
}
@Override
public void saveGISData(String address, String lat, String lng)
throws MXException, RemoteException
{
}
By returning true in isGISDataReadonly(), we make sure that the “Set Record Location” action will never be available in the map's context menu and the marker drag'n'drop feature will be disabled. And, because the map marker cannot be changed on the map tab (only by changing the MBO's service address), all the implementation in saveGISData() can be removed because it will never be executed anyway.
But if the purpose is to be able to change the latitude and longitude of a record using the map's drag'n'drop action, follow the instructions in the article “Making your Maximo objects available on the Map” only. The tutorial from that article does not use existing Service Address features, it just creates ad-hoc fields in the MBO to fulfill the purpose of displaying and moving records on a map.
The next step is to add the presentation changes so that Service Address information can be displayed in its own tab in the Inventory application.
Export the Inventory presentation in Application Designer and then add this data source tag in the beginning of the xml file (underneath the “clientarea” tag):
<datasrc id="srv_add" relationship="SERVICEADDRESS"/>
Then, assuming that you want to display all Service Address information, add this tab tag in the same level as the other tab tags:
<tab id="serviceaddr" label="Service Address" licensekey="SERVICEADDRESS">
<section id="serviceaddr_grid8" border="true">
<sectionrow id="serviceaddr_grid8_11">
<sectioncol id="serviceaddr_grid8_11_1">
<section id="serviceaddr_grid8_11_1_1">
<multiparttextbox id="serviceaddr_grid1_1" datasrc="MAINRECORD"
dataattribute="itemnum" descdataattribute="item.description" />
</section>
</sectioncol>
<sectioncol id="serviceaddr_grid8_11_2">
<section id="serviceaddr_grid8_11_2_1">
<textbox id="serviceaddr_grid8_11_2_1_1" datasrc="MAINRECORD" dataattribute="orgid" inputmode="readonly" />
</section>
</sectioncol>
<sectioncol id="serviceaddr_grid8_11_3">
<section id="serviceaddr_grid8_11_3_1">
<textbox id="serviceaddr_grid8_11_3_1_1" datasrc="MAINRECORD" dataattribute="siteid" inputmode="readonly" />
</section>
</sectioncol>
</sectionrow>
</section>
<section id="serviceaddr_grid2" label="Address Information" datasrc="srv_add">
<sectionrow id="serviceaddr_grid2_row">
<sectioncol id="serviceaddr_grid2_row_col1">
<section id="serviceaddr_grid2_row_col1_sec">
<multiparttextbox id="serviceaddr_grid2_col1_1" datasrc="MAINRECORD" dataattribute="SADDRESSCODE"
label="Service Address" descdataattribute="SERVICEADDRESS.DESCRIPTION"
lookup="serviceaddresstable" menutype="SERVICEADDRESS" applink="srvad"
applinkreturn="addresscode" descinputmode="readonly" ondatachange="resetchildren"/>
<textbox id="serviceaddr_grid2_col1_1_a" dataattribute="FORMATTEDADDRESS" />
<textbox id="serviceaddr_grid2_col1_2" dataattribute="STREETADDRESS" menutype="SA_MOD_STRADDR" />
<textbox id="serviceaddr_grid2_col1_3" dataattribute="ADDRESSLINE2" />
<textbox id="serviceaddr_grid2_col1_4" dataattribute="ADDRESSLINE3" />
<textbox id="serviceaddr_grid2_col1_5" dataattribute="CITY" />
<textbox id="serviceaddr_grid2_col1_6" dataattribute="REGIONDISTRICT" />
<textbox id="serviceaddr_grid2_col1_7" dataattribute="COUNTY" />
<multiparttextbox id="serviceaddr_grid2_col1_8" dataattribute="STATEPROVINCE"
descdataattribute="STATEPROVINCE.description" descinputmode="readonly" lookup="valuelist" />
<textbox id="serviceaddr_grid2_col1_9" dataattribute="POSTALCODE" />
<multiparttextbox id="serviceaddr_grid2_col1_10" dataattribute="COUNTRY"
descdataattribute="COUNTRY.description" descinputmode="readonly" lookup="valuelist" />
<textbox id="serviceaddr_grid2_col1_11" dataattribute="GEOCODE" />
<multiparttextbox id="serviceaddr_grid2_col1_12" dataattribute="TIMEZONE"
descdataattribute="TIMEZONE.description" descinputmode="readonly" lookup="valuelist" />
</section>
</sectioncol>
<sectioncol id="serviceaddr_grid2_row_col2">
<section id="serviceaddr_grid2_row_col2_sec">
<textbox id="serviceaddr_grid2_col2_1" dataattribute="LATITUDEY" width="150"/>
<textbox id="serviceaddr_grid2_col2_2" dataattribute="LONGITUDEX" width="150"/>
<textbox id="serviceaddr_grid2_col2_3" dataattribute="REFERENCEPOINT" />
<textbox id="serviceaddr_grid2_col2_4" dataattribute="DIRECTIONS" lookup="longdesc" />
</section>
</sectioncol>
</sectionrow>
</section>
</tab>
Note that you can choose not to include some of the fields from the above xml snippet.
Then import back the presentation and you are done. You can now associate a Service Address record to the Inventory record by using the lookup button.
Figure 1: Service Address tab added to the Inventory application.
