Development

Overview

The goal of this section is to provide a broad understanding of the coding principles and fundamentals used when developing Windchill Extensions, along with some technical information required to do so.

A Windchill Extension developer requires knowledge of Java, Java Server Pages (JSP) and the Windchill API. They should be aware of the Windchill architecture and important aspects of software development such as queries, queues and security, to name a few. In addition, skills in using an IDE are also required. In this guide our examples are based on Apache NetBeans, but any IDE can be used to program extension as long as it supports Maven.

Extension Fundamentals

Identity

All extensions are delivered as uniquely identified packages. Internally, the extension is identified by the combination of these elements, which are defined in the pom.xml file:

<groupId>com.wincomplm</groupId> <!-- typically the reverse domain of the author -->
<artifactId>wex-example-mycode</artifactId>

The system pairs them together in the following manner:

[groupId].[artifactId] - e.g com.wincomplm.wex-example-mycode

Each extension has its own ClassLoader, so at runtime this unique ID can be obtained as follows:

String wexid =  ((IsolatedClassLoader) this.getClass().getClassLoader()).getPid(); 

Packages

The Java source code can be in any package, and classes only need to be uniquely named within the extension, thanks to the WEX Platform's isolation-based design. However, it is common to use this package structure:

 [groupId]/[artifactId]

The standard procedure is to have have a sub package impl that is used to store code internal to the extension and to have other packages e.g. commands and methods that hold the externally facing methods.

image-20230417122703990

None of this is enforced by the WEX Platform, but they are good practices that will help maintain coherence between the packaging and internal organization of all extensions, which is always a good thing when developing them.

Bootstrap

A single Java file is used by the WEX Kernel to initially load the WEX. It must inherit from IWexBootstrap and is detected by the Kernel automatically. A simple example of WexBootstrap.java would look like this:

package com.acme.wex.helloworld.impl.bootstrap;

import com.wincomplm.wex.kernel.impl.bootstrap.IWexBootstrap;
import com.wincomplm.wex.kernel.impl.manager.IWexKernelPackage;
import com.wincomplm.wex.system.impl.scan.WexScanner;

public class WexBootstrap implements IWexBootstrap {

    @Override
    public void initialize(IWexKernelPackage wex) throws Exception {
        WexScanner.scan(wex, "com.acme.wex.helloworld");
    }

    @Override
    public void terminate(IWexKernelPackage wex) throws Exception {
    }

}

There are two methods in it:

Method Description
initialize Invoked when the WEX is loaded by the Kernel. It can be used to perform any additional startup actions, and it must include a scan action that takes the package's path as an argument. This ensures the Kernel scans all the code for annotations that register hooks into Windchill, such as listeners or features. This is always triggered on the startup of a server, not only on install, so it must be reentrant.
terminate This is triggered when the package is unloaded by the kernel, typically when uninstall the extension. It's usually a good idea to revert any changes made by the extension to the system here.

Note: The initialize of the bootstrap is not the same as a method server ready, so it cannot be assumed the MS is started and ready to process requests on this initialize. No queries should be made at this point.

Resources

Resources are a common Windchill coding class and are used for localization. All labels in the UI should be implemented as resources so they can be, if required, translated. Resources use the browser's locale and will return the matching language. In a normal customization resources are located in the Windchill codebase but, since Windchill Extensions are isolated, we use special annotations to declare a set of resources to Windchill in a similar fashion to how we declare the Features:

package com.acme.wex.helloworld.resources;

import com.wincomplm.wex.kernel.impl.annotations.WexComponent;
import wt.util.resource.RBEntry;
import wt.util.resource.RBNameException;
import wt.util.resource.RBUUID;
import wt.util.resource.WTListResourceBundle;

@WexComponent(uid = "com.acme.wex.helloworld.resources.WexResources", description = "Resource bundle")
@RBUUID("com.acme.wex.helloworld.resources.WexResources")
@RBNameException
public class WexResources extends WTListResourceBundle {
    @RBEntry("Hello World")
    public static final String C1 = "com.acme.wex.helloworld.edkHelloWorld.description";
    @RBEntry("Hello World")
    public static final String C2 = "com.acme.wex.helloworld.edkHelloWorld.tooltip";
    @RBEntry("Hello World")
    public static final String C3 = "com.acme.wex.helloworld.edkHelloWorld.activetooltip";
}

Dependencies

There are cases in which a Windchill Extension will need to use a set of functionalities already implemented in another package, whether it is of the developer's own creation or not. In order to avoid duplication of code, that package can be included as a dependency in the pom.xml file of the WEX, which will make the methods available to the developer.

Several types of dependencies that can be included in a project; this section offers an outline of the main Wincom-related ones. These differences exist primarily at a conceptual and format level, but it's interesting to be aware of them as they can make finding the right package easier.

Commons

Naming conventions for Commons libraries are wt-xxxx-commons and wex-xxxx-commons, depending on their Windchill API usage. The wt labeled ones call Windchill, while the wex ones do not. As a rule of thumb, they should never be labeled as provided in the pom.xml file, since they almost always need to be placed in the extension's own codebase.

Base

The naming convention for Base libraries is xxxx-base.

Aux

Aux libraries follow the wex-xxxx-aux naming pattern.

A Note on JAXB

As JAXB was removed from Java 11, it is necessary to include it as a plugin in the pom.xml file.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>xjc</goal>
            </goals>
            <id>services</id>
            <configuration>
                <sources>
                    <source>${basedir}/src/main/xsd/your_file.xsd</source>
                </sources>
                <xjbSources>
                    <xjbSource>${basedir}/src/main/xjb/your_file.xjb</xjbSource>
                </xjbSources>
                <clearOutputDir>true</clearOutputDir>
            </configuration>
        </execution>

    </executions>
</plugin>

Features

A Feature is the registering of a component by the WEX Kernel to Windchill with the objective of making it available to other elements of the system. This is achieved through the use of the @WexComponent and @WexMethod annotations, which place a request to the Kernel making the code visible to Windchill and thus giving the possibility that it may be invoked by non-related extensions using the kernel's WexInvoker.invoke method.

An example of the declaration of a feature looks like this:

@WexComponent(uid = "feature-uid", description = "This description will be displayed by the Extension Manager")
public class ClassName {
    // Class code
}

The developer can see the features their extension is exposing to Windchill in the Extension Manager:

1617183084693

1617182692194

Features are highly versatile entities that are key to the development of Windchill Extensions. Here's some of the main purposes they can have:

Listeners

A Listener is an entity that is triggered by events that happen in the system and executes a method they do. Typical events that a Listener can monitor are:

The classes that extends the wt.events.KeyedEvent class are known as Event Services, and the Listener can be configured to listen for calls to these classes and get triggered by them. All of the abovementioned events have a KeyedEvent class associated to them that a Listener can react to.

Listener code runs in the same thread and must not impact performance. This means that if, for example, a user does a Set state to a Windchill object, the UI will wait for all code listening on the event to complete. Therefore, it is extremely dangerous and detrimental to the system to use queries or any other time-expensive code in a listener: asynchronous calls should be used instead, such as queues.

Implementation

Listeners are usually hooked to the system by adding a line in the wt.properties file, which will enable a specified Java service to listen for events that are generated by out-of-the-box code. Since all Windchill Extensions are hot-loaded, we cannot use the classic customization technique, which requires a system restart. The WEX Framework provides instead a solution in the form of a feature:

@WexComponent(uid = "wex-example-listener", description = "Example Set State Listener")
@WexWtListener({
@WexWtEventKey(eventClass = LifeCycleServiceEvent.class, keys = LifeCycleServiceEvent.SET_STATE, supported = {WTPart.class}, fatal = false)})
public class StateListener implements IWexWtListener<Object> {

The listener class must inherit from:

com.wincomplm.wex.kernel.impl.wki.IWexWtListener

and implement this method:

public void onEvent(KeyedEvent event, String key, Object target) throws Exception
Listening for a Method Server Startup

There are some particularities to be observed when listening for a Method Server start-up.

A listener targeting that event must inherit from:

com.wincomplm.wex.kernel.impl.manager.IWexMethodServerReadyListener

and implement this method:

public void onMethodServerReady() throws Exception

Example

The Listener Example implements a simple listener and will help you get the hang of the basics involved.

Methods

Registering methods as features is the way of making them available to Windchill Extensions which don't have them in their codebase.

An example of the declaration of a Method Feature will look like this:

@WexComponent(uid = "feature-uid", description = "This description will be displayed by the Extension Manager")
public class ClassName {

    @WexMethod(name = "method-id", description = "High level description of the method.")
    public void doSomething(Object parameter) throws Exception {
        // Code
    }
}

The previously declared method could be invoked by another Windchill Extension in the following manner:

WexInvoker.invoke("com.acme.wex-helloworld", "feature-uid.method-id", parameter);

Command Lines

A command line is a method meant to be called by a user or administrator from a Windchill Shell. They must be registered as features due to the code within the extension being isolated and not available to the Windchill system. The @Option annotation can be used to specify any arguments that might be needed:

@Option(name = "arg", usage = "A useful explanation", required = false)
public String myStringArg;

The syntax for calling a previously defined feature from the Windchill Shell as a command line is:

windchill com.wincomplm.wex.kernel.commands.WexCommandLineExecutor -pid {target extension uid} -cid {method's id} -u {username}  {password} [-a -arg1 {arg1} -arg2 {arg2} ... ]

Example

The Example - Methods illustrates the basics of it.

Logging

Logs are an important part of debugging extensions. Understanding where and how to use them is crucial: bad logs are worse than no logs.

There are several types of logging we can add to the code. They differ in their way of being enabled and the locations in which they appear:

Log type Enable via action Appears on Method Server Description
error false true Indicate a serious error, normally put into caught exceptions.
warn false true Provide information on anomalous events.
info true true General information that show interesting data.
debug true false Debug messages.
trace true false Used to mark entry and exit points of methods.

If your code has a lot of info type logs, that could be indicating insufficient log decomposition. Each of the logging types must be used for their correct purpose in order to keep log files clean, readable and useful. For example, we gather sufficient information from the trace type logs to analyze the execution of code, assuming that method does a single, clean function.

Note: Reading about Cyclomatic complexity will help you understand why your methods should have an limit of 5 decisions in order to keep them at the lower limit of the magic number.

Enabling logs

Logging can be enabled and disabled using the Extension Manager:

1617686355251

They are downloaded from the Manager too:

1617686537033

Coding logs

The logs can be added to the code by including this dependency in the pom.xml file:

<dependency>
    <groupId>com.wincomplm</groupId>
    <artifactId>wex-log</artifactId>
</dependency>

Here is a simple but illustrative example on how logs are actually included in a Windchill Extension's code:

final IWexLogger logger = WexLoggerFactory.getLogger(getClass());

private String exampleTrace(String arg) {
    logger.trace("=>exampleTrace {0}",arg);

    logger.error("Will always appear, for serious errors {0}", anObjectWithToString);
    logger.warn("Something we want to know about {0}", anObjectWithToString);

    logger.info("Used to give information on data {0}", anObjectWithToString);

    // Debug and trace only appear if the extension logging is enabled in the manager
    logger.debug("Used to give information on execution {0}", anObjectWithToString);

    String result = "Done!";
    logger.trace("<=exampleTrace {0}",result);
    return result;
}

Example

The Log Example implements all the various logging techniques.

Debugging

Enabling Debugging

Add this line to the site.xconf file (you'll find it in your Windchill Home) to enable debugging:

<Property name="wt.manager.cmd.MethodServer.debug.args" overridable="true" targetFile="codebase/wt.properties" value="-Xrunjdwp:transport=dt_socket,server=y,suspend=n"/>

Run thexconfmanager -p command through your Windchill shell. After that, the Method Server will show on startup the port you can connect the debugger to:

1617398081391

Note: An optional address=8000 parameter can be used to specify the port, but only if the system is configured to run a single Method Server, as explained below.

Running a Single Method Server

It can be hard to debug code in a multi-Method-Server system, as we can never predict which server will be chosen to run our code. Therefore, a good development technique for pure functional coding is to reduce the system's running Method Servers to just one.

Remove BackgroundMethodServer from windchillconfigurator.xconf to reduce the servers to a single MS. You can find the file in the following path:

%WT-HOME%\utilities\wca\conf\windchill\windchillconfigurator.xconf

The new property should look like this:

<Property name="wt.manager.monitor.services" overridable="true" targetFile="codebase/wt.properties" value="MethodServer"/>

The useQueues parameter should be set if we run without a background server.

Coding for Performance

All developers should be acutely aware of any performance implications of adding an extension to a Windchill system. Windchill is a multi-server, multi-threaded system running on high performance servers with a industrial strength database, but a high performance environment is never an excuse for poor performing code. Having said that, the impact of a poorly performing code is often contained and managed by the system.

A common mistake is to consider that Java code and calculations are a performance bottleneck: the primary area of concern, performance-wise, is almost always queries.

Database Queries

Sometimes a very inefficient query can be the cause of low performing code. However, in most cases the iteration of a not optimized query will be the source of the problem.

A well defined database query can take as little as 1ms. In some cases, due to the volume of the information or the scope of the query, it can take 100ms or more: a loop that contains a query of this type will become a performance bottleneck. Checking for this kind of patterns in our code will help improve its performance.

Network Calls

As with queries, network calls can take time to perform and can also have varying performance depending on network conditions and latency. Similar approaches to queries should be used, such as caching and looking out for potentially avoidable looping.

Queues and Asynchronous Coding

Windchill has a strong queuing system, and this should always be used for tasks that can done asynchronously, such as report generation. The queue method that you register must be called using the WexInvoke and cannot directly call any code in the extension, as these are in an isolated class path.

Profiling

The WEX framework supports the standard Java profiling tools. However, due to the unique isolation architecture used, we can have duplicate class running in the same JVM, which may cause difficulty in the interpretation of profiling data in some, although rare, cases.

Performance Throttling

The WEX framework comes with tools designed to monitor and throttle certain APIs. The wex-performance-commons extension is responsible for this, and can be used in the following manner:

return WexControlledExecutor.instance.execute(this,"methodName",maxTimeout, 2, 30000, 3000, () -> {
    // Code that must not exceed 2ms
});

It will monitor a frequently executed code and throw and exception:

try {
    return getRepresentationIcon(maxTimeout, datum, extension);
} catch (WexTimeoutException ex) {
    logger.warn("getRepresentationIcon timed-out, took = {0}ms, max = {1}ms", ex.getTaken(), ex.getMax());
} catch (WexDisabledException ex) {
     logger.warn("getRepresentationIcon is disabled for performance reasons");
} 

Quality Control

Code Review

Here are some suggested review points that a developer should take into consideration when writing Windchill Extensions:

Dangerous Coding Practices

There are some coding practices that are considered dangerous and can potentially introduce security issues in the code. This doesn't mean that they should be banned or avoided, but the developer should definitely take extra care when using them. Below are some examples:

Some of these are not considered general security issues, but in the case of code running in Windchill they can be labeled so as the access to these resources should only be achieved via the Windchill API.

Windchill Fundamentals

The goal of this section is to provide some information on key Windchill concepts that, although they are not strictly WEX-related, are useful to know when developing Windchill Extensions.

Object Identification

An oid is a combination of the class name and a Long that uniquely identifiers a single object in the Windchill database. All objects that inherit from persistable have an oid, and the user interface in Windchill uses these extensively. There are also prefixes:

Prefix Name Description
OR or none Object Reference This reference is to a unique object, for example, an iteration of a part. A part or document number is not a unique id.
VR Version Reference This references the control branch (not the master) and represents the latest iteration in said branch (for example B.3)

An example of an OID is:

VR:wt.part.WTPart:10268

In the URL the : with be encoded as %3A, so the same OID will be represented as:

VR%3Awt.part.WTPart%3A10268

This is an example in a URL:

1618308476907