ResourceTypeMeta
ResourceTypeMeta is a declarative manifest of metadata describing your resource and how the rest of Ignition should interact with it. While it is technically possible to provide your own instance of ResourceTypeMeta, the rest of this page will assume you are using the DefaultResourceTypeMeta.Builder class, which will be provided when calling the method DefaultResourceTypeMeta.newBuilder.
Miscellaneous
There are some useful miscellaneous attributes you can provide for your resource in the meta. These arguements would be applied immediately after providing the required arguments to construct the builder instance.
An important scenario would involve setting the singleton flag if that type applies to your resource:
ResourceTypeMeta.newBuilder(MyConfigResource.class)
.resourceType(MyConfigResource.RESOURCE_TYPE)
.singleton()
Additional useful attributes that can be provided to your resource in the meta include:
- A web accessible icon path
- Whether your resource should be disabled if an end user chooses to restore a Gateway backup in disabled mode
- The default configuration to provide for your resourcenote
This is most useful for singleton resources, but can be provided for named resources as well. This will be consumed by resource handlers, if applicable, as well as other locations such as the REST API.
Serialization
One of the most fundamental roles of the ResourceTypeMeta is to house the single source of truth for how to serialize and deserialize files on disk into the arbitrary Java class that represents your configuration. For most config resources, this can be inferred automatically. If you have a basic Java record class that defines only fields of common/primitive Java types, Gson can automatically infer a serialization strategy for your class. If you do not override anything, DefaultResourceTypeMeta.Builder will automatically create a new instance of GsonResourceCodec with some sane defaults and attempt to serialize your class. If you need to provide a custom adapter (i.e. for a nested class inside your record) you can call the buildGson method:
metaBuilder.buildGson(gson -> gson
.registerTypeAdapter(SecurityLevelConfig.class, new SecurityLevelConfig.GsonAdapter())
.registerTypeAdapter(SecurityLevelsResource.class, new SecurityLevelsResource.GsonAdapter())
)
If you need more control, you can also provide your own ResourceCodec instance entirely:
metaBuilder.withCodec(new CustomResourceCodec())
It is strongly recommended that you provide a subclass of JsonResourceCodec if the base configuration you are providing can be expressed in JSON. This will provide a number of niceties in other subsystems detailed below.
Validation
If you choose to use the default Gson-based serialization, you can take advantage of our common base validation pattern. This allows you to reject basic invalid configuration states (e.g. ports out of range, negative values for timeouts) that can be verified statically. To opt in, pass a ResourceValidator to the withValidator function, or build a custom one in a lambda via buildValidator. The latter receives a ValidationErrors.Builder object that can be used to easily provide contextual error messages:
metaBuilder.buildValidator((config, validation) -> validation.requireNotNull("date", config.date()))
Action Sets
Underlying both the REST API and the system.config scripting namespace is the common ResourceActionSet layer. This is a declarative set of capabilities that your resource can choose to support or not, which will cascade automatically down to the REST API and config script API. You can provide your own ResourceActionSet entirely, or override ResourceActionSet.Default as needed:
metaBuilder.withActionSet(ResourceActionSet.EMPTY) // remove all actions
// or:
metaBuilder.buildActionSet(builder -> builder
.create(null) // removes the "Create" action in particular, but all others are implicitly allowed
)
It is possible to provide a completely custom implementation of any of the basic action set “verbs” (Read, ListResources, Create, Modify, Delete, Move, Copy, Rename), which are defined in ResourceAction, but it is not recommended. Instead, custom behavior should be layered in, and then you should delegate to standard platform behavior as defined in the DefaultResourceActions class.
Validation
If you need validation that cannot be performed statically, but depends on the rest of the system, you can provide custom validating resource actions when constructing your ResourceActionSet. These validators will be provided with decoded resource instances. So, instead of receiving only a static bundle of configuration data, you'll have access to additional helper methods and constraints that can be applied to your custom config class. This allows more complex validation logic, such as:
metaBuilder.buildActionSet(builder -> builder
.withValidatingCreate(DatasourceManagerImpl::validateCreate)
.withValidatingModfy(DatasourceManagerImpl::validateModify)
.withValidatingCopy(DatasourceManagerImpl::validateCopy)
)
REST API
To opt in to the REST API, you must provide or customize a RouteDelegate in your type meta. At minimum, you must provide an open API tag name and group name:
metaBuilder.withRouteDelegate(new DefaultResourceRouteDelegate<>(...))
// or
metaBuilder.buildRouteDelegate(routeDelegateBuilder -> routeDelegateBuilder
.openApiGroupName(MODULE_GROUP_NAME)
.openApiTagName("config-resource")
.addSearchField("title")
)
The base config.json is used by convention for the vast majority of configuration resources. If your resource supports “extra” data files beyond this, pass the information in the builder to expose additional REST API routes:
routeDelegateBuilder.supportsExtraDataFiles(true)
Schema
Building or providing a schema is strongly recommended. You can provide a class reference using the configSchema method and it will be reflectively parsed into a JSON Schema using SchemaUtil.
routeDelegateBuilder.configSchema(MyConfigResource.class) // pass a class to build a schema reflectively
// or
routeDelegateBuilder.configSchema(builder -> {}) // build a schema in a lambda
// or
.configSchema(myExistingSchemaReference) // pass a prebuilt JSON Schema as a JsonObject node
SchemaUtil used reflectively will populate OpenAPI metadata via annotations from the com.inductiveautomation.ignition.gateway.dataroutes.openapi.annotations package. Use these annotations on your base Java class to provide extra information, such as the descriptions and value ranges:
public record AuditProfileConfig(
@Required
@Description("The name of the audit profile type")
String type,
@FormCategory("GENERAL")
@Label("Retention *")
@DescriptionKey("myModule.ConfigObject.retentionDaysDesc")
@FormField(FormFieldType.NUMBER)
@Minimum("0")
@DefaultValue("90")
Integer retentionDays
)
Permissions
When customizing your route delegate, you can override the permissions required for read and write capabilities. In addition, you can override the API capabilities implicitly exposed by your provided ResourceActionSet to a specific set, either by passing an explicit set or modifying the implicit values.
routeDelegateBuilder.withCapabilitySet(Set.of(ApiCapability.List_Names))
// or
routeDelegateBuilder.buildCapabilitySet(apiCapabilities -> apiCapabilities.add(ApiCapability.Describe_Type)
Serialization
If you have a resource that does not ordinarily store its configuration in JSON it is possible to provide a custom JsonResourceCodec instance from your RouteDelegate, separate from the one used at the base level. This can also be used for resources that want to accept a certain format of JSON from the REST API, but not use that format for storage.
Searching
By default, the “list” routes that are exposed for every named resource search the name, description, enabled, and mode attributes of each configuration resource. If your resource contains other fields that end users may want to use as a search axis, you can add them using the addField or addSearchField methods:
routeDelegateBuilder.addSearchField("connectURL")
References
To encode links between resources (e.g. a database should not be able to be removed if it is being used by a Database Alarm Journal) you can provide a ReferenceDelegate during build construction, either by passing one explicitly or via construction:
metaBuilder.withReferenceDelegate(someReferenceDelegate)
// or
metaBuilder.buildReferenceDelegate(referenceBuilder -> referenceBuilder
.referenceProperty(
"defaultTranslator",
reference -> reference
.targetType(DbTranslatorResource.RESOURCE_TYPE)
.value(JdbcDriverResource::getDefaultTranslator)
.caseSensitive(true)
.onUpdate(JdbcDriverResource::setDefaultTranslator)
)
)
These references will be automatically consumed by the REST API, preventing potentially destructive updates unless users explicitly opt to allow the update.
Auditing
By default, if a Gateway Audit Profile is specified, changes to resources will be logged in a human readable format to the audit log. If you wish to customize how your resource is formatted into the audit log (e.g. if you have a resource that does not primarily store its config as JSON) or you want to opt out of automatic auditing entirely, you can provide your own ResourceAuditDelegate during meta builder construction:
metaBuilder.withAuditDelegate(ResourceAuditDelegate.DISABLED)
Ultimately the ResourceAuditDelegate is responsible for providing an AuditRecord (or an empty Optional) given a particular change operation, by implementing the the following signature:
default Optional<AuditRecord> auditChangeOp(
ChangeOperation operation,
Resource resource,
AuditContext auditContext
)
Status
To provide health checks and system metrics to Ignition about your custom resource, you can provide or build a custom ResourceStatusDelegate implementation in your type meta.
In the simplest form, you can reference a status value that's provided separately to the metric system in your own code:
metaBuilder.withStatusDelegate(myStatusDelegate)
// or
metaBuilder.buildStatusDelegate(statusBuilder ->
statusBuilder.instanceHealthCheck("status", "database-driver.%s.status")
)
Or you can provide an entire set of metrics and healthchecks, including for the resource category as a whole:
metaBuilder.buildStatusDelegate(status -> status
.instanceMetric("throughput", getMetricName("%s", "throughput") )
.instanceMetric("queries", getMetricName("%s", "queries") )
.instanceMetric("rows", getMetricName("%s", "rows") )
.instanceMetric("active-connections", getMetricName("%s", "active-connections") )
.instanceHealthCheck("status", getMetricName("%s", "status") )
.instanceMetric("active-queries", getMetricName("%s", "active-queries") )
.instanceMetric("expensive-queries", getMetricName("%s", "expensive-queries") )
.categoryMetric("active-connections", "databases.active-connections")
.categoryMetric("queries", "databases.queries")
)
Overview Pages
All registered resource types are displayed on the Platform Overview page of the Gateway, based on the EntityDelegate provided by the meta builder by default.
You can opt out of this, or customize your resource’s representation on the Platform Overview page, by providing or customizing the EntityDelegate your meta returns. You can also choose to have it appear in your resource’s category overview, or the Diagnostics Overview page.
metaBuilder.withEntityDelegate(ResourceEntityDelegate.NONE) // opt out entirely
// or
metaBuilder.buildEntityDelegate(entityBuilder -> entityBuilder.includeInSectionOverview(true).includeInDiagnosticsOverview(true))
The default behavior is to return a DefaultResourceEntityDelegate constructed with no arguments, which automatically includes some basic functionality.