Resource Identifiers
Resource Identifiers
A resource identifier is a class generated in prototypes which represent the URI for a specific resource. Resource identifiers take the <uri/> elements in resource definitions and convert them to Java types, which can then be used in prototype classes.
The main benefit of using a resource identifier class is to avoid having to construct URIs themselves manually: this is prone to human error. Instead, we can either use Builder classes build URIs accurately when needed, or @Inject annotations to inject them into our prototypes. Builder classes also perform runtime validation, ensuring that all necessary components are present. Resource identifier classes also ensure type safety.
For certain API modeling patterns (specifically, 'transforms'), Helix also performs validation on identifier components to ensure that, for example, the caller has access to all referenced Resources.
Simple Identifiers
Consider the following resource definition:
<name>cars</name> <description>Family for entities</description> <uri-part> <name>car-id</name> <description>Identifier for one car</description> </uri-part> <resource> <name>car</name> <description>A car resource.</description> <uri>/{base.family}/{car-id}</uri> <entity>car</entity> </resource>
The URI for the car resource will be /cars/{car-id}. The corresponding resource identifier class will be CarIdentifier.
CarIdentifier is actually an interface, which extends ResourceIdentifier. The interface includes a getCarId() method:
@Property(name = CAR_ID) IdentifierPart getCarId();
It also contains a generated Builder, which is used to construct instances of the identifier:
CarIdentifier myNewIdentifier = CarIdentifier.builder().withCarId(StringIdentifier.of("some-id")).build();
The URI-part can be injected into prototype instances using the @Inject annotation.
To @Inject the car-id URI-part:
@Inject @UriPart(CarIdentifier.CAR_ID) private IdentifierPart<String> carId;
The entire resource identifier, including the the URI-part, can also be injected into prototype instances using the @Inject annotation:
@Inject @RequestIdentifier private CarIdentifier carIdentifier;
Composite Identifiers
Resource identifiers can also be "composed" of other resource identifiers (which, in turn, may be "composed" of other resource identifiers).
Consider the following resource definition:
<name>listcreate</name> <description>Family for list create</description> <uri-part> <name>thing-id</name> <description>Thing id</description> </uri-part> <resource> <name>thing-list</name> <description>Thing list</description> <uri>/{base.family}/things</uri> <list-of>thing</list-of> </resource> <resource> <name>thing</name> <description>Thing resource</description> <uri>{thing-list}/thing/{thing-id}</uri> <entity>thing</entity> </resource>
The URI of thing-list might be /listcreate/things. The URI of the thing resource is a composite identifier: The {thing-list} token contained within the URI of the thing resource is, itself the full identifier of the thing-list resource.
Note: The code-generation tooling will validate your <uri/> elements to ensure that you are assembling your composite URIs correctly.
Examining the generated ThingIdentifier interface, you'll find an accessor method for obtaining ThingListIdentifier:
@Property(name = THING_LIST) ThingListIdentifier getThingList();
There's also an accessor method for obtaining IdentifierPart:
@Property(name = THING_ID) IdentifierPart getThingId();
Composite resource identifiers can be constructed using the generated Builder classes. Builder classes are obtained by calling the static builder() method on the generated Identifier, just as simple identifiers can be constructed:
ThingIdentifier thingIdentifier = ThingIdentifier.builder() .withThingList(ThingListIdentifier.builder().build()) .withThingId(StringIdentifier.of("some-id") .build();
Transform Resource Identifiers
There is a pattern for modeling APIs that is sometimes referred to as a "transform resource". The form-resource example in the Helix pattern catalog is an example of this pattern.
Consider the resource definitions for the form resource, create-order-form, and the form action resource, create-order-action:
<name>forms</name> <description>Forms resource family</description> <resource> <name>create-order-action</name> <description>Action resource for the form</description> <uri>{item}/order</uri> <entity>create-order-form</entity> </resource> <resource> <name>create-order-form</name> <description>Create order form resource</description> <uri>{item}/form</uri> <entity>create-order-form</entity> </resource>
Notice how the URIs for these resources contain {item}. This effectively embeds the URI of the item resource into the URI of the form and action resources, respectively. The URI for {item} is itself a composite identifier: {item-list}/{item-id}.
The fully-expanded URI of the form resource, for example, would be /{base.family}/items/{item-id}/form.
Helix is able to decompose the resource URI into a composite identifier, and verify that the caller has access to all of the referenced resources. In this case, we'd verify that the caller has access to /{base.family}/items/{item-id}. If the caller does not have the necessary access, the operation is rejected.
URI-part Transformers
Typically you will not need to do any additional work to transform URI-parts that you define in your resources, i.e. thing-id. Transformers are provided for basic types but in some cases you might want to override the default behavior.
For example you might want your thing-id to be an un-encoded String. By default StringIdentifier will encode String URI parts. To achieve this you can use PlainStringTransformer instead.
First you need to create an export wrapper for the transformer:
public class ExportPlainStringTransformer { @SuppressWarnings("PMD.UnusedPrivateField") @Inject @Named("plain") private Export<IdentifierTransformer> permissionLookupExport; }
You then need to add some wiring:
public class ImportExportModule extends AbstractModule { protected void configure() { .... bind(ExportPlainStringTransformer.class).asEagerSingleton(); bind(export(IdentifierTransformer.class)) .annotatedWith(Names.named("plain")) .toProvider(service(PlainStringTransformer.class) .attributes(names("uri-part=things.thing-id")) .export()); .... } }
For a custom transformer, you need to implement IdentifierTransformer:
@Component(service = IdentifierTransformer.class, property = "uri-part=cars.car-id") public class CarIdTransformer extends PlainStringTransformer { // Not required. For informational purposes only. // Only needed if the behaviour of the method needs to be changed. @Override protected IdentifierPart<String> uriPartToIdentifierPart(final String part) { return super.uriPartToIdentifierPart(part); } }