ResourceController
is a controller that provide conveniences for implementing endpoint controllers. A ResourceController
must be subclassed, and in that subclass, you write a method for each operation on that type of resource. For example, a UserController
might handle the following operations:POST /users
)GET /users
)GET /users/:id
)PUT /users/:id
)DELETE /users/:id
)ResourceController
subclass that has an @Operation
annotation. It must return an instance of Future<Response>
. Here's an example:CityController
handles GET
requests without path variables. To handle operation methods with path variables, the name of the path variable is added to the @Operation
annotation:/cities/[:name]
, so that it can handle both of these operations. Read more about path variables in Routing.Operation
tells us which HTTP method the operation method handles. The following named constructors exist:Operation.post()
Operation.get()
Operation.put()
Operation.delete()
Operation()
constructor takes the HTTP method as its first argument for non-standard operations, e.g.:Operation
constructors take a variable list of path variables. There can be multiple path variables for an operation. An operation method will only be invoked if all of its path variables are present in the request path. There can be multiple operation methods for a given HTTP method, as long as each expects a different set of path variables.ResourceController
subclass must be preceded by a Router
in the application channel. The Router
will parse path variables so that the controller can use them to determine which operation method should be invoked. A typical route to a ResourceController
contains an optional identifying path variable:CityController
to implement operation methods for all HTTP methods with both no path variables and the 'name' path variable./cities/[:name/[attractions/[:id]]]
, while valid, makes controller logic much more unwieldy.apiKey
:@Bind.path(pathVariableName)
@Bind.query(queryParameterName)
@Bind.header(headerName)
@Bind.body()
int
or DateTime
. Simply declare the bound parameter's type to the desired type:String
or any type that implements parse
(e.g., int
, DateTime
). Query parameters may also be bound to bool
parameters; a boolean query parameter will be true if the query parameter has no value (e.g. /path?boolean
).int cityID
- if the path variable 'id' can't be parsed into an int
, a 404 Not Found response is sent. If a query parameter or header value cannot be parsed, a 400 Bad Request response is sent.List<T>
parameters to headers and query parameters, where T
must meet the same criteria as above. Query parameters and headers may appear more than once in a request. For example, the value of ids
is [1, 2]
if the request URL ends with /path?id=1&id=2
and the operation method looks like this:List<T>
.X-API-Key
to the apiKey
parameter:X-API-Key
header is present in the request, its value will be available in apiKey
. If it is not, getAllCities(apiKey)
would not be called and a 400 Bad Request response will be sent. If apiKey
were optional, the method is called as normal and apiKey
is null or a default value.apiKey
will be bound in all cases.cityName
:cityID
:Router
must have a route that includes a path variable and that path variable must be listed in the Operation
annotation. Path variables are case-sensitive and may not be optional.Operation
, you will get a runtime exception at startup. You do not have to bind path variables for an operation method to be invoked.Bind.body()
doesn't take any identifying arguments (however, it does take optional arguments for ignoring, requiring or rejecting keys; this matches the behavior of Serializable.read
and only works when the bound type is a Serializable
or list of).City
in this example) must implement Serializable
. Conduit will automatically decode the request body from it's content-type, create a new instance of the bound parameter type, and invoke its read
method. In the above example, a valid request body would be the following JSON:read
throws an exception, a 400 Bad Request response will be sent and the operation method won't be called.List<Serializable>
parameters to the request body. Consider the following JSON that contains a list of cities:List
of the desired type:Content-Type
is 'x-www-form-urlencoded', its must be bound with Bind.query
and not Bind.body
.ResourceController
s may also have Bind.query
and Bind.header
metadata. This binds values from the request to the ResourceController
instance itself, making them accessible from all operation methods.timestamp
and limit
are bound prior to getCities
being invoked. By default, a bound property is optional. Adding an requiredBinding
annotation changes a property to required. If required, any request without the required property fails with a 400 Bad Request status code and none of the operation methods are invoked.ResourceController
s have some other behavior that is important to understand.ResourceController
can limit the content type of HTTP request bodies it accepts. By default, a ResourceController
will accept only application/json
request bodies for its POST
and PUT
methods. This can be modified by setting the acceptedContentTypes
property in the constructor.ResourceController
prior to your operation method being invoked. Therefore, you can always use the synchronous RequestBody.as
method to access the body from within an operation method:ResourceController
can also have a default content type for its responses. By default, this is application/json
. This default can be changed by changing responseContentType
in the constructor:responseContentType
is the default response content type. An individual Response
may set its own contentType
, which takes precedence over the responseContentType
. For example, the following controller returns JSON by default, but if the request specifically asks for XML, that's what it will return:ResourceController
subclasses will execute queries. There are helpful ResourceController
subclasses for reducing boilerplate code.QueryController<T>
builds a Query<T>
based on the incoming request. If the request has a body, this Query<T>
's values
property is read from that body. If the request has a path variable, the Query<T>
assigns an expression to the primary key value. For example, in a normal ResourceController
that responds to a PUT request, you might write the following:QueryController<T>
builds this query before a operation method is invoked, storing it in the inherited query
property. A ManagedObject<T>
subclass is the type argument to QueryController<T>
.ManagedObjectController<T>
is significantly more powerful; you don't even need to subclass it. It does all the things a CRUD endpoint does without any code. Here's an example usage:GET /users
- can be modified with query parameters. For example, the following request will return the users sorted by their name in ascending order:offset
, count
, pageBy
, pageAfter
and pagePrior
.ManagedObjectController<T>
can also be subclassed. A subclass allows for callbacks to be overridden to adjust the query before execution, or the results before sending the respond. Each operation - fetch, update, delete, etc. - has a pair of methods to do this. For example, the following subclass alters the query and results before any update via PUT
: