1. Introduction

The Prosys OPC UA SDK for Java Code Generator (later just 'Codegen') can be used to reduce manual workload when using custom information models in the Prosys OPC UA SDK for Java. It creates Java classes based on OPC UA information models.

2. About Information Models

Codegen will work with most information models. If you encounter a model that does not generate properly, let us know.

2.1. Obtaining Information Models

Codegen requires OPC UA information models defined in the OPC UA NodeSet2 XML format ('NodeSet' in the following text). The NodeSet format is defined in the OPC UA Specification v1.05, Part 6., Annex F.

A popular software for creating and managing information models is the OPC UA Modeler that also enables exporting the information model in the NodeSet2 format. The exported models can then be used as the input for Codegen.

If your model depends on other information models, you will need to obtain and generate those as well.

Some of the well known Companion Specification information models can be obtained from the OPC Foundations GitHub page: https://github.com/OPCFoundation/UA-Nodeset/. For example, the NodeSet for the well known Device Information model ("DI") is at: https://github.com/OPCFoundation/UA-Nodeset/blob/latest/DI/Opc.Ua.Di.NodeSet2.xml. The repository also contains the XML Schema Definition corresponding to the NodeSet format: https://github.com/OPCFoundation/UA-Nodeset/blob/v1.05/Schema/UANodeSet.xsd.

2.2. Known Issues

Codegen is currently supporting OPC UA Specification up to version 1.05.02. Since the specifications are still evolving over time, and the OPC Foundation is constantly adding new data types in new versions, models that take advantage of those features might not always work without further updates to the Codegen itself.

In addition, there are some well known issues, which are listed below:

Possible name clashes related to method names
  • Codegen will generate a setter and getter for each UA Variable of an UA Object, e.g. getVar() and setVar(…​). If you define an UA Method with the same name, e.g. getVar or setVar, and the same argument type inside the same UA Object, then you will get a name clash. Additionally same kind of conflict can happen if a model defines names used by the SDK base API which the generated classes extend.

  • The generated Java methods do not include the UA namespace in any way. This means that if an UA type defines multiple components with the same BrowseNames that only differ by their NamespaceIndices, then it will result in a name clash.

In both cases currently either the model has to be changed, or one of the components be excluded (starting from 3.1.8) from generation. In the case where there is a conflict between an UA Method and a component, we recommend excluding the component, as excluding a Method will also remove it’s handling logic. See Excluding sub-nodes under a Type node from generation for more information.

Multiple types having same name within the same Namespace

The OPC Foundation Best Practices is giving guide lines that define rules for naming OPC UA types. However, it is possible to create a NodeSet that contains multiple types with the same name (that would result into the same fully-qualified Java class name).

Codegen as of version 5.0.0 tries to resolve this by default using separate subpackages for different NodeClass (ObjectTypes, VariableTypes, DataTypes) outputs. However, if same NodeClass contains duplicate names, Codegen is not capable of handling those and will report the situation and halt.

Missing SymbolicNames when BrowseNames are not suitable names to be used in generated code

The specification makes the option to define a SymbolicName in the model when the BrowseName is not usable in generated code. Some models are missing these, but Codegen will try to guess a suitable name if necessary. In general, it would be better to fix the model, nevertheless.

3. Using Codegen

Codegen can be currently used in two alternative ways:

In both cases, the generated code will compile and work in Java 8 or later.

3.1. Using Codegen from Command Line

In order to run Codegen from the command line you will need the java command to be available in the PATH. Codegen requires Java 8, 11, 17 or 21. It is not tested on newer than 21, but it should work (contact support if not).

The parameters of the code generation can be modified through a configuration XML file. Please see the sample configuration files in the configexamples folder. Also see the Configuration chapter for more general information on configuring the code generation procedure.

Codegen is designed to be launched via the supplied startup scripts that are in the codegen/bin directory: codegen.bat for Windows and codegen.sh for other operating systems.

The following arguments can be passed to the scripts:

Table 1. Codegen command line arguments
Argument Info

-c <path>

Path to the configuration XML file.

This is NOT the NodeSet XML, but the configuration for the Codegen itself. See Configuration chapter for more information.

-v

Output debug log

-h

Display help

On Windows, you can run Codegen with:

codegen.bat -c path_to_configuration_file

For example, on Windows PowerShell running the Codegen for the SampleTypes.xml model (in commandline/models folder) the call could look like this (in working directory commandline):

.\bin\codegen.bat -c .\configexamples\sampletypesconf.xml

On other operating systems, you can run Codegen with:

./codegen.sh -c path_to_configuration_file

3.2. Using Codegen with Maven Integration

If you use Apache Maven as your build tool, you can use the included Maven plugin for Codegen.

This tutorial does not cover Maven usage in general. Please, visit Apache Maven website for more information.

The Maven plugin for Codegen is provided in the codegen/maven-integration/maven-install-helper folder. To install it to your .m2 local repository, go to this folder and run

mvn install

Once installed, you can test the integration with the provided sample project. Go to codegen/maven-integration-sample folder and run

mvn generate-sources
The generated classes are not added to the build path automatically. You can use the Build Helper Maven Plugin to achieve that.

The parameters of code generation can be modified by configuring the Codegen plugin in the associated 'pom.xml' file. See the Configuration chapter for more general information on configuring the code generation procedure.

The plugin should work on relatively recent Maven 3 versions. If you encounter an error and are using Java 11 or later, please first try updating Maven to the latest version.

4. Configuration

This section explains some of Codegen’s configuration and usage concepts.

4.1. Mapping OPC UA NamespaceUris to Java Packages

OPC UA uses the concept of namespaces to separate between types defined in different models. Each namespaces is identified by a globally unique NamespaceUri (for example, the NamespaceUri of the OPC UA standard namespace is http://opcfoundation.org/UA/).

OPC UA Servers use NamespaceArrays, where they keep the namespaces that they support and they often use a concept called NamespaceIndex to refer to a namespace (the index for the uri in the array), instead of using the NamespaceUri directly. This saves space for the messages client and server exchange. The NodeSet files also use a local NamespaceArray and the NodeId and BrowseName definitions refer to the namespaces using the indices.

Note that the NodeSet indices are local to that file. They are not the same as the server NamespaceArray mappings! Basically each Server NamespaceArray and NodeSet file is it’s own context for the mappings.

Thus, the Codegen cannot use NamespaceIndex, because it doesn’t know where the outputs are used and they could be potentially used in multiple conflicting NamespaceArrays. And it must load multiple NodeSets, as a NodeSet (i.e. UA namespace, in practice) can depend on another NodeSet. Thus it will always use the NamespaceUri to identify the namespaces.

Java programming language, on the other hand, uses packages to organize classes in Java namespaces. Thus, Codegen will generate classes in each OPC UA namespace to a respective Java package and the mapping between these is defined in the Codegen Configuration.

4.1.1. Namespace Prefix

Some Codegen outputs are always named the same way: Ids, UaIds, CommonInformationModel, ClientInformationModel, ServerInformationModel, and DataTypeDictionaryHelper. Thus, if you generate more than one model, you will end up with multiple classes with same names (in different Java packages). It is only possible to import a single one of these in Java code, thus for the rest you would need to use the fully qualified name.

You can define a unique prefix for each namespace mapping to avoid this issue. For example, for the Device Information common model typically the prefix 'Di' is used. Thus, instead of needing to use the fully qualified name org.opcfoundation.ua.di.ClientInformationModel the prefix causes the class name to be DiClientInformationModel, which can be imported.

SDK internally has already one set of these from the core namespace. If possible, it is recommended to set a prefix for each namespace you generate.

4.2. Generation Targets

Codegen generates Java classes from types (ObjectTypes, VariableTypes, ReferenceTypes and DataTypes) defined in OPC UA information models.

If your models include instances (Objects and Variables), Codegen ignores these, since it can only generate Java classes for UA types. However, when you load the model to your server at startup, these instances will be created in the address space by the SDK. See the Server Tutorial for more information.

Codegen can create the following Java interfaces and classes for each OPC UA ObjectType and VariableType that is defined in the models (using the AnalogItemType defined in the OPC UA Core model as an example):

  • Common interface that has the same name as the UA type: AnalogItemType

  • Base class for the client side: AnalogItemTypeImplBase

  • Actual class for the client side: AnalogItemTypeImpl

  • Base class for the server side: AnalogItemTypeNodeBase

  • Actual class for the server side: AnalogItemTypeNode

The base classes should not be modified after generation. The actual classes can be modified, but note that Codegen will override the changes when you regenerate. So you should copy the actual classes to version control to protect them from unwanted changes..

For each UA Method defined in an ObjectType, the following Java files are also created (using the ResendData Method of the ServerType as an example):

  • Parameter object class for each Method that has more than one output argument. It is used as the return value for the method. It is generated as a nested class of the Common interface.

  • Helper interface for providing an external method implementation: ServerTypeResendDataMethod.

Additionally, the following classes are generated per model:

  • Structure class for each UA Structure DataType

  • Enumeration enum for each UA Enumeration DataType

  • UaOptionSet class for each UA OptionSet DataType

  • DataTypeDictionaryHelper class, which is used internally by the SDK to handle custom structures

  • CommonInformationModel class, which will holds UaDataTypeSpecifications

  • ClientInformationModel class that is used to register the generated classes on the client side

  • ServerInformationModel class that is used to register the generated classes on the server side

  • Ids class which contains the identifiers (as ExpandedNodeId) for each generated type and their subcomponents

  • UaIds class which contains the identifiers (as UaNodeId) for each generated type and their subcomponents

If a namespace is mapped with a prefix, it will be used to prepend the Ids and InformationModel classes. For example, for a prefix Test the output would be TestIds, TestClientInformationModel and TestServerInformationModel.

4.2.1. Target Configuration

Codegen enables you to configure which targets you wish to generate by editing the configuration files. For this purpose, the following target names are defined:

Table 2. Possible generation targets
Target Description

common

Generate Java interfaces for each type in the model plus Ids, UaIds, Structures, Enumerations, OptionSets and helper classes. These are not designed for editing.

client_base

Generate client-side base class for each type. Not designed for editing.

client_impl

Generate client-side actual class for each type (extends base classes, can be edited)

server_base

Generate server-side base class for each type Not designed for editing.

server_impl

Generate server-side actual class for each type (extends the base classes, can be edited)

server_model

Special Copy the used information model file to the server-side package

client_model_provider, server_model_provider

Special Writes META-INF/services descriptor files com.prosysopc.ua.client.ClientCodegenModelProvider com.prosysopc.ua.server.ServerCodegenModelProvider. Used for automatic detection of generated models for the SDK. See Automatic Model Registration for more information.

As there can be only one of such files per output folder, Codegen appends to that file (unless it already had the provider that would be appended).
Table 3. Additional generation targets that are combinations of the previous targets
Target Description

client_all

Combination of client_base, client_impl.

server_all

Combination of server_base, server_impl.

base_all

Combination of common, client_base, server_base.

impl_all

Combination of client_impl, server_impl.

all_code

Combination of everything except server_model, i.e. all .java output.

all_resources

Combination of client_model_provider, server_model_provider and server_model, i.e. all other than .java output.

all

Combination of every target

If you generate the base and actual (i.e. "impl") targets in different code generation configurations, remember that both are always required.

4.3. Configuration Parameters

Codegen can be configured with a few configuration parameters. In the command line version of Codegen, the changes need to be implemented in a configuration XML file. In the Maven plugin version, the parameters are defined in the POM XML file. Please also see the provided examples for more complete examples of the configuration process. The examples for the command line version are in the 'configexamples' folder and the 'maven-integration-sample' Maven project provides examples for configuring the Maven integration.

4.3.1. Model Directories

The modelSources parameters define the directories (or files directly) where Codegen is searching for information models that are used in the code generation. The files must conform to the OPC UA NodeSet2 XML format.

In the command line version, the models are defined in the following way:

<modelSources>
	<modelSource>${app.home}/models</modelSource>
</modelSources>

The Maven plugin is configured the same way (but you would use e.g. ${project.basedir}/models as the setting value.

4.3.2. Namespace Mapping Parameters

The namespace mapping parameters define how the OPC UA namespaces that are defined in the information models are mapped to their respective Java packages. See chapter Mapping OPC UA NamespaceUris to Java Packages for an explanation of the concept. Each mapping consists of the following parameters:

Parameter Description

uri

The NamespaceUri associated with the information model used as the input of the code generation.

packageName

The name of the Java package where the Java code output for the information model is placed.

prefix

Optional The prefix used in the name of the ServerInformationModel, Ids and UaIds classes, e.g., prefix value 'Di' outputs the class DiServerInformationModel. It is recommended to set this, assuming you can come up with a shorthand name for the model.

nodeClassSpecificPackageNames

Optional Default true if not set. Controls whether or not NodeClass-specific subpackages are used when generating.

The namespace mapping parameters are defined as follows (In both command line version and the Maven plugin configuration):

<namespaceMappings>
	<namespaceMapping>
		<uri>http://ua.prosysopc.com/SampleTypes</uri>
		<packageName>example.packagename</packageName>
		<prefix>Sample</prefix>
	</namespaceMapping>
</namespaceMappings>

Starting from 5.0.0, a separate subpackage is used for each NodeClass, by default. This can be disabled by setting nodeClassSpecificPackageNames as false. For example, for the Device Information (DI) model, the configuration could look like:

 <namespaceMapping>
 	<uri>http://opcfoundation.org/UA/DI/</uri>
 	<packageName>com.prosysopc.ua.types.di</packageName>
 	<prefix>Di</prefix>
 	<nodeClassSpecificPackageNames>false</nodeClassSpecificPackageNames>
< /namespaceMapping>

Note that this will still create a separate subpackage for the client and server specific implementations:

com.prosysopc.ua.types.di
com.prosysopc.ua.types.di.client
com.prosysopc.ua.types.di.server

By default (when nodeClassSpecificPackageNames is true), the following packages will be created:

com.prosysopc.ua.types.di
com.prosysopc.ua.types.di.datatypes
com.prosysopc.ua.types.di.objecttypes
com.prosysopc.ua.types.di.variabletypes
com.prosysopc.ua.types.di.client
com.prosysopc.ua.types.di.objecttypes.client
com.prosysopc.ua.types.di.variabletypes.client
com.prosysopc.ua.types.di.server
com.prosysopc.ua.types.di.objecttypes.server
com.prosysopc.ua.types.di.variabletypes.server

Note that data types do not have any client/server specific subpackages as they are common for both.

4.3.3. Generation Target Parameters

The concept of the generation target is explained in more detail in the chapter Generation Targets.

Parameter Description

targets

Defines what is the output of the code generation, a comma separated list of Generation Targets

uri

The NamespaceUri associated with the information model used as the input of the code generation

outputs

Definition of output folders (see below examples)

timestampOutputs

Optional (default true), creates timestamp of the generation in the javadoc of generated classes. Set to 'false' to disable.

In the command line version, the generation target parameters are defined in the following way:

<generates>
	<generate>
		<targets>all</targets>
		<uris>
			<uri>http://ua.prosysopc.com/SampleTypes</uri>
		</uris>
		<outputs>
			<code>${app.home}/sampletypes/output_code</code>
			<resources>${app.home}/sampletypes/output_resources</resources>
		<outputs>
	</generate>
<generates>

In the Maven plugin, the configuration is the same, but typically you would define the output locations as properties e.g. ${codegen.output.code} and ${codegen.output.resources}, that would be then defined e.g. as:

<properties>
	...
	<codegen.output.code>${project.build.directory}/codegenoutput/code</codegen.output.code>
	<codegen.output.resources>${project.build.directory}/codegenoutput/resources</codegen.output.resources>
	...
</properties>

Then you would use the build-helper-maven-plugin to include them as extra sources and resources. See the 'codegen/maven-integration/maven-integration-sample/pom.xml' for more information.

4.3.4. Excluding sub-nodes under a Type node from generation

The generation of some models might result in generated code that conflicts with the base SDK API. Or there might be a name clash when generating more than one model. Currently the only workaround (outside of editing the model itself) is to exclude those nodes when generating get/set/getXXXNode methods for the type. An excluded sub-node behaves in the context of the generation as-if it were never present in the Type under which it is.

In the both versions they can be excluded:

<excludes>
	<instanceDeclarations>
		<instanceDeclaration>namespace_uri:name_part_of_BrowseName</instanceDeclaration>
	</instanceDeclarations>
</excludes>

For example, in order to exclude the PowerInput subnode (that is used in ValveObjectType in the SampleTypes model), you would use the following configuration:

<excludes>
	<instanceDeclarations>
		<instanceDeclaration>http://ua.prosysopc.com/SampleTypes:PowerInput</instanceDeclaration>
	</instanceDeclarations>
</excludes>
Excluding a component does not remove it from the AddressSpace of the server, it just means that the generated accessor for it wont be available. It is possible to manually search nodes below nodes e.g. via UaNode.getComponent (for HasComponent reference targets), UaNode.getProperty (for HasProperty reference targets) and via UaNode.getReferences (and checking the target of the returned references). Excluded Methods do not create the handling logic, and must be done via a listener (see MyMethodManagerListener in the samples).
Currently the exclude excludes it from all types, as the main use case is to avoid name clashes with the SDK API.
Starting from 5.1.0, Codegen will automatically exclude some typical BrowseNames (such as 'Description', 'DisplayName'; for every namespace) that would conflict with the base SDK API.

4.3.5. Excluding output classes

This is an advanced option. One use-case for this is if you would e.g. only implement some of the methods for XXXTypeNode outputs, you might wish to include the implemented ones in source control and still generate the rest during build.

If you need to exclude Java class from the outputs, you can use the following:

<excludes>
	<outputClasses>
		<outputClass>fully.qualified.java.name</outputClass>
	</outputClasses>
</excludes>
This will only disable the writing of the file to the disk, the "generation" of it must still succeed. Also, the type can still be used in other types and must be present on the classpath.

4.4. Sample configuration files

This is a basic example of a configuration file for the command line version. Note that the same file is available from the codegen/commandline/configurationexamples folder

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<codegenConfiguration>

        <!-- Mapping of UA Namespace -> Java package -->
        <namespaceMappings>
                <!-- Add mapping to your model(s) namespace(s) here -->
                <!-- Each mapping must have unique java package name -->
                <namespaceMapping>
                        <uri>http://ua.prosysopc.com/SampleTypes</uri>
                        <packageName>example.packagename</packageName>
                </namespaceMapping>
        </namespaceMappings>

        <!-- Defines where models are loaded from. Use absolute paths, 'app.home'
                is set by the launcher scripts to parent directory of the launch scripts
                and can be used to make relative paths. Entries can be files or folders.
                All xml files from the folder are assumed to be model files -->
        <modelSources>
                <modelSource>${app.home}/models</modelSource>
        </modelSources>

        <!-- These define which namespace should be generated and which targets
                to use. See the Codegen Manual about the possible Generation Targets. -->
        <generates>
                <!-- Multiple generate blocks are possible -->
                <generate>
                        <targets>all</targets>
                        <uris>
                                <!-- Multiple uris possible -->
                                <uri>http://ua.prosysopc.com/SampleTypes</uri>
                        </uris>
                        <outputs>
                                <code>${app.home}/sampletypes/output_code</code>
                                <resources>${app.home}/sampletypes/output_resources</resources>
                        </outputs>
                </generate>
        </generates>

        <enhancements>
                <!-- Optionally define a header to generate for each file. Every line is
                        prepended with '//'. -->
                <fileHeader>
                        <line>Generated from SampleTypes</line>
                        <line>by Prosys OPC UA Java SDK Codegen</line>
                </fileHeader>
        </enhancements>
</codegenConfiguration>

Here is another example with the Maven plugin version. Please refer to the full pom.xml in the codegen/maven-integration/maven-integration-sample. Please note that you must have taken the steps explained in the Using Codegen with Maven Integration section before the plugin can work.

<plugin>
        <groupId>com.prosysopc.ua.codegen</groupId>
        <artifactId>prosys-opc-ua-java-sdk-codegen-maven-plugin</artifactId>
        <version>${codegen.plugin.version}</version>
        <executions>
                <execution>
                        <id>run-codegen</id>
                        <phase>generate-sources</phase>
                        <goals>
                                <goal>generate</goal>
                        </goals>
                        <configuration>
                                <modelSources>
                                        <modelSource>${project.basedir}/models</modelSource>
                                </modelSources>
                                <generates>
                                        <generate>
                                                <targets>all</targets>
                                                <uris>
                                                        <uri>http://ua.prosysopc.com/SampleTypes</uri>
                                                </uris>
                                                <outputs>
                                                        <code>${codegen.output.code}</code>
                                                        <resources>${codegen.output.resources}</resources>
                                                </outputs>
                                        </generate>
                                </generates>
                                <namespaceMappings>
                                        <namespaceMapping>
                                                <uri>http://ua.prosysopc.com/SampleTypes</uri>
                                                <packageName>example.packagename</packageName>
                                        </namespaceMapping>
                                </namespaceMappings>
                        </configuration>
                </execution>
        </executions>
</plugin>

5. Using Generated Classes in Applications

5.1. Automatic Model Registration

The generated InformationModel classes must be registered to the SDK before they can be used in applications.

To avoid the manual registration via registerModel (as described in the Client and Server Tutorial), you can use the client_model_provider and server_model_provider Generation Targets to skip this phase in your applications. Both create a special resource file to the resources output folder. If this is included in your application resources, SDK is able to locate and register the respective models automatically.

The SDK uses the standard Java ServiceLoader mechanism to locate the resources, so the generated files (com.prosysopc.ua.client.ClientCodegenModelProvider or com.prosysopc.ua.server.ServerCodegenModelProvider under META-INF/services) must be visible in the Java classpath. This might not be the case by default, if you are using some environment that uses custom ClassLoaders, such as OSGi, so check the respective configuration, if it doesn’t seem to work for you. Additionally it should be stressed that the part from classpath root must be META-INF/services/com.prosysopc.ua.client.ClientCodegenModelProvider and META-INF/services/com.prosysopc.ua.client.ServerCodegenModelProvider for this to work.
These targets are special compared to other Codegen outputs, which replaces any existing files. In order for the above to work, Codegen instead appends to the files, if needed. This allows generating more than one namespace to one configured output (all other outputs will go to their respective Java packages).

5.2. Client-side Applications

For instructions on using the generated classes in client-side applications developed with the Prosys OPC UA SDK for Java, please refer to the Prosys OPC UA SDK for Java Client Tutorial in the 'tutorial' folder of the distribution package.

5.3. Server-side Applications

For instructions on using the generated classes in server-side applications developed with the Prosys OPC UA SDK for Java, please refer to the Prosys OPC UA SDK for Java Server Tutorial in the 'tutorial' folder of the distribution package.

5.3.1. Method Implementations

Instead of defining method implementations in the actual classes, the SDK also supports implementing them out of the actual class. Therefore, the Codegen also generates a method interface which can be implemented and plugged into the actual class. Please see the generated classes to learn how this can be done.

5.3.2. Object Initializations

Additionally, an initializer can be defined for each type, for example to provide initial values. Please see the generated classes to learn how this can be done.