|
|
|
Books demo
This sample demonstrates Atom support with the zero.atom extension and ibm_atom Dojo extension. zero.atom is useful for explicit Atom support; Zero also provides implicit Atom support as part of the Zero Resource Model. ZRM is still evolving, but we're looking to make it an easy choice for rapid model-based development of resource handlers.
For those that prefer explicit Atom support, the "back-end" of the Books Demo demonstrates use of the Atom renderer and Abdera APIs with a relational database from a Groovy-based resource handler. The "front-end" of the application uses a Dojo extension to enable simple create/read/update/delete operations of records via Atom and Atom Publishing Protocol.
The Books Demo is available from the download page under the Project Zero Examples Plugin for Eclipse. Instructions for installing Books Demo and other samples are available in the Getting Started section of the Core Developer's Guide. Additional post-install steps are documented in the README.TXT file in the Books Demo folder.
Leveraging Zero conventions
This application uses zero.data to access the relational database. The simplest form of the zero.data APIs returns records as =Map=s, where the keys are the column names. Given that we want to serialize the results with the Atom renderer, we'll save ourselves some work by setting up the table schema with column names that match the convention of the Atom renderer. (Obviously this is most suitable for "new" applications, although mapping from existing tables is possible.)
Table schema
The table schema is defined in createBookTable.sql:
CREATE TABLE books (
ID int NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
TITLE varchar(56) NOT NULL,
AUTHORNAME varchar(56) NOT NULL,
UPDATED date NOT NULL,
CONTENTTYPE varchar(256) NOT NULL,
CONTENT varchar(256) NOT NULL,
CATEGORYTERM varchar(256) ,
CATEGORYLABEL varchar(56) ,
FOREIGN1 varchar(256),
FOREIGN2 varchar(56),
PRIMARY KEY (ID)
);
Most of the columns map directly into Atom elements (e.g. TITLE, AUTHORNAME); FOREIGN1 and FOREIGN2 are extensions employed here for additional metadata related to the books.
The application is configured to use embedded Derby, as shown in config/derby-embedded.config:
/config/db/books_db = {
"class" : "org.apache.derby.jdbc.EmbeddedDataSource",
"databaseName" : "db/books_db",
"connectionAttributes" : "create=true"
}
Simple resource list/retrieve handlers
Given the table schema, the simplest implementation of a resource handler for serving the collection as Atom feeds (list) and entries (retrieve) would be:
def onList() {
def dataManager = zero.data.groovy.Manager.create('books_db');
def rs = dataManager.queryArray('SELECT * FROM books ORDER BY UPDATED DESC')
request.view='atom'
request.atom.output = rs
render()
}
def onRetrieve() {
def dataManager = zero.data.groovy.Manager.create('books_db')
def id = request.params.booksId[]
def rs = dataManager.queryFirst("SELECT * FROM books WHERE id=$id" )
request.view='atom'
request.atom.output = rs
render()
}
Note that we're able to render directly the data results (again, because of our careful selection of column names).
app/resources/books.groovy contains an enhanced implementation, including error checking and support for queries within the list operation.
Create handler
Create and update handlers are a little more verbose with zero.atom, since you'll be explicitly mapping the posted entry to a SQL insert/update. For example, a simplified excerpt from app/resources/books.groovy:
def onCreate() {
def entry = org.apache.abdera.Abdera.getNewParser().parse(request.input[]).getRoot()
def updated = new java.sql.Timestamp(System.currentTimeMillis())
def dataManager = zero.data.groovy.Manager.create('books_db')
id = dataManager.insert('INSERT INTO books (TITLE, AUTHORNAME, CONTENT, CONTENTTYPE, UPDATED, CATEGORYTERM, CATEGORYLABEL, FOREIGN1, FOREIGN2) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)',
['id'],
[entry.getTitle(),
entry.getAuthor().getName(),
entry.getContent().toString(),
entry.getContentType().toString(),
updated,
entry.getCategories().get(0).getTerm(),
entry.getCategories().get(0).getLabel(),
zero.atom.AtomUtils.getExtension(entry, 'FOREIGN1'),
zero.atom.AtomUtils.getExtension(entry, 'FOREIGN2')])
resourceUri = getRequestedUri() + '/' + id
request.headers.out.Location = resourceUri
entry.setId(resourceUri)
link = entry.addLink(resourceUri)
link.setAttributeValue('rel', 'edit')
request.status = 201
request.view = 'atom'
request.atom.output = entry
render()
}
}
zero.atom.AtomUtils.getExtension() is a convenience API for extracting default extensions set by the Atom renderer. This simplifies the case where you want to use extensions, but you're not concerned with the namespace and prefix details.
Note that the auto-incremented id is used to construct the member URI, which is set into the Location response header. When using zero.atom, you'll be required to manage low-level aspects, like the Location header, of proper HTTP responses.
|