Extending Ignition with Extension Points
As described in the Extension Points section, extension points are hooks that allow modules to implement new versions of abstract ideas, such as a new type of authentication profile or a new alarm journal.
How Extension Points Work
Various parts of Ignition have been defined as "extension points", our name for an exposed capability that we expect to be provided by both first and third party code. Common extension points defined by the Ignition platform, or first party modules, include:
- Alarm Notification profiles
- Audit logging destinations
- Tag History Providers
- Realtime Tag Providers
- User sources
- OPC UA devices
- Cloud Connector modules
Each of these defines an extension point, allowing our (and your) code to provide extended functionality. You can reference the All Known Implementing Classes header in Javadocs for ExtensionPoint to view a more complete list of public first party extension points.
When Ignition starts up, your module's Gateway hook will be asked for any extension point implementations it provides. When a user chooses to create a new instance of something, such as an authentication profile, the system will enumerate known types and display them. Once the user selects one, an internal "manager" will create the necessary configuration files for both the base settings (controlled by the extension point itself) and any profile data that is needed for your custom implementation.
Generally, each extension point will be an abstract class or interface for you to extend in your code. That base class will define a 'create' function, which will be called automatically to create a new instance of your implementation. The exact mechanism will vary depending on which extension point you are implementing, but the general principle is the same across them all.
At a high level, the process for creating a new extension point implementation is as follows:
Create a Class that Implements the Desired Type of Object
Ultimately, the goal is to provide your own implementation of functionality to some piece of the system before you worry too much about the other scaffolding needed in Ignition. Below is a minimal example of a custom class named MyAlarmNotificationProfile that implements the AlarmNotificationProfile interface:
import static com.inductiveautomation.ignition.testproject.gateway.profile.MyAlarmNotificationProfileExtensionPoint.MY_ALARM_WEBHOOK;
public class MyAlarmNotificationProfile implements AlarmNotificationProfile {
private final GatewayContext context;
private final String profileName;
public MyAlarmNotificationProfile(GatewayContext context,
DecodedResource<ExtensionPointConfig<AlarmNotificationProfileConfig, ?>> profileRecord) {
this.context = context;
this.profileName = profileRecord.name();
}
@Override
public String getName() {
return profileName;
}
@Override
public String getProfileType() {
return MyAlarmNotificationProfileExtensionPoint.TYPE_ID;
}
@Override
public ProfileStatus getStatus() {
return profileStatus;
}
public Collection<NotificationProfileProperty<?>> getProperties() {
return List.of(MyAlarmProperties.MESSAGE);
}
@Override
public Collection<ContactType> getSupportedContactTypes() {
return List.of(MY_ALARM_WEBHOOK);
}
@Override
public void sendNotification(final NotificationContext notificationContext) {
// do something useful
}
}
The rest of the extension point system is for bookkeeping and linking in your class.
Define Your Extension Point Implementation
Each system in Ignition that exposes an extension point will define an abstract implementation of the ExtensionPoint interface. In addition to default implementations, it will also define a function to instantiate an instance of the type. For example, the defining function for AlarmNotificationProfileExtentionPoint is createNewProfile():
@Override
public AlarmNotificationProfile createNewProfile(GatewayContext gatewayContext, DecodedResource<ExtensionPointConfig<AlarmNotificationProfileConfig, ?>> decodedResource, MyAlarmNotificationProfileResource myAlarmNotificationProfileResource) throws Exception {
return new MyAlarmNotificationProfile(gatewayContext, decodedResource, myAlarmNotificationProfileResource);
}
Your extension point definition will implement this function to create your implementation at runtime. In the example above you can see the function returns a new instance of MyAlarmNotificationProfile, an object of the same class defined in the example for Create a Class that Implements the Desired Type of Object.
Define Settings Required for Implementation
If your implementation has its own settings, you can define your own resource in the form of a Java record class to hold them. The resource will then be referenced in your Extension Point implementation as a settings object.
For example, the Alarm Notification extension point is generic on the settings object, so when you define MyAlarmNotificationExtensionPoint you provide your MyAlarmNotificationProfileResource type as a parameter:
public class MyAlarmNotificationProfileExtensionPoint extends AlarmNotificationProfileExtensionPoint<MyAlarmNotificationProfileResource> {
...
}
It's worth noting that in this example, a settings object was made for this extension point, but that is not required. Use void for extension points that do not need any settings.
Provide Your Extension Point Factory in Your GatewayModuleHook
The final task is to provide your extension to the base extension point. All extension points your module implements, even if they are from disparate subsystems, must also be returned by your hook class from the getExtensionPoints() method. The core extension point will automatically filter this list and know the existence of your factory implementations.
For example, a custom AlarmNotificationProfileExtensionPoint named MyAlarmNotificationProfileExtensionPoint, would be declared in your GatewayModuleHook as:
@Override
public List<? extends ExtensionPoint<?>> getExtensionPoints() {
return List.of(new MyAlarmNotificationProfileExtensionPoint());
}