Wednesday 5 February 2020

Maximo business object development

Maximo business objects (MBOs)

  • The initValue() method is the first method executed after the constructor.
    • Typically used to initialize attributes on new records and to set default values.
  • The init() method is called after initValue(). Currently used to set current attribute as read-only based on some condition. Set related attributes as read-only.
  • Setting of field flag should not be done in the init() of mbo. It should be done in the initFieldFlagsonMbo():
    • Consider performing logging, in the initFieldFlagsOnMbo() method.
      • The logic of setting the readonly and required field flags to the MBO.
      • Any setting of the field flags that require expensive operation, such as database query should be moved from init() to this method. However, those which don't require steps such as fetching Mbos can be left in init().
      • You can group the logic for a few attributes together if logic of deciding their flag is the same, or in other words, can be handled at once.
    • Don't fetch MBOs in the init() method.
    • If MBO needs to be fetched, do it in initFieldFlagsOnMbo().
    • Doing this could avoid fetching additional MBOs when this current MBO being called. This could also improve the performance.
  • Constructing a MBO is costly
    • If you have a MBO available to use, do not refetch the MBO.
    • Don't implement a cache that holds on to MBOs, use smaller data structure.
    • Use the information that already resides in memory whenever possible, avoid getting new MBO from the database.
      1. Using the same relationship from the same MBO will get you the same MBO set already created.
      2. Pass the Mbo/MboRemote as parameters to the methods.
      3. Use BulletinBoardService to communicate information among objects within the execution of the same thread if passing parameter is not possible.
  • The constructor should only be used to call super and set some instance variables.
    • Should never execute methods that could call exceptions.
  • Don't ever use a MBO or mbo set class as a event Listener, it will never get garbage collected.
  • Don't ever put a mbo/set as static anywhere.

MboSets

  • If you only need to process records that are already instantiated in the MboSet use MboSet.getSize to loop through the records instead of count.
  • Avoid using the MboSet method count. It hits the database and can take a long time.
  • To see if a MboSet is empty use the MboSet method isEmpty(), do not use count().
  • To see if there's only one record in the set, use:
    • ((set.getMbo(0) != null) && (set.getMbo(1) == null)).
  • If you want to visit every member of a set, use code like:

    *EXAMPLE

  • If you only want to check to see if a set has been modified, use Mbo method getInstanciatedMboSet.
    • If the set hasn't been read in yet, this method returns null, so you know no possible changes could have occurred:

      EXAMPLE
  • Close the MboSet if you are certain you will not use it anymore.
  • Use set NOSAVE flag to true, if MboSet obtaining is for readonly purpose. Then it won't be added to transaction.
  • If a MboSet is used for traversing forward only and not to be saved use DISCARDABLE MboSet.
    • The children of a discardable MBO is NOSAVE, change NOSAVE flag back to true.
    • Or don't use relationship, directly create set using a composed where clause.
  • SAVE - Don't call save in business logic code.
    • Exceptions are transactions that don't need to be rolled back.
    • Think Rollback when calling save.
  • The modification of a MboSet that hasn't been saved can be obtained from the same relationship from the same parent, if you use a different relationship you won't see the change.
  • If two MboSets contain MBOs pointing to the same record in the database and you modify both you will get "Record has been updated by another user". That is why you should use the same relationship to obtain the record.
  • Avoid repeating logic, reuse MboSets and MBOs
    • Pay extra attention to the logic inside a loop.
    • When implement lower level method, understand how it is to be used. Avoid expensive operations that would become repeated when the method is used.

Field validations and actions

NOTE: Even though the validation and action logic can be put in either method, by convention the validation method should only do validation and the action method should only do business logic. Do not perform validation in the action method.
  • Field validation gets invoked when tabbing out of a field, or when a field is being set from the backend.
    • When the setValue method is invoked for an attribute the framework accesses the data dictionary table MAXATTRIBUTE to determine if the attribute has a validation class associated with it.
    • Typical order of execution: initValue(), init(), validate(), action(), hasList(), getList().
    • initValue() - initializes fields for new records
    • init() - sets read-only fields
    • Once a validation class has been determined, the validate() will be executed. The validation method can be customized to verify whether the new value for the attribute is valid.
    • An action() method may then be executed. Action methods contain business logic and updates to other objects.
    • hasList() - returns true if there is a list.
    • getList() - creates MboSet for list.
  • MEA transactions also invoke field validation for all fields it sets at the very end.
  • MEA - Use getValidateOrder() to specify the attribute validate order
    • In case MEA performs delay validation, use this method to specify the validate order.
  • Register validation classes, by defining the class name in the class field.
    • If a class is already defined, then you can extend the existing class
  • Using Access Flags
    • The access flags are defined in the MboConstants interface. Implement psdi.mbo.MboConstants to use the constants without using a prefix with them.
    • Some of the more commonly used flags: NOACCESSCHECK, NOVALIDATION, DELAYVALIDATION, NOACTION, NOVALIDATION_AND_NOACTION.
NOTE: It is important to set the appropriate flags to help increase Maximo performance.
  • DOMAINS - MAXTableDomain, CrossOverDomain, ALNDomain, SynonymDomain, NumericDomain
    • Use Domains and Crossover Domains as much as possible
    • If unachievable then use FldClass
      • Understand all flags when developing field class.
      • Avoid doing validation if it's not needed.
        • Conditional Domain Value
          • ALNDomain/SynonymDomain/NumericDomain
        • Conditional Cross Over Domain (various flags - copy don't copy, etc)
        • Domain and FldClass could co-exist.
          • Consolidate Domain and FldClass if possible
            • Validation occurs for both.
            • Everything in the Field Class will be executed except for the getList(). The list will come from the domain that is defined. If a domain is not defined the list will be fetched from the field class.
        • MaxLookupmap - key mapping for lookup and goto app.
          • Better to be implemented in table and not in fldclass.
        • Avoid Yes-No-Cancel Dialog, it's memory intensive.
          • Understand User-Interactive.
  • Field Flags (ex. Readonly)
    • Do not implement costly field flags in initvalue or init of MBO.
    • Implement in mbo.initFieldFlagsonMbo
  • Non-persisistent attributes
    • Population of value should be defined in that fields initvalue, not in MBO's init.
  • When using MAXTABLEDOMAINS - getList() gets called every time.
    • Do criterias in the constructor if it's fixed and not dynamic.
  • Two classes can be associated to an attribute. MboValue and Validation Class.
    • The MboValue class contains the attribute value and all the attribute properties defined in the data dictionary.
  • Some of the more commonly used methods are listed:
    • getString(): Gets attribute value as String
    • getInt(), getFloat, getDouble(): Get value as int, float, ...
    • getColumnTitle: Gets the title of the column
    • getInitialValue(): Gets initial value
    • getMbo(): Gets the MBO associated with the attribute
    • setValue(): Sets the value of the attribute
    • setValueNull(): Sets the value null
    • isNull(): True if attribute value is null
    • isReadOnly(): True if attribute is read-only

Relationships

  • A whereclause generated by a relationship is only a portion of the whereclause generated, there are other parts to the clause such as security.
  • Make sure a relationship is not defined in the database before adding a new one.
  • Don't modify an existing relationship. Create a new one.
  • Everything obtained via a relationship is included in the same transaction. When any MboSet in the transaction is saved, all MboSets in that same transaction will be saved.
  • Don't use a relationship if you want a brand new transaction, instead get the set from MXServer.
Example: The following gets the complete Work Order table which you can further narrow the set by applying a where clause.
\\ WOSetRemote mboSet = (WOSetRemote) MXServer.getMXServer().getMboSet("WORKORDER", getUserInfo());

General

  • When using a private method, consider writing another public method to call this private method. So outside objects can extend from this private method if needed.
  • If a set is filtered from the UI using setQbe, the user should be aware that if they attempt an action using the getThisMboSet(), they will only be getting the filtered set, not the complete set.
  • When writing code, do not forget that there are possibilities that the code could be called from MEA or other objects that don't involve the UI.
  • When executing scripts, the order of execution is first DBC script and then JAVA.
  • If you want to implement an interface, you need to implement each and every of its abstract classes. Otherwise your class will remain abstract as well.
  • Do not use System.out.println
  • Avoid implementing static variable and class
  • Don't use synchronized class if not needed
  • Don't use toUpperCase() or toLowerCase() liberally, pass parameters in the right case.
  • Use StringBuilder(StringBuffer if need synchronization) instead of String += in a loop.
  • Formatter classes are expensive, directly use the object instead of converting to string and converting back.
  • Consider fetching a MBO independent of the MBO used in a while loop, which may have been fetched via a relationship. This helps release MBOs and free up memory.
  • Consider using the DISCARDABLE flag to enable the release of MBOs quicker. Garabage collection usually cleans up the left over MBOs and mboSets, but it could be slower than what you need. Use discardable when a MboSet is used for traversing forward only and is not to be saved.
  • Consider using getMXTransation().save(): If processing a large set of records, consider saving each transaction instead of waiting for the entire set in the while loop to perform transactions. This will get the currently active transaction for the object. If there is no current transaction, a new one is created for you.
  • What may also help release memory is fetching a new database connection. This helps avoid extensive instantiation of MBOs in the memory:

Keeping costs low

  • Improve the database query for the MboSet
    • A not well tuned Sql query is generally the biggest contributor to elapsed time taken by business logic
  • Don't make the table too wide
    • Using normalized tables, especially if the tables can be separated out by different functions, such as can be displayed on a different tab or dialogs on UI, can be beneficial
  • Keep MBO's init() light
    • Don't query DB in init() at all and move expensive logic of setting MBO or field flags to initFieldFlagsOnMbo()
  • In init of field A, don't initialize non-persistent field B.
    • Field B should initialize itself in its own initValue()

Common performance problems

  • Repeated queries, too many queries, and redundant queries
    • Use the existing relationship, use getSharedMboSet() if applicable
    • Consider passing parameter(s) or caching
    • Don't issue queries in Mbo.init()
    • Don't call reset/save unnecessarily
    • If the method is a method that can be called repeatedly, don't simply issue the query, think alternative solutions
  • Lacking Indexes
    • Don't assume the record set will be small, have strategies for unexpected
    • Test against large database
    • Use explain plan or other tool to help with the query and indexes
    • Analyze the possible queries to the table, create appropriate indexes
  • Not able to use indexes.
    • If the field can be UPPER, don't define it as ALN
    • Avoid using QBE in the code, use setWhere() to prevent leading %
    • Define TEXT search type on description like columns
    • Avoid using "not in" (not to be confused with not in ('abc', 'efg')) and !=
    • Avoid selects and sub-selects that only using columns with few distinct values
    • Keep in mind that result can be different on different DBs
  • Statements too complex
    • Use tools such as Optim query tuner to help with writing the query and create index
    • Consider using multiple queries
  • Query returns too many records, not enough qualifiers
    • Re-examine functionality, rewrite the query
    • Provide a default filter for the application list page and lookup
    • Find alternative app flow to use more selective columns in the query
    • Reduce the records returned in subqueries
  • Example of SQL which performs differently based on the database platform
    • Query result may be different on different DB platform, but in majority of cases, they are similar.
    • Costs shown are relative to the DB, don't literally compare between platforms.
    • The "not in" is much worse than "in".

Considerations when defining indexes

  • Do not index columns that are modified frequently (like rowstamp)
  • Consider indexing columns that are used frequently to join tables in SQL statements
  • Don't index the column that have very few distinct values, consider composite index to increase the selectivity, especially if their combined selectivity is better than the selectivity of either column individually
  • Create the index so the keys used in WHERE clauses make up a leading portion.
    • If index is on columns A, B, C in this order, query that only uses A and C will not use the index effectively; query that uses A and B will use the index effectively.
  • If all keys are used in the SQLs equally often choose the most selective columns first in the index
  • If a column in a composite index allows null, make sure a not null column precedes it in the index
  • Beware of the cost of having indexes, don't define too many of them, drop unused ones
    • This is to reduce overhead on inserts and updates
  • Start with the frequently executed queries and most expensive when creating indexes
  • Seek help from DB tools and subject experts

Database performance

  • Keep the transaction short
    • Avoid overwrite MboSet.saveTransaction()
    • Avoid using event listener after save
    • If you have to do the above, please keep the logic minimum
    • Use NOSAVE MboSet to keep the sets in MXTransaction small, thus shortens the looping time for the transaction
  • Close a MboSet immediately if you know you can close it
    • Keep the cursor open in some DB can cause locking or even dead locking
    • If isEmpty() is called, and you know it is possible that there are more than 2 records in the set, and you know you are not going to reuse the set, close it. (Maximo fetches 1 record further, and will close the set automatically if all the records are fetched.)
  • Don't design very wide tables
    • Select * is costly when the table is wide
    • Use views or sub-select with proper index defined is not slower
  • Providing default Order by only if the result set is small enough
    • Order by is very costly, especially on large result set. Even though maximo may not fetch all, the cost on DB side is high

Other practices affecting performance

  • Care should be taken when using Yes/No/Cancel dialog
    • Yes/No/Cancel is expensive because the logic repeats
    • Call the logic of deciding whether to throw yes/no/cancel as early as possible in the logic
  • Know when to use MboSet.count() and MboSet.getSize()
    • MboSet.count() will most likely hit the Database, MboSet.getSize() only give you how many has been loaded in memory
    • Use MboSet.getSize() if what you are going to process are already instantiated as Mbos in memory.
  • Don't use save() in the business logic code unless for separate transactions
    • Transaction atomicity
    • Save invokes commits, which resets the mboset. If you get child mboset from this MBO again, all its related sets will be re-instantiated by re-querying the DB.
  • When logging, always check the logging level (don't check when logging error)
if (logger.isWarnEnabled())
logger.warn();
  • There is added cost when using conditional UI and conditional sigoption, use them wisely, do not over-do it
  • When defining maximo condition, evaluate the cost of complex expression that will rely on DB query to evaluate
    • Consider using a java class if complicated logic is involved
  • Share maximo condition if possible to reduce the evaluation time
    • Condition result is cached for each MBO
    • Don't choose always evaluate if the business logic doesn't require so
  • String operations such as toUpperCase() is very expensive
    • Don't use toUpperCase() or toLowerCase() liberally, passing parameters in the right case
    • StringBuilder(StringBuffer if need synchronization) should be used instead of String for string concatenation in a looping logic.
    • Formatter classes are expensive, directly use the object instead of converting to string and converting back.

Considerations when designing

  • Consider redesigning the look and feel and the functionality to improve usability and performance, for example, show information from different tables on different tabs or dialogs
  • Consider whenever possible to introduce default filters to the list page. It is hard to add the filter after the client is accustomed to and facing performance problem when their data grows.
  • Consider providing warning when performance threshold is met. (e.g. don't wait until the user is reporting 5,000 labor reported to the same workorder.)
  • Consider providing support for using background async job when the operation is anticipated to be slow (AsyncJobHandler and AsyncJobSubmitter)
  • Offer alternative solutions such as crontasks to handle slow and resource intensive operations.
  • Weigh among non-persistent field, related field, and crossed over persistent field and using views. Each has its pros and cons, views generally offer the most benefit if can be used
  • Provide purge functionality for transactional history tables to control the size

Example

Redundant Logic
Correction of Redundant Logic which makes a big difference

Start Center best practices

Start Centers

Use the following best practices for managing start centers:
  • Maintain records in portlet tables.
    • Determine what users are inactive and delete their start center records.
    • If indexes are not present, put indexes on portlet tables. There should be an index on LAYOUTID for all start center portlet configuration tables.
  • When a user record is deleted or a user is made inactive, we do not delete that user's start center data.
    • Create a trigger that watches when users are made inactive and deletes them from corresponding tables. (See the tables in blue on the ERD figure below.)
  • A high number of queries are fired when a start center is loaded.
    • Starting with Base Services patch 7.1.1.7, start center layout and configuration of a portlet is cached when a user first logs in. Editing a portlet invalidates the cache. Editing the portlet layout invalidates portlet layout cache. Choosing a different start center also invalidates the cache. For administrators, creating a new template invalidates the cache.
      Administrators should make start center templates lean by making sure few templates are not packed with many portlets. It is recommended that only 1-2 result set portlets are configured in any template. Queries being used by the result set portlets should be reviewed. This is especially true for clients using releases prior to 7.1.1.7
      Make sure a user is not given the same template(s) multiple times. Although the user would not see multiple instances of a template on a start center, following this practice reduces some checking during log in.

Start Center Tables

The following table describes start center tables:
TablePurpose / Contains
SCTEMPLATEStart Center templates and presentation XMLs
SCCONFIGStart Centers given to a user based on the group and template. For admins, GROUPNAME and SCTEMPLATE are null when editing a template until saved into SCTEMPLATE
LAYOUTConfiguration for a given start center, i.e., which portlets, which column, what order etc.
INBXCONFIGConfiguration of a inbox portlet, i.e., all available columns, which ones are visible, custom names etc.
ACTIONCFGConfiguration of a quick insert portlet, i.e., which app, which INSERT sigoption, custom name etc.
FACONFIGConfiguration of a favorite applications portlet, i.e., which app
KPILCONFIGConfiguration of records related to KPIMAIN records, i.e., which KPIs
KPIGCONFIGConfiguration of records related to KPIMAIN records, i.e., which KPIs
RSCONFIGConfiguration of result set portlet, all available columns, which ones are visible, custom names, used in condition or for graph display etc.
REPORTLISTCFGConfiguration of a records related to REPORT, i.e., which reports belong to which app
Additional Tables
RESULTSETCOLSHolding table for persistent columns for a given object and table in maximo. Loaded when new query is selected in Result Set portlet.
EXCLUDEDACTIONSActions that are not allowed to be configured in Quick Insert portlet.
The following entity relationship diagram (ERD) shows Start Center tables.


Upgrading to Maximo 7.6.1 from Maximo 7.6.0.x

Upgrading to Maximo 7.6.1 from Maximo 7.6.0.x

Body

Upgrading Maximo 7.6.0.x to Maximo 7.6.1 is like upgrading Maximo 7.5.0.x to Maximo 7.6.0.x:
  • Install a new SMP directory
  • Install new versions of WebSphere and/or DB2
  • WebSphere Application Server:
    • If installing to a new server, configure WebSphere with Tivoli's process automatic suite configuration tool
    • If installing to an existing server where Maximo is running, using WebSphere migration tools to recreate existing WebSphere 8.5.5.x profiles in WebSphere 9.0.0.7
  • Database:
    • Db2: Follow Db2 instructions to upgrade the database server (if on the same server), the instances and Maximo database
    • Microsoft SQL Server or Oracle database: ensure database version is compatible with Maximo 7.6.1
    • For all databases: Use Tivoli's process automatic suite configuration tool to connect to existing Maximo 7.6.0.x database. See the Platform Configuration Matrix.

Environment being upgraded in this blog:
image
The server name is maxsup097, which you may see in some of the screen shots below.
Upgrade scenario notes for this blog post:
1. Since I intend to keep these step-by-step instructions limited to Maximo only, this post will not include WebSphere configuration or migration, or any database upgrade steps.
As can be seen above, I previously installed WebSphere 9.0.0.6 and deployed Maximo 7.6.0.x to it. I will apply WebSphere 9 Fix Pack 9.0.0.7 to bring it into line with the version distributed with Maximo Asset Management 7.6.1. Also, Maximo 7.6.1 may be run on WebSphere 8.5.5.13 or later using Java 8 as the default JDK.
However: Use of the version of Tivoli's process automatic suite configuration tool in Maximo 7.6.0.6+ is not supported, but may work with WebSphere 9.0.0.7.  Likewise, the version of Tivoli's process automatic suite configuration tool in Maximo 7.6.1 is not supported, but may work with WebSphere8.5.5.13. In my less-than-exhaustive testing, I found the Maximo 7.6.1.x Config Tool did not detect a correct JMS Queue configuration (and was quirky with Microsoft SQL Server).
Part 0: Upgrade Microsoft Windows Server 2012 R2 to Windows Server 2016. If at all possible, do not do this. Stand up a new server. It will take a lot less time. (I did it because I had several Maximo 7.6.0.x instances I wanted to test upgrade against and it was easier for me.)

Part 1: Install Maximo Asset Management 7.6.1. See Installing Maximo Asset Management 7.6.1 From Start to Finish Part 1 Only.
In Installation Manager, after installing Maximo 7.6.1 but before running the Config Tool, I added the following packages to the repository:
  • IBM Maximo Asset Health Insights 7.6.0
  • Asset Health Insights Feature Pack 7.6.0.5
  • IBM Maximo for Service Providers 7.6.3
  • Maximo for Service Providers Fix Pack 7.6.3.2
A Detour Into Installing Packages through Installation Manager
I did this rather than using the Launchpad to avoid prerequisite errors.While the available versions of both products will install to Maximo 7.6.1, they should be updated to the latest Fix Pack before configuring them. This means you will need to download and add the Fix Pack repository to Installation manager after you install the 7.6.x version.
To speed things up, I took a second approach: Bypass the Launchpad and install them at the latest Fix pack right off.
For each of the four packages above, I created a directory with a unique, expanded the main package, and moved the repository zip into the correct directory, for example:
  1. Create directory ahi-760
  2. Download Max_Asset_Health_Insights_760.zip
  3. Expand .zip into Max_Asset_Health_Insights_760 directory
  4. Move \Max_Asset_Health_Insights_760\AssetHealthInstallerRepository.zip into \ahi-60.
  5. Create directory ahi-7605
  6. Download 7.6.0.5-TIV-AHI-FP0005.zip
  7. Move 7.6.0.5-TIV-AHI-FP0005.zip into ahi-7605
and so on. I don't actually need to move the FP into its own directory. Now I have a directory structure:
  • ahi-760
  • ahi-7605
  • sprin-763
  • sprint-7632
The resulting list of repositories:
image
IBM Maximo Asset Health Insights. There is an additional step that needs to be taken after installing AHI. Go to the tech note Updating the Maximo Asset Management IoT file. Follow the steps there to download and install the correct IoT configuration files.
IBM Maximo for Transportation 7.6.2: See the tech note Unable to install Maximo for Transportation 7.6.2. Installing Transportation requires following the steps I describe above.
Detour ends

After the repositories are added, click Apply and OK. Click on the big Install button.
image
Select the add-ons and Industry Solutions and install them to your new Maximo 7.6.1 instance. When installed, keep IBM Tivoli process automation engine configuration utility selected and click Finish.

Part 2:
image
Click on Configure a new Administrative Workstation to point to an existing Asset Management deployment.
image
On this screen I selected the WebSphere is already configured radio button. Do not check Bypass WebSphere validation.
image
Here's the blank Database Instance Information form...
image
...and here it is filled out, with the cursor carefully placed over the database name, m7609703.image
and the same for configuring the Application server:
image
imageNo J2EE security on my out-of-the-box Maximo:
image

Next, apply the deployment operations:
imageThe new Maximo 7.6.1 directory is now configured as the Administrative workstation for the database called m7609703.

Another Detour: Backing up the Administrative Workstation.
Nothing has happened to the database or web application server yet. Before we upgrade the database, let's go over what to back up when backing up the administrative workstation.
1. The entire SMP directory. You don't have to back up the EAR files in \SMP\maximo\deployment\default\.
2. The entire Installation Manager shared directory, which contains all of the installed .zipp files. (.zipp is .zip with an extra p.)
imagebut not the Installation Manager application directory, [which in this case I put into C:\IBM rather than C:\Program Files\IBM]. If you need to manually roll back or recreate rather than roll back to a snapshot of the Admin workstation, you will have to install Installation Manager anyway.
3. The hidden folder C:\ProgramData\IBM\Installation Manager. This folder contains the repository database of all of the products installed by Installation Manager.
image
End of Detour.

Interesting.
Taking Maximo 7.6.1 out of it, all I've done here is install Maximo, and Configure a new Administrative Workstation to point to an existing Asset Management deployment. These steps can be used independently. whenever you need to "move" the admin workstation or it goes missing somehow. Remember: The only irreplaceable piece of Maximo is the database.

Part 4: The Upgrade
While this will be the big part for Maximo users, it is not the big part for Maximo server administrators, which we just completed.
image
Click on Update Database and Build and Deploy Application EAR Files.
image
image
Click Next.
image
You may also see an option to Deploy the application EAR file.
Change the skin to the new one, and enable enhanced navigation:
image
Click Finish.
image
After deploying the EAR file, log in and finished! Click on picture below to find the 7.6.1 Preview Site.
image

IBM Readme for IBM Maximo Asset Management 7.6.1.3 Fix Pack

  Fix Readme Abstract This fix pack updates IBM® Maximo® Asset Management version 7.6.1, 7.6.1.1, and 7.6.1.2 Content IBM Maximo Asset Manag...