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

Reply via email to