Hello, Tapestry community! I've been working on something I really wanted Tapestry to have but haven't had a chance to implement myself until some time ago: proper REST support. And not do it by integrating with some other framework like Jersey or CXF or by being a JAX-RS implementation, but by doing it in a very Tapestry way: event handler methods, TypeCoercer for type conversions, everything being customizable by contributions to Tapestry-IoC services, return value of event handler methods handled by ComponentEventResultProcessor implementations, etc.
This is a work in progress, done in the "rest" branch, so I decided to publish a snapshot (5.8.0-SNAPSHOT) so you can use it yourself and tell me what you feel about it. All feedback is welcome! Major points: - New page-level events are triggered after the "activate" event is triggered (i.e. onActivate()) so you can write REST endpoints as regular, live-class-reloaded event handler methods. - One for each supported HTTP method: GET, POST, DELETE, PUT, HEAD, PATCH. - Event name is onHttp[method name]. For example, onHttpPut. - New constants in EventConstants for each supported HTTP method. - Activation context for REST event handler methods exactly in the same way as in onActivate(). - Just like onActivate(), these new events are not triggered in components. - New service in tapestry-http (which is included by tapestry-core), RestSupport, with utility methods and <T> Optional<T> getRequestBodyAs(Class<T> type), which returns the request body converted to a given type. - getRequestAsBody(Class) uses HttpRequestBodyConverter. - Another new service in tapestry-http, HttpRequestBodyConverter, which uses an ordered configuration of itself, which provides conversions of request body to given types. - Only one method, <T> T convert(HttpServletRequest request, Class<T> type); - tapestry-http contributes one implementation, which is added as the last one in the ordered configuration, which uses TypeCoercer. - Out of the box, it supports conversions to String, primitive types, JSONArray, JSONObject plus any other type that has a String -> type conversion available in TypeCoercer, directly or not. - New annotation for page event handler method (onActivate(), onHttp*) parameters: @RequestBody, which receives the request body and converts it to the parameter type using RestSupport.getRequestAsBody(). - Think of it as @RequestParameter but for the request body instead of a query parameter. - Example below. - New annotation for page event handler method (onActivate(), onHttp*) parameters: @StaticActivationContextValue("something"), for defining that method will only be called if that page activation context value matches a given string. - Example below. - Parameters with that annotation still get their values set as usual. - This was built mostly for REST support, but it can useful for non-REST situations too when a give activation context has a number of possible values and there's some logic tied to it. - Automatic generation of Swagger/OpenAPI 3.0 REST API definition files. - OpenApiDescriptionGenerator service, which is an ordered configuration of itself. - An internal implementation generates a base JSON object definition that can be customized by contributing more OpenApiDescription implementations. - Titles, names, summaries, etc can be provided by configuration symbols or message files (app.properties). - Messages files are live class reloaded too, so all changes done to the REST endpoint event handler methods and the corresponding messages are live reloaded. - Formats for message keys and described below. - Parameter descriptions not implemented yet. - It will support query parameters and path parameters. - [Not implemented yet] Embedded Swagger/OpenAPI viewer (i.e. the right pane of https://editor.swagger.io) hooked directly to the definition file generated by OpenApiDescriptionGenerator - It's going to be a separate subproject/JAR to avoid bloating projects not using the viewer @StaticActivationContextValue example: final private String COMPLETED = "completed"; final private String CLOSED = "closed"; // Only called if first page activation context value is "completed" @OnEvent(EventConstants.ACTIVATE) void completed(@StaticActivationContextValue(COMPLETED) String state, int id) { ... } // Only called if first page activation context value is "closed" @OnEvent(EventConstants.ACTIVATE) void closed(@StaticActivationContextValue(CLOSED) String state, int id) { ... } REST endpoint event handler method example: Object onHttpPatch( @StaticActivationContextValue("subpath") String subpath, String parameter, @RequestBody String body) { ... } @OnEvent(value = EventConstants.HTTP_DELETE) Object delete(@StaticActivationContextValue("subpath") String subpath, String parameter) { return createResponse(EventConstants.HTTP_DELETE, null, parameter); } Happy coding! -- Thiago