I was working with a customer recently that was having problems with the Direct Print with Attachments function from a list of work orders in Maximo. In Maximo 7.6, they would navigate to the Work Order Tracking application, filter for a list of work orders, and run the Direct Print with Attachments option against those work orders. After upgrading to MAS 9.0, they were receiving errors from the system when attempting to do so under certain conditions.
The main trigger for the error they were seeing was the presence of a URL-based attachment on one of the work orders being printed. Without a URL-based attachment on any work order in the batch, the Direct Print with Attachments routine worked just fine. The problem was that most work orders had a safety data sheet attached that referenced an online PDF document that would get updated frequently.
We went through the standard process of raising an IBM support case, but unfortunately timelines were in danger of being impacted and we needed a workaround solution. We decided to create an Automation Script that could get called from the REST API in Postman that could generate the reports.
The Automation Script accepts two query parameters:
- where: This represents a where clause against the WORKORDER table that can be applied to filter the list of work orders down to just those wanting to be printed.
- max: A numeric value that represents the maximum number of records to process in a given print run. This protects against a where clause that may return more records than expected.
Here is the script. Please note that there is not a Launch Point associated with the script as it is currently meant to be called from the REST API. Also, this is meant to be a temporary script until the underlying issue is resolved from IBM.
# ------------------
# This script will generate a single PDF file for direct
# printing a work order or a group of work orders.
#
# The script can either be called from:
# 1. the REST API via: /oslc/script/WODIRECTPRINT, or
# 2. a JSP file. If using this method, please be sure
# to set the parameters as part of the script context.
#
# Parameters:
# - where: The where clause of the work orders to pull.
# - max: How many work orders do we want to limit
# the PDF attachments to? Default is -1 which does
# not apply a limit.
#
# @author Alex Walter alex@a3jgroup.com
# @date 09 JUL 2025
# ------------------
"""
{
'autoscript': 'WODIRECTPRINT',
'description': 'Direct Print Work Orders',
'scriptlanguage': 'python',
'version': '1.0.0',
'loglevel': 'DEBUG',
'active': true
}
"""
from com.groupdocs.conversion import Converter
from com.groupdocs.conversion.licensing import License
from com.groupdocs.conversion.options.convert import PdfConvertOptions
from com.ibm.tivoli.maximo.oslc.provider import AttachmentStorage
from com.ibm.tivoli.maximo.report.birt.runtime import ReportParameterData, ReportRuntimeTempLocation
from java.io import ByteArrayInputStream, File
from java.lang import System
from java.util import HashMap, Vector
from java.util.concurrent.atomic import AtomicInteger
from javax.imageio import ImageIO
from psdi.app.report import PathAndReportFlag, ReportUtil
from psdi.iface.router import HTTPHandler
from psdi.iface.util import XMLUtils
from psdi.server import MXServer
from psdi.util.logging import MXLoggerFactory
# -----------
# Convert Word and Excel documents to PDF
# -----------
def convertByGroupDocs(sourceFilePath, targetFilePath):
converter = Converter(sourceFilePath)
converter.convert(targetFilePath, PdfConvertOptions())
converter.close()
# -----------
# Get the file extension from a file path
# -----------
def getFileExtension(path):
# Find the last occurrence of '.' in the path
lastDotIndex = path.rfind('.')
# If a dot is found and it's not the last character, return the extension
if lastDotIndex != -1 and lastDotIndex < len(path) - 1:
return path[lastDotIndex + 1:]
# Return empty string if no extension is found
return ""
# -----------
# Get a byte array from a given URL
# -----------
def getByteArrayforUrl(docpath):
properties = HashMap()
properties.put("HTTPMETHOD", "GET")
properties.put("URL", docpath)
return HTTPHandler().invoke(properties, None)
# -----------
# For a given work order, loop over all printable
# attachments and convert them to PDF.
# -----------
def convertAllAttachmentsToPDF(baseMbo):
global listOfFilesVector, outputFolder, docIndex, logger
# Set license key of PDF converter
converter = None
groupdocLicense = License()
groupdocLicense.setLicense(ReportRuntimeTempLocation.getGroupdocsLicLocation() + File.separator + "GroupDocs.Conversion.lic")
# Get the set of attachments for this WO
doclinksSet = baseMbo.getMboSet("DOCLINKS")
doclinks = doclinksSet.moveFirst()
while doclinks:
if doclinks.getBoolean("PRINTTHRULINK"):
# Increment document index
docIndex = docIndex + 1
# What kind of file are we looking at?
isListItem = False
isImageFile = False
isTextFile = False
isPdfFile = False
# Make a temporary file name
groupDocConvertedFileName = "File" + str(docIndex)
# Get the document URL and see what type of file we are working with
attachUrl = doclinks.getString("urlname")
fileExtension = getFileExtension(attachUrl)
attchUrlUpperCase = attachUrl.upper()
if attchUrlUpperCase.endswith(".XLS") or attchUrlUpperCase.endswith(".XLSX"):
isListItem = True
elif attchUrlUpperCase.endswith(".PNG") or attchUrlUpperCase.endswith(".JPG") or attchUrlUpperCase.endswith(".GIF") or attchUrlUpperCase.endswith(".JPEG"):
isImageFile = True
elif attchUrlUpperCase.endswith(".TXT") or attchUrlUpperCase.endswith(".CSV"):
isTextFile = True
elif attchUrlUpperCase.endswith(".PDF"):
isPdfFile = True
if logger.isDebugEnabled():
logger.debug("attach url:" + attachUrl)
logger.debug("file extension:" + fileExtension)
logger.debug("isListItem:" + str(isListItem))
logger.debug("isImageFile:" + str(isImageFile))
logger.debug("isTextFile:" + str(isTextFile))
logger.debug("isPdfFile:" + str(isPdfFile))
try:
# Get the file data from URL or COS Storage
fileData = None
try:
urlType = doclinks.getString("URLTYPE")
if urlType == "URL":
if logger.isDebugEnabled():
logger.debug("urlType: [" + urlType + "] [" + doclinks.getString("weburl") + "]")
# Get the file data from URL
docpath = doclinks.getString("weburl")
fileData = getByteArrayforUrl(docpath)
else:
if logger.isDebugEnabled():
logger.debug("urlType: [" + urlType + "]")
# Get the file data from COS
fileData = AttachmentStorage.getInstance().getAttachment(doclinks)
except Exception as fe:
logger.error("URL attachment " + attachUrl + " might be missing")
logger.error("Error: " + str(fe))
# Convert file data to PDF (if necessary)
if fileData:
# Set the final path for PDF of attachment
finalFilePath = outputFolder + File.separator + groupDocConvertedFileName + ".pdf"
if logger.isDebugEnabled():
logger.debug("finalFilePath: " + finalFilePath)
if isImageFile:
# Handle images
stream = ByteArrayInputStream(fileData)
img = ImageIO.read(stream)
if logger.isDebugEnabled():
logger.debug("convertImgToPDF: [" + groupDocConvertedFileName + "] [" + fileExtension + "]")
ReportUtil.convertImgToPDF(outputFolder, groupDocConvertedFileName, img, stream, fileExtension, logger)
elif isTextFile:
# Handle text-based files
stream = ByteArrayInputStream(fileData)
if logger.isDebugEnabled():
logger.debug("convertTextToPDF: [" + groupDocConvertedFileName + "] [" + fileExtension + "]")
ReportUtil.convertTextToPDF(outputFolder, groupDocConvertedFileName, stream, fileExtension, logger)
elif isPdfFile:
# Handle PDFs (just write to disk)
XMLUtils.writeBytesToXMLFile(fileData, finalFilePath, False)
else:
# Handle Word, Excel, and other document types
tempFileName = outputFolder + File.separator + "tempFile." + fileExtension
XMLUtils.writeBytesToXMLFile(fileData, tempFileName, False)
convertByGroupDocs(tempFileName, finalFilePath)
# Add to our vector of attachments to be concatenated
pathAndReportFlag = PathAndReportFlag()
pathAndReportFlag.filePath = finalFilePath
pathAndReportFlag.isReport = False
listOfFilesVector.add(pathAndReportFlag)
if logger.isDebugEnabled():
logger.debug("ReportUtil file path of attached file is: " + doclinks.getString("urlname"))
else:
logger.error("URL attachment " + attachUrl + " does not have file data")
except Exception as e:
logger.error("ReportUtils.convertAllPrintableAttachmentsOfThisMboToPDF error occured while converting attachment to pdf; probable problamatic url=" + doclinks.getString("urlname"))
logger.error("e.getMessage(): " + str(e))
# Move to the next attachment
doclinks = doclinksSet.moveNext()
# Return the document index
return docIndex
# Create a logger
logger = MXLoggerFactory.getLogger("maximo.script." + service.getScriptName())
if logger.isDebugEnabled():
logger.debug("Starting " + service.getScriptName() + " script")
# Test to see if we're coming from the REST API or not
try:
request
except:
request = None
try:
where
except:
where = "1=2"
try:
maxRecords
except:
maxRecords = -1
# Initialize Variables
if request:
userInfo = request.getUserInfo()
if request.getQueryParam("where"):
where = request.getQueryParam("where")
if request.getQueryParam("max"):
maxRecords = request.getIntegerQueryParam("max")
# Report information
reportDesignName = "woprint.rptdesign"
reportApp = "WOTRACK"
# Output logging information
if logger.isDebugEnabled():
logger.debug("where: " + where)
logger.debug("maxRecords: " + str(maxRecords))
logger.debug("ReportName: " + reportDesignName)
# Construct file paths and names
atomicIntegerForFilepath = AtomicInteger()
nextNumber = atomicIntegerForFilepath.incrementAndGet()
folderNameTemp = str(System.currentTimeMillis()) + "_" + str(nextNumber)
outputFolder = ReportRuntimeTempLocation.getGroupdocsLocation() + File.separator + folderNameTemp + "O"
woDirectPrintFileName = "woDirectPrint.pdf"
woDirectPrintFullPath = outputFolder + File.separator + woDirectPrintFileName
fileDir = File(outputFolder)
fileDir.mkdirs()
# Output logging information
if logger.isDebugEnabled():
logger.debug("outputFolder: " + outputFolder)
logger.debug("woDirectPrintFullPath: " + woDirectPrintFullPath)
# Call PDF generator
reportAdminService = MXServer.getMXServer().lookup("BIRTREPORT")
listOfFilesVector = Vector()
mboSet = None
reportData = None
try:
# Get a set of Work Orders based on the where clause
mboSet = MXServer.getMXServer().getMboSet("WORKORDER", userInfo)
mboSet.setWhere(where)
mbo = mboSet.moveFirst()
docIndex = 0
countIndex = 0
while mbo:
countIndex += 1
woDetailsFileName = "woDetails-" + mbo.getString("WONUM") + ".pdf"
woDetailsFullPath = outputFolder + File.separator + woDetailsFileName
# Generate the PDF report
parameterData = ReportParameterData()
parameterData.addParameter("appname", reportApp)
parameterData.addParameter("where", "(workorderid = " + str(mbo.getLong("WORKORDERID")) + ")")
report = reportAdminService.runReport(userInfo, reportDesignName, reportApp, parameterData, woDetailsFileName, "pdf")
# Write the report to the server
XMLUtils.writeBytesToXMLFile(report, woDetailsFullPath, False)
if logger.isDebugEnabled():
logger.debug("PDF Report written for work order " + mbo.getString("WONUM"))
# Add the report to the list of files vector
docIndex += 1
pathAndReportFlag = PathAndReportFlag()
pathAndReportFlag.filePath = woDetailsFullPath
pathAndReportFlag.isReport = True
listOfFilesVector.add(pathAndReportFlag)
# Generate PDFs for all printable WO attachments
# docIndex = ReportUtil.convertAllPrintableAttachmentsOfThisMboToPDF(mbo, listOfFilesVector, outputFolder, docIndex, logger)
docIndex = convertAllAttachmentsToPDF(mbo)
# Break the loop if max records has been reached
if maxRecords > -1 and countIndex > maxRecords:
if logger.isDebugEnabled():
logger.debug("Breaking out of record loop at " + str(countIndex) + " index")
break
# Move to the next work order in the set
mbo = mboSet.moveNext()
# Concat all of the PDF files together and cleanup
ReportUtil.concatAttachmentPDFFiles(outputFolder, woDirectPrintFullPath, listOfFilesVector, logger)
reportAdminService.deleteNeededFileFromAttachmentTempDirectory(outputFolder, listOfFilesVector, logger)
# Read the final PDF into byte array
reportData = XMLUtils.readXMLFileToBytes(woDirectPrintFullPath)
finally:
# Clean up the set
if mboSet:
try:
mboSet.cleanup()
mboSet.close()
except:
logger.error("Failed closing mboSet")
# Send the response
if request:
responseHeaders.put("content-type", "application/pdf")
responseBody = reportData
if logger.isDebugEnabled():
logger.debug("Finished " + service.getScriptName() + " script")
About
A3J Group is a leading solutions and consulting firm based in Tampa, Florida, specializing in Enterprise Asset Management (EAM) with a focus on fully developed Computerized Maintenance Management System (CMMS) solutions like IBM Maximo.
In addition to our suite of proprietary products—including MxMobile, our powerful mobile solution for Maximo—we provide traditional consulting services that help organizations plan, implement, optimize, and support their EAM strategies. Whether clients need hands-on configuration, strategic guidance, or ongoing system support, our team delivers the expertise to drive long-term success.
With over 50 years of combined experience, our specialized team is dedicated to our core mission: to serve as excellent stewards of our clients’ trust and investments, while driving measurable results through tailored, high-impact solutions and intelligent consulting.
Visit us at www.a3jgroup.com. Or drop us a line at info@a3jgroup.com.



