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.
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
.
- Base libraries are pure code libraries that can be compiled in isolation.
- Used as a shared base for extensions and other code that needs to share the extension's data model.
- Some API call directly to the base libraries.
- System extensions are often dependent on Base libraries.
Aux
Aux libraries follow the wex-xxxx-aux
naming pattern.
- These are libraries that are located at the top of the dependency tree.
- They are used to extend features in system extensions without requiring new system extensions to be deployed.
- A good example is
wex-config-aux
, which holds handlers to be used in config.
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:
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:
- State changes.
- Check in and out of Windchill objects.
- Publish events.
- ESI events.
- Method Server start-ups.
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:
They are downloaded from the Manager too:
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:
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:
- All IWexConfiguration classes must implement Externalizable.
- All IWexConfiguration classes must perform validation.
- Queries must be performance wrapped.
- Code must be reviewed for security.
- All updates must included in the release-body.md file.
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:
- Any use of java.sql package.
- Any use of java.net.Socket class.
- PersistenceServerManager.
- SessionServerHelper.manager.setAccessEnforced.
- SessionHelper.manager.setAdministrator.
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: