Create a custom resource artifact
This example publishes /main/counter directly from a CArtAgO artifact. The agent sets the counter through an operation; HTTP clients can read it but cannot write it. Use this pattern when a Java-Types data class does not capture your resource’s behaviour.
Requires ASTRA 2.6.1, MAMS 2.4.1, Java 17 or later and Maven. Download the complete project. Its POM uses the same dependencies as the first service, so the standard resource base is available while the child is custom.
Implement the artifact
package example;
import java.util.List;
import cartago.ARTIFACT_INFO;
import cartago.OPERATION;
import cartago.OUTPORT;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import mams.artifacts.ResourceArtifact;
import mams.handlers.ResourceHandler;
import mams.web.ResponseEntity;
import mams.web.WebServer;
@ARTIFACT_INFO(outports = { @OUTPORT(name = "out-1") })
public class CounterArtifact extends ResourceArtifact {
private volatile int value;
@OPERATION
void init(String name) {
this.name = name;
defineObsProperty("value", 0);
}
@OPERATION
void set(int next) {
value = next;
updateObsProperty("value", next);
}
@Override
protected ResourceHandler createHandler() {
return new ResourceHandler(this);
}
@Override
public List<String> representationMethods() {
return List.of("GET");
}
@Override
public String representationType() {
return "application/json";
}
@Override
public boolean handle(ChannelHandlerContext ctx, FullHttpRequest request) {
if (!HttpMethod.GET.equals(request.method())) {
WebServer.writeResponse(ctx, request,
ResponseEntity.type("application/json").status(HttpResponseStatus.METHOD_NOT_ALLOWED)
.header("Allow", "GET")
.body("{\"error\":\"method_not_allowed\",\"message\":\"Only GET is supported\"}"));
return true;
}
WebServer.writeResponse(ctx, request,
ResponseEntity.type("application/json").body("{\"value\":" + value + "}"));
return true;
}
}
ResourceArtifact supplies routing support and metadata operations. The output port lets the artifact attach to its parent. init receives the name supplied by the agent; set changes the value and its observable property. volatile makes the most recent integer visible to the HTTP handler thread. It does not make a multi-field business update transactional.
ResourceHandler routes requests to handle and attaches the configured headers and links. The handler serves JSON for GET and returns 405 with Allow: GET for other methods. The representation metadata also declares GET and JSON, matching the handler.
This intentionally small handler always returns JSON for a successful GET. It does not negotiate Accept, provide HAL/JSON-LD conversions or perform an active agent decision. Those behaviours must be implemented or delegated explicitly in a custom artifact; they are not automatically inherited from the Java-Types implementation. Use Accept: application/json for this example.
Create and initialize it from ASTRA
agent Main extends mams.MAMSAgent {
plan +!main(list args) {
mams.MAMSAgent::!setup();
mams.MAMSAgent::!created("base");
mams.MAMSAgent::!createResource("counter", "base", "example.CounterArtifact",
["counter"], cartago.ArtifactId id);
cartago.operation(id, set(42));
mams.MAMSAgent::!setResourceHeader("counter", "Cache-Control", "no-store");
console.println("Counter ready at /main/counter");
}
}
!createResource creates the artifact beneath base, connects its route, focuses the agent and records artifact("counter", QualifiedName, Id) and its URI. The parameter list ["counter"] supplies the argument to init(String name); the output id identifies the created artifact.
The agent calls set(42) and adds a cache header. The resource’s self link comes from creation. The operation does not need an incoming HTTP request: agents can create and initialize their environment directly.
Read it and test method support
Extract the project and run mvn clean compile astra:run. Leave it running and use a second terminal:
curl -i -H 'Accept: application/json' http://localhost:9000/main/counter
curl -i -X PUT -H 'Content-Type: application/json' -d '{"value":7}' \
http://localhost:9000/main/counter
curl -i -H 'Accept: application/json' http://localhost:9000/main/counter
The GETs return 200 and {"value":42}. Their headers include Cache-Control: no-store and a self link. PUT returns 405 with Allow: GET, and leaves the value unchanged. Stop the service with Ctrl+C.
This is method support enforced by the custom handler, rather than an active request refusal: a Java-Types active policy would emit an event and could return 403 for a supported but refused write.
Extend it deliberately
Change set(42) to another value and rerun the example. Then consider a timer-driven agent that updates the counter through the same operation. If you add HTTP writes, define validation, accepted media types, error responses and ownership before implementing them. To publish updates through WebSub, connect the resource to a hub and explicitly publish its representation; see Following resources with WebSub.
Return to Manage resources.