Thanks for your quick reply, Alex!
Alexander Klimetschek writes:
But anyway my gut feeling tells me that you should not make the
Resource depending on request parameters or HTTP headers. These
should rather define how the resource should be rendered. In your
case the resource would most likely simply the table to be
addressed. Limits, offsets, and other "query" parameters would then
be handled in a servlet that provides a query rendering of this
resource/db table. That would most likely make that servlet and the
resource implementation generic for all tables, but you could still
have specific overrides for special tables (by using a more specific
resource type and a "generic/table" super resource type). In your
Resource implementation you could implement adaptTo to elegantly get
to the underlying JDBC connection or whatever the raw database
access it and use that in your Database query servlet.
Interesting idea! My initial implementation fetches the data from the
table in the custom ResourceResolver and place it in a table-specific
Resource implementation instance, but it sounds like you're suggesting
instead to place the means of fetching the data into the Resource and
let the rendering servlet actually do the fetching. By deferring the
actual fetch, I can let the rendering servlet inspect the request for
filters and query parameters and have it fetch based on those params.
For example, let's say I want to map a User table to /prefix/users.
For JsonRestStore to work, calling /prefix/users should return a JSON
array of user objects and calling /prefix/users/id should return a
single JSON object representing the user with the specified ID. In
addition, requesting /prefix/users?active=true should return a list of
users in an "active" state (as defined by my application). I would
create a UserResourceProvider class that inspects the path to
determine whether a list of users or a single user has been
requested. For a list of users, it would return a UserResource with a
type of "myapp/user-list". For a single user, it would return a
UserResource of type "myapp/user" with a resource metadata key set to
the user ID parsed off the path. The UserResource object would
implement adaptTo(MyAppDAO.class) where MyAppDAO is the data access
object used to fetch User objects.
I'm using Groovy scripts for rendering in this application, so to
render a GET request for the user list I would create /apps/myapp/user-
list/GET.groovy. Inside that script, I can use the predefined request
variable to look at the Content-Range header and any request
parameters, then call my DAO that was adapted from the resource:
// pseudocode
def active = request.getParameter("active")
def dao = resource.adaptTo(MyAppDAO.class)
def users = []
if (active != null) {
users = dao.findUsersByActiveState(active)
}
else {
users = dao.allUsers()
}
// now render each user as JSON...
I would also need to implement POST.groovy to fully support the
JsonRestStore, but that is trivial since I have access to the MyAppDAO
object in the script. The scripts for GET, POST, PUT, and DELETE for
a "myapp/user" object would get the MyAppDAO object the same way, but
in addition would pull the user ID out of the resource metadata to
determine which user to act upon.
My only concern with this setup is that I feel like I'm not leveraging
all of the existing capabilities of Sling. For example, instead of
rendering JSON myself, it would be nice for my Resource subclass
supported adaptTo(ValueMap.class) and use the "json" selector. Also,
I expect that I would be able to save and delete instances of objects
if I supported adaptTo(PersistableValueMap.class), though I haven't
tried this yet. Your suggestion is great and it solves my immediate
problem, but it feels like I'm bypassing a lot of functionality I
could otherwise get for free.
Thanks again!
p.