 | Level: Introductory Roland Barcia (barcia@us.ibm.com), Senior Technical Staff Member, IBM Steve Ims ( steveims@us.ibm.com), Senior Technical Staff Member, IBM
15 Jan 2008 In this series about IBM’s
Project Zero, get a
hands-on guided tour of Zero’s innovations to create, assemble, and deploy Web
applications. Part 1 introduced you to Project Zero by
building a simple RESTful service. In this article, continue learning how Project Zero
can help you build RESTful solutions. The focus is on application-centric
design, modeling RESTful data, security with REST, and simplified RIA and
integration.
Modeling resources RESTfully
 |
The Project Zero community
Project Zero explains the powerful, but radically simple, development and
execution platform for modern Web applications. The active
community
discusses project development, provides help to developers, and wants to hear your
ideas. |
|
Part 1 "Building RESTful services for your Web application," showed you how to expose a simple table as a RESTful resource using
resource handlers and the data API. You also built a sample Dojo application to
invoke the services.
This article enhances the example to show how
you can expose more complex data in a RESTful fashion. You'll also build
a second Consumer application, which will illustrate how to model resources
with Zero and build a quick rich client with a Zero event-based client programming
model.
Expanding the example
The exercise in Part 1 exposed an incentive table as a RESTful service. You will expand that common example in this article. Figure 1 shows the use case diagram.
Figure 1. Use case
There are two roles: a consumer and incentive. You will
build two applications:
- Provider Incentive application
- For providers to manage the incentives they
offer and to find target consumers. This article only focuses on building the
RESTful services for the incentive.
- Consumer Incentive application
- Allows consumers to find incentives. They will
be able to search for incentives either by provider or location.
Application-centric versus server-centric design
In the enterprise, platforms (such as Java™ EE-based servers) follow a
server-centric
mind set. Web applications are built and deployed onto an application server
platform. Server platforms, such as WebSphere®, would provide all the qualities of
service that the Java EE specification demanded. Examples of these services
include queue-based messaging, distributed transactions, or protocol management.
Often, the application server ran several applications on the same Java
Virtual machine. Architects designed applications around the notion of
sharing software and data resources with other applications, and services
provided by the application server (even if they were not being used, in some
cases).
Even if applications were deployed in isolated application servers, the
servers themselves often still carried all the available services. Application
servers allow for an enterprise-level integration. Characteristics of enterprise
integration include distributed transactions across various systems, queue-based
messaging for critical data delivery, or various other types of services. Figure 2 below
highlights an example of enterprise integration. Platforms in the enterprise are
designed around managing various protocols and middleware. Sometimes they talk to
enterprise databases that service many applications.
Figure 2. Enterprise integration
The Web 2.0 world involves a class of less-critical integration at the
HTTP level. Applications usually are designed around a set of data, meant to be
exposed and mixed with other data sets to create new applications that perhaps
the data provider does not anticipate. Figure 3 shows an example of how
integration may look.
Figure 3. Integration in Web 2.0
Applications are designed around the data, and exposed RESTfully through HTTP.
Rich Internet applications can then use the data by mixing and matching
them to create new situations. For example, a map application can expose its
points on the map RESTfully. A totally different application, such as an
employment management tool, can use the map application and mix it with a set of
employees to create a visual map of where an employee may be located. The two sets
of data were mashed to create this opportunity. In the enterprise, such an
application, although not critical, may have required more of an enterprise
process to roll out.
Server-centric versus application-centric design is not a statement on scale.
Application-centric scale involves having an external entity to scale the
application, which can involve software such as WebSphere XD to manage processes
and replicate state. Server-centric design involves a more integrated scaling
solution, where the application server may have built-in mechanisms of scaling.
With an application-centric design, it is easier to start small and scale up as
users increase, thus realizing the value proposition of Web 2.0-style design. Another
important characteristic is that Web 2.0-style applications can invoke enterprise applications, and vice
versa. Figure 4 shows a mashup that accesses an enterprise application with HTTP.
You can use technologies such as the WebSphere Web 2.0 Feature Pack to expose
enterprise artifacts RESTfully so they can participate in a mashup or Rich
Internet Application (RIA).
Figure 4. Mashup accessing the
enterprise
There are advantages and disadvantages to both approaches. Server-centric
approaches are often easier to manage, while application-centric design is easier
for developers to code. It often takes external software to manage application-centric solutions.
RESTful Data
As shown, exposing data RESTfully is fundamental to becoming part of a
Web 2.0 mashup. Part 1 discussed how REST is central to Project Zero. REST
is used to expose resources. Resources become the foundation for exposing
information over the Internet so it can be easily consumed to form the next
class of Web applications. Mashups can quickly be composed out of multiple
resources. Project Zero is optimized around the concept of exposing
resources RESTfully. In the example in this article, we want to expose incentives
that are provided by energy providers. Figure 5 has the data model, which shows a provider and its relationship to incentives.
Figure 5. Provider and incentive data model
The goal is to give interested parties the ability to find incentives. And, providers need to maintain incentives. Table 1 shows how
to expose this data RESTfully. (Part 1 showed how a table like this can be
beneficial to modeling your resources RESTfully.)
Table 1. REST design
|
Resource
|
URI
|
Method
|
Representation
|
Description
| | Provider | /provider/<providerId> | GET | JSON Object | Retrieve provider record | | Incentive | /provider/<providerId>/incentive | POST | JSON Object | Create a new incentive | | Incentive | /provider/<providerId>/incentive | GET | JSON Array | Retrieve a list of incentives for a particular providerId | | Incentive | /provider/<providerId>/incentive/<incentiveId> | GET | JSON Object | Retrieve an individual incentive | | Incentive | /provider/<providerId>/incentive/<incentiveId> | PUT | JSON Object | Update a single incentive | | Incentive | /provider/<providerId>/incentive/<incentiveId> | DELETE | | Delete a single incentive | | Incentive | /incentive?location=<state_name> | GET | JSON Array | Return a list of incentives in a particular state for any provider |
Unlike in Part 1, incentives are accessible under the provider
namespace. An incentive lifecycle is tied to a provider, and therefore is
managed by the provider that offers it. RESTfully, we can ensure that the
incentive record is nested within the provider to meet the requirement. However, a
consumer may be interested in searching for incentives across providers. The
/incentive namespace can be used to provide a list of incentives that span
multiple providers.
You must take into account that an incentive namespace can
become quite large if there are many providers, so you need to limit it somehow. The example
only allows /incentive namespace to be accessed with a location
specified. We also chose to use the query parameter to filter the choice
down.
Applying nonfunctional requirements to your RESTful services
Once you define the services, you can begin to attach qualities of service to
them. Nonfunctional requirements come in different shapes and sizes. (For a good
practical treatment on nonfunctional requirements, see the article
"Why do non-functional requirements matter?")
With REST we are providing HTTP-based services, so applying
nonfunctional requirements may be simplified. The example will deal with
security, which means you'll apply security rules to the REST resources by
securing URLs. Table 2 shows an example of a REST table with a security focus.
Table 2. Secure REST resources
|
Resource
|
URI
|
Method
|
Role
|
Instance level security required?
| | Provider | /provider/<providerId> | GET | All | No | | Incentive | /provider/<providerId>/incentive | POST | Provider | Yes: A provider can only manage his own incentives. | | Incentive | /provider/<providerId>/incentive | GET | All | No | | Incentive | /provider/<providerId>/incentive/<incentiveId> | GET | All | No | | Incentive | /provider/<providerId>/incentive/<incentiveId> | PUT | Provider | Yes: A provider can only manage his own incentives. | | Incentive | /provider/<providerId>/incentive/<incentiveId> | DELETE | Provider | Yes: A provider can only manage his own incentives. | | Incentive | /incentive?location=<state_name> | GET | All | No | | Any other resource | /<Anything Else> | ALL | No one else | NA |
Table 2 shows the URL resource and the role that can access it. It is
important to consider that REST defines a pattern for exposing entities. Often,
some data may need instance-based security; the data can only
be viewed by the data owner. In this case, incentives can only be created, updated,
and deleted by the owning provider. Being part of the provider role is not enough.
The design table above has a column to mark if the data needs to be protected by
instance.
Prerequisites for running the example
To run the example in this article you will need:
-
Eclipse 3.2 or higher. Eclipse 3.3 was used in the example.
- Project Zero Java and Groovy Plug-in for Eclipse.
The example was built
using the M3 Release of Zero. Make sure you set your Eclipse Update Site to
http://www.projectzero.org/update/zero.eclipse.M3
.
You will not need the PHP plug-in for this exercise. For information on
installing the plug-in, see Project Zero Downloads.
Note: If you did Part 1, you will have to uninstall the M1
plug-in before installing the M3 feature and plug-in. Refer to the
Eclipse Help for instructions on removing plug-ins.
- The download file with this article. Extract it to your C:
drive.
-
The Firefox browser
- A good understanding of the concepts taught in Part 1 of this series.
Other useful tools:
Building your provider RESTful application
You'll begin by building the provider application. The goal is to
create the RESTful services defined earlier. The Incentive application will
further build on what you learned in Part 1.
Create a new application
To create a new application:
- Open a fresh workspace.
Figure 6.
- Create a new Project.
Figure 7.
- Select Project Zero -> Project Zero Application as the
project type.
Figure 8.
- Name the application
ProviderIncentiveApp.
Figure 9.
Add dependencies
You must add the dependencies you need. As shown in Part 1, the ivy technology was used to manage dependencies. (See Part 1, or the Project Zero Developer’s Guide, for more details.)
- Open the ivy.xml file under the config directory.
Figure 10.
- Under the Dependencies section, click Add.
Figure 11.
- Add the following dependencies:
- zero.data
- zero.data.setup.webtools
- derby
The dependency wizard is shown below.
Figure 12.
- Save the ivy.xml file. If you examine the source, it should look like Listing 1 below.
Listing 1
<!-- Note: dependencies from maven require the maven2 notation. -->
<dependencies>
<dependency name="zero.core" org="zero" rev="1.0+"/>
<dependency name="zero.webtools" org="zero" rev="1.0+"/>
<dependency name="zero.data" org="zero" rev="1+"/>
<dependency name="zero.data.setup.webtools" org="zero" rev="1+"/>
<dependency name="derby" org="org.apache.derby" rev="10+"/>
</dependencies>
|
- Click the Update Dependencies icon. If you recall, the icon makes
checks both in the remote repository and local repository for the latest
version of any configured packages.
Figure 13.
- Click OK to check for updates.
Figure 14.
- When you are done, close the editor.
Create tables
You will use the embedded version of derby to code the
application. This is a good option for development purposes. In creating the
required tables for the sample, you will use the new database setup tool
introduced in the M3 driver of Project Zero.
- Right-click on the ProviderIncentiveApp project.
Figure 15.
- Select General->File System.
Figure 16.
- Assuming you extracted the downloadable material into the C: directory,
select C:\ProjectZeroArticleSeries/Part2Artifacts/ProviderAppArtifacts.
Select sql, then click Finish.
Figure 17.
- Your project directory layout should look like the following figure.
Figure 18.
- Open the create.sql file. You will see the DDL for creating the
PROVIDER and INCENTIVE table, as shown in Listing 2.
Listing 2
CREATE TABLE PROVIDER (
PROVIDER_ID VARCHAR (50) NOT NULL,
NAME VARCHAR(256) NOT NULL,
DESCRIPTION VARCHAR(256) NOT NULL,
LOCATION VARCHAR(50) NOT NULL,
PROVIDER_TYPE VARCHAR(128),
CONTACT VARCHAR(256));
ALTER TABLE PROVIDER ADD CONSTRAINT PROVIDER_PK PRIMARY KEY (PROVIDER_ID);
CREATE TABLE INCENTIVE (
INCENTIVEID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1, INCREMENT BY 1),
|--10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
NAME VARCHAR(256) NOT NULL,
DESCRIPTION VARCHAR(256) NOT NULL,
PROVIDER_ID VARCHAR (50) NOT NULL,
INCENTIVETYPE VARCHAR(128),
VALIDFROM TIMESTAMP,
VALIDTO TIMESTAMP,
WEBSITE VARCHAR(256)
);
ALTER TABLE INCENTIVE ADD CONSTRAINT INCENTIVE_PK PRIMARY KEY (INCENTIVEID);
ALTER TABLE INCENTIVE ADD CONSTRAINT INC_PPR_FK FOREIGN KEY (PROVIDER_ID)
REFERENCES PROVIDER (PROVIDER_ID);
|
- There is a file for deleting the tables and adding
sample data. Next, you need to run your application. Right-click on your
project and select Run As -> Project Zero Application.
Figure 19.
- Examine the console to ensure it has started and is running.
Figure 20.
- Open a browser and go to
http://localhost:8080/setup. Your
browser should look like the figure below.
Figure 21.
- Enter the information below, then click Create Tables.
- Database Name:
PRO_DB
- Database Type: Apache Derby (Embedded)
- User Name:
APP
Figure 22.
The tool should report that it successfully executed the create.sql
script.
- Click Add Sample Data. The tool should report
that it executed the sample.sql script successfully.
Figure 23.
- Stop the application by clicking the Stop button in the console.
Stopping and starting applications like this is OK for development.
For starting and stopping applications in test and production environments,
using the management command line is preferred. See the
Zero Management CLI extensions reference for more information.
Figure 24.
Create the RESTful services for the Incentive application
To create the RESTful services for the Incentive application:
- Right-click on the app/resources folder and select
New -> File.
Figure 25.
- Name the file
provider.groovy.
Figure 26.
- Click Yes to add groovy support to the project.
Figure 27.
- Ensure the provider.groovy file is open. Enter the code shown in Listing 3 (or
you can paste it from
C:\ProjectZeroArticleSeries\Part2Artifacts\ProviderAppArtifacts\codeSnippet\proSnippet.1.txt).
The code uses the data API to execute queries, and stores the result in a
JSON format. This pattern was shown in Part 1 of this series. See the data access section of the Project Zero Developer’s Guide for information on the API.
The onRetrieve method is used to represent a
handler for getting a single instance of a Provider. This
pattern was also shown in Part 1 (or, see Resource handling in the Project Zero
Developer’s Guide).
Above the service
there's a comment with annotated information. Project Zero lets you
document your RESTful resources this way. Project Zero will use this to
render RESTful documentation and a test tool for the REST service as well.
You will later see this tool in action. (See RESTful documentation for more information.)
In the example, we document the available HTTP return codes, describe the
format, and give a sample for what the return value may look like.
Listing 3
import zero.data.groovy.Manager;
/**
*
* @success 200 Returns the profile for the provider with the given ID.
* @error 404 Not authorized Provider for User
* @format application/json
* @example
* {
* "name": "Energy Provider Name",
* "description": "Sample Output",
* "location": "New Jersey"
* "provider_type": "energy"
* "contact": "roland@projectzero.org"
* }
*
*/
def onRetrieve()
{
def data = Manager.create('PRO_DB');
def result = null;
def id = request.params.providerId[0];
//Note: Next piece of code should be in one line.
result =
data.queryFirst
("select name,description,location,provider_type,contact from provider where
provider_id = $id");
if(result != null) {
request.view='JSON'
request.json.output = result
render()
} else {
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "Provider $id not found."
request.view = "error"
render()
}
}
|
- When using the database tool, a configuration for your database was
generated inside a data.config file. For your application to make
use of it, it must be included inside your master zero.config. Open the
zero.config file. (Part 1 discussed the zero configuration model. See
Configuration
for more details on zero configurations.)
Figure 28.
- Examine the first part of the zero.config file.
Notice the new simplified configuration syntax for configuration.
Figure 29.
- The second entry shown in the figure below shows the generated data configuration.
The new syntax for complex properties uses the JSON format.
Figure 30.
- Launch your application from the launch button on the menu bar.
Figure 31.
- From your browser, go to
http://localhost:8080/resources/docs.
The figure below shows the available RESTful resources. Select Provider.
Figure 32.
- The resources documentation tool renders the information you commented
in your groovy file. The REST tool can also be used to test your service.
Figure 33.
- By clicking on the format, as shown below, you can see the
sample.
Figure 34.
Figure 35.
- By clicking the URI shown below, you can launch a test driver for your
RESTful service.
Figure 36.
- Click Send.
Figure 37.
- The result will show the return of a JSON Object.
Figure 38.
Create the incentive service
In this section you create the incentive service and associate it with the
provider service.
- Create another file in the /app/resources folder and call it
incentive.groovy.
Figure 39.
- Create another file in the /app/resources folder and call it
incentive.bnd.
Figure 40.
- Enter the following two lines:
provider/incentive
incentive |
This defines a relationship between provider and incentive. In this case, it is
specifying that both /provider/<provider_id>/incentive and
/incentive namespace are supported. If you only wanted to allow the nested
pattern, then you would only specify provider/incentive.
Figure 41.
- Add the following
onList method (or paste it from C:\ProjectZeroArticleSeries\Part2Artifacts\ProviderAppArtifacts\codeSnippet/proSnippet3.txt).
Notice that we examine the
headers using groovy shortcuts, first looking to see if the provider is
present. If so, we execute a query for all incentives for a provider. If
the providerId is not present, then the request is for /incentives
namespace. There is also a check for the existence of a location parameter. If it exists,
an SQL search by query is executed. Otherwise, no other call is allowed.
Listing 4
import zero.data.groovy.Manager;
/**
*
* @success 200 Returns incentives by provider or location.
* @error 404 Cannot Query all Incentives in the system.
/provider/providerId/incentive or /incentive?location=locationValue
must be used.
* @format application/json
* @example
* {
* "incentiveId" : "Incentive Id"
* "name": "Incentive Name",
* "description": "Sample Output",
* "providerName": "Provider Name"
* "providerId": "Provider Id"
* "incentive_type": "Incentive Type"
* "validfrom": 1189396800000,
* "validto": 1189396800000,
* "website": "http://www.projectzero.org"
* }
*
*/
def onList()
{
def data = Manager.create('PRO_DB');
def provider = null
def location = null;
// List by provider flag
if(request.params.providerId)
provider = request.params.providerId[0];
// List by location flag
if(request.params.location)
location = request.params.location[0];
def result = null;
if(provider != null)
{
result = data.queryList("select i.incentiveId,i.name,p.name
as providerName,i.provider_id,i.incentivetype as
incentive_type,i.validFrom,i.validTo,i.website,p.location from provider
as p,incentive as i where p.provider_id = $provider and i.provider_id =
p.provider_id");
}
else if (location != null)
{
result = data.queryList("select i.incentiveId,i.name,p.name
as providerName,i.provider_id,i.incentivetype as
incentive_type,i.validFrom,i.validTo,i.website,p.location from provider
as p,incentive as i where p.location = $location and i.provider_id =
p.provider_id");
}
else
{
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "Cannot Query all Incentives in the
system. /provider/providerId/incentive or
/incentive?location=locationValue must be used."
request.view = "error"
render()
return;
}
if(result != null) {
request.view='JSON'
request.json.output = result
render()
}
}
|
- Save the file.
Back in the browser, go back to
http://localhost:8080/resources/doc
and select the Incentive link.
Figure 42.
- The incentive URI is under the provider namespace.
There is a bug in M3 where Resource documentation only shows one
pattern for the resource. This will be corrected in the next Milestone.
Figure 43.
- Click on the URI. For Provider ID, enter
austinEnergy.
Figure 44.
- You should get back the list of incentives for Austin Energy.
Figure 45.
- In the browser, try to look for an incentive under
http://localhost:8080/resources/incentive.
You should receive an error like the one shown below.
Figure 46.
- Using the browser, or a tool like Firefox Poster, execute a
GET request
http://localhost:8080/resources/incentive?location=Texas.
The figure below shows the result using the Poster tool.
Figure 47.
- Back inside the incentive.groovy file, enter the code in Listing 5 (or
paste it from
C:\ProjectZeroArticleSeries\Part2Artifacts\ProviderAppArtifacts\codeSnippet/proSnippet3.txt).
The code contains the call for retrieving an individual incentive, and for
creating, updating, and deleting an incentive. The code is very similar to
the type of code in Part 1.
Listing 5
/**
*
* @success 200 Returns incentive by id.
* @error 404 Incentive Id not found.
* @format application/json
* @example
* {
* "incentiveId" : "Incentive Id"
* "name": "Incentive Name",
* "description": "Sample Output",
* "providerName": "Provider Name"
* "incentive_type": "Incentive Type"
* "validfrom": 1189396800000,
* "validto": 1189396800000,
* "website": "http://www.projectzero.org"
* }
*
*/
def onRetrieve()
{
def data = Manager.create('PRO_DB');
def id = request.params.incentiveId[0];
result = data.queryFirst("select
i.incentiveId,i.name,i.description,p.name as
providerName,i.incentivetype as
incentive_type,i.validFrom,i.validTo,i.website from provider as
p,incentive as i where i.incentiveId = $id");
if(result != null) {
request.view='JSON'
request.json.output = result
render()
} else {
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "Incentive $id not found."
request.view = "error"
render()
}
}
/**
*
* @success 204 Post new Incentive for the provider.
* @error 403 Not authorized to insert this record.
* @error 404 Cannot Post directly to /incentives, only
/provider/<providerId>/incentive
* @format application/json
* @example
* {
* "name": "Incentive Name",
* "description": "Sample Output",
* "incentive_type": "Incentive Type",
* "validfrom": 1189396800000,
* "validto": 1189396800000,
* "website": "http://www.projectzero.org"
* }
*
*/
def onCreate()
{
def provider = request.params.providerId[0];
if(provider != null)
{
def data = Manager.create('PRO_DB');
def incentive = zero.json.JsonType.fromData(request.input[]).getJson()
def user = request.subject['remoteUser'];
def validFrom = new java.sql.Timestamp(incentive.validfrom);
def validTo = new java.sql.Timestamp(incentive.validto);
def key = data.insert("insert into incentive
(name,description,provider_id,incentivetype,validfrom,validto,website)
values ($incentive.name,$incentive.description,$provider,$incentive.incentivetype,
$validFrom,$validTo,$incentive.website)",['incentiveId']);
def locationUri = getRequestedUri(false) + '/' + key
request.headers.out.Location = locationUri
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
else
{
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "Cannot POST directly to /incentive,
only /provider/<providerId>/incentive."
request.view = "error"
render()
return;
}
}
/**
*
* @success 204 Delete Incentive for the provider.
* @error 403 Not authorized to insert this record.
* @error 404 Cannot delete directly to /incentives,
only /provider/<providerId>/incentive
*
*/
def onDelete()
{
def provider = request.params.providerId[0];
if(provider != null)
{
def data = Manager.create('PRO_DB');
def id = request.params.incentiveId[0];
def user = request.subject['remoteUser'];
data.update("delete from incentive where incentiveId = $id");
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
else
{
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "Cannot DELETE directly to
/incentive, only /provider/<providerId>/incentive."
request.view = "error"
render()
return;
}
}
/**
*
* @success 204 Incentive updated for provider.
* @error 403 Not authorized to update this record.
* @error 404 Cannot Put directly to /incentive/<incentiveId>,
only /provider/<providerId>/incentive/<incentiveId>
* @format application/json
* @example
* @example
* {
* "name": "Incentive Name",
* "description": "Sample Output",
* "incentive_type": "Incentive Type",
* "validfrom": 1189396800000,
* "validto": 1189396800000,
* "website": "http://www.projectzero.org"
*
* }
*
*/
def onUpdate()
{
def provider = request.params.providerId[0];
if(provider != null)
{
def data = Manager.create('PRO_DB');
def incentive = zero.json.JsonType.fromData(request.input[]).getJson()
def user = request.subject['remoteUser'];
def id = request.params.incentiveId[0];
def validFrom = new java.sql.Timestamp(incentive.validfrom);
def validTo = new java.sql.Timestamp(incentive.validto);
data.update("update incentive set
name=$incentive.name,description=$incentive.description,incentivetype =
$incentive.incentiveType,validfrom=$validFrom,validto=$validTo,website=
$incentive.website where incentiveId = $id ");
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
else
{
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "Cannot Put directly to
/incentive/<incentiveId>, only
/provider/<providerId>/incentive/<incentiveId>."
request.view = "error"
render()
return;
}
}
|
Secure the operations
As part of the requirements, you need to secure the create, update, and
delete operations on an incentive to the provider that owns them. You will
begin by using the built-in Zero File Registry. The User File Registry is a good
option for development. Project Zero also supports LDAP-based registries. It is
often a best practice to use LDAP in production. With Project Zero, it's easy
to override the default registry at deployment time.
- In your browser, enter
http://localhost:8080/zero/webtools/user
as shown below.
Enter the following:
- UserName:
austinEnergy
- Password:
passw0rd
- Groups:
Provider
Click add.
Figure 48.
- Enter another user, as shown in the figure below.
- UserName:
centerPoint
- Password:
passw0rd
- Group:
Provider
Click add.
Figure 49.
- You should now have two users in the File Registry.
Figure 50.
- Examine your config directory. You should see the file zero.users, as
shown below. (You might have to refresh the Eclipse Project.)
Figure 51.
Add authorization rules
At this point you need to add your authorizations rules. You need to secure the
RESTful URI. You will secure unsupported URIs with a non-existent group. You
will also secure the RESTful data to the owner, which shows Project Zero’s
support for instance based security.
- Create a new file in the config directory.
Figure 52.
- Name the file
security.config.
Figure 53.
- Enter the text in Listing 6 (or paste it from
C:\ProjectZeroArticleSeries\Part2Artifacts\ProviderAppArtifacts\codeSnippet\proSnippet4.txt).
The rule.config is included, and a set of parameters is
entered. The uri defines the REST url. The vertical bar will also take into
account anything after the URI pattern. The first rule blocks anyone from
executing the update HTTP methods on the /incentive namespace. The second
rule only allows a provider who is logged in to execute the update methods.
For example, if I logged in as austinEnergy, then I can only issue POST, DELETE,
or PUT on the austinEnergy namespace. More details are in the
Security considerations section of the Project Zero Developer’s Guide.
Listing 6
@include "${/config/dependencies/zero.core}/config/security/rule.config"
{
"uri":"/resources/incentive|",
"condition":"urimatches",
"authType":"Basic",
"groups":"[NO_ONE]",
"methods":"DELETE|POST|PUT"
}
@include "${/config/dependencies/zero.core}/config/security/rule.config"
{
"uri":"/resources/provider/{remoteUser}|",
"condition":"urimatches",
"authType":"Basic",
"methods":"DELETE|POST|PUT"
}
|
- Open the zero.config file in the config directory.
Figure 54.
- Add an
include entry for the security.config file.
Figure 55.
Test the security
Now that you have secured your RESTful resources, you can test the security
using the REST documentation tool.
- Terminate (by clicking the Stop button on the console)
and Relaunch your Provider application. You can use the launch
shortcut.
Figure 56.
- In the browser, go to
http://localhost:8080/resources/docs.
Click Incentive.
Figure 57.
- You will see all the HTTP methods available, as shown below.
Figure 58.
- Click on GET for the individual record.
Figure 59.
- Enter
austinEnergy for Provider ID and 1 for Incentive ID.
Figure 60.
- You should get the JSON object back.
Figure 61.
- Close the result window.
Click on the format for the POST.
Figure 62.
- Copy the JSON example.
Figure 63.
- Click on the POST URI. Paste that value into the Body, and enter
austinEnergy as the Provider ID.
Figure 64.
- You should get authenticated.
Enter austinEnergy for the User Name,
and passw0rd for the Password.
Figure 65.
- You should get a result as shown below. Make note of the
Location of the posted result.
Figure 66.
- You can test the PUT and DELETE with 5, if desired. The Delete is shown in
the figure below.
Figure 67.
- Now that you are logged in as austinEnergy, reopen the POST using URI.
Try to POST data under the centerPoint URI.
Figure 68.
- You should get a 403 Forbidden error.
Figure 69.
For this exercise you've completed the Incentive portion
of the application. The application should remain running as you build the
Consumer example.
Building your consumer application
 | | A subsequent article will go into detail on the Consumer application and RESTful data. |
| Thus far you've seen how you
can build resource handlers using the Data API. In M2, Project Zero introduces
another way of modeling resources. Some developers prefer coding SQL, while others
prefer using a persistence technology. The Zero Resource Model is focused on mapping
data to REST (not necessarily objects).
In addition to the Zero Resource, you'll use the Zero Web Template technology to
build a quick client that will use the Zero Connection API to call a remote REST
resource.
Create a new application
The first step is to create a new application.
- Create a new Project Zero Application called
ConsumerIncentiveApp.
Figure 70.
- Right-click on the ConsumerIncentive application and select
Import.
Figure 71.
- The From directory should be
C:\ProjectZeroArticleSeries\Part2Artifacts\ConsumerAppArtifacts. Select the sql folder only, as shown.
Figure 72.
- Examine the create.sql file shown in the figure below.
Figure 73.
- Notice the very simple consumer table with five fields.
Figure 74.
- Open the zero.config file.
Figure 75.
- Change the default port number to
8081. This lets you run both
applications at the same time.
Figure 76.
- Open ivy.xml.
Figure 77.
- Add the following Dependencies:
- derby
- dojo
- zero.data
- zero.data.setup.webtools
- zero.web.template
- zero.resource
Figure 78.
- Update your dependencies.
Figure 79.
- Run the Consumer.
Figure 80.
- Examine the console to ensure it is running on Port 8081.
Figure 81.
- Run the database setup tool for the Consumer application. The Port should
be http://localhost:8081/setup/.
Enter CON_DB for the database name, Apache Derby (Embedded) for the
Database Type, and APP for the User Name.
Figure 82.
- Click Add Sample Data.
Figure 83.
- Open the zero.config file for the application again and enter the
line /config/reosurce/dbKey="COB_DB", as shown in Figure 84 below.
Figure 84.
- Terminate the Consumer application (not the Incentive).
Create a simple resource model
In this section you create a simple resource model based on the Consumer table. M2 is
a preview release of this technology, so you'll use a very simple pattern to
illustrate its usage. (In subsequent parts of this series we'll use a more
elaborate example.)
- Create a new source folder by right-clicking the app directory and
selecting New -> Source folder.
Figure 85.
- Create a folder called
app/models.
Figure 86.
- Create a new file in the app/models directory called
consumer.groovy.
Figure 87.
- Click Finish to add Groovy support.
- Enter the code from Listing 8 into consumer.groovy (or paste it from
C:\ProjectZeroArticleSeries\Part2Artifacts\ConsumerAppArtifacts\conSnippet2.txt).
This creates a simple model.
Listing 8
import zero.resource.fields.*
fields = [
name: [type:'CharField'],
state:[type:'CharField'] ,
provider:[type:'CharField']
]
|
- You can now access the data locally using the Zero API, or using HTTP.
To expose the model with HTTP, create another file in the
/app/resources.
Figure 88.
- Also name it consumer.groovy. There will be an error about
duplicate consumer.groovy files; you can ignore that error.
Figure 89.
- Enter the following code,
ZRM.delegate(), as shown below. This will
expose the model using HTTP.
Figure 90.
- Terminate the Consumer application (not Provider) if running and launch the consumer application.
Figure 91.
- Open a Browser (or use the Poster Firefox plug-in) to
http://localhost:8081/resources/consumer.
Figure 92.
- Open the resulting JSON text to see the list of consumers.
Figure 93.
- Open the following URL:
http://localhost:8081/resources/consumer/
1
.
You should get a single record.
Figure 94.
- A single record should be returned.
Figure 95.
Create a simple client
In this section you create a very simple Rich Internet client using the Zero Web templates.
Part 1 showed how a Dojo client can invoke a RESTful service. Zero also
supports other patterns, such as fetching HTML fragments, which we'll illustrate. (Later in the series we'll use Dojo together with Zero Templating.) For
more details on the Zero client, see
Writing rich Web applications.
The Zero client programming model provides the ability for Zero server
events to respond to JavaScript events. They can also share data using
the client zone of the global context.
- Create a new File under the public directory.
Figure 96.
- Name it
incentiveSearch.zhtml.
Figure 97.
- Enter the HTML code from Listing 9 (or paste it from
C:\ProjectZeroArticleSeries\Part2Artifacts\ConsumerAppArtifacts\conSnippet3.txt).
The HTML template is using two Dojo Grid components: one to hold the consumer data, and
the other to hold incentive results. The use of the groovy language
lets you create application events either on the server
or the client in response to UI events. Project Zero uses the following definition syntax to declare an application event with the Ajax lifecycle:
on("<UI_event>").fire("<application_event>").before
("<before_event>").after("<after_event>") |
| before_event | Specify event that fired before any Ajax requests, for show loading, data validation, and so on. |
|---|
| after_event | Specify event that fired after any Ajax request, for hide loading, UI update, and so forth. |
|---|
| application_event | Event that fired, for example update/fetch data, update UI or any custom biz/UI logics. |
|---|
At run time, the event processing sequence is: before_event --> application_event --> after_event.
application_event should be handled at the server side, which makes sense with the Ajax lifecycle. before_event and after_event are normally handled in the client side.
For more information, see Writing Rich Web Applications.
Listing 9
<html>
<head>
<title>Incentive Search</title>
<style type="text/css">
@import "incentiveSearch.css";
</style>
<script type="text/javascript" src="/dojo/dojo.js" djConfig="isDebug:
true, parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojox.grid.Grid");
dojo.require("dojox.grid._data.model");
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.form.ValidationTextBox");
dojo.require("dijit.form.DateTextBox");
dojo.require("dojox.grid._data.editors");
dojo.require("dojox.grid.editors");
dojo.require("dojox.grid._data.dijitEditors");
dojo.require("dojo.parser");
</script>
<%
on(".search:submit").fire("incentiveSearch").after("incentiveRender")
%>
</head>
<body class="tundra">
<h1> Consumer Incentive Search </h1>
<br>
<br>
<form class=.search>
<div id="consumerGrid" dojoType="dojox.Grid" structure=
"consumerLayout">
</div>
</form>
<br>
<br>
<hr>
<div id="incentiveGrid" dojoType="dojox.Grid" structure="incentiveLayout">
</div>
</body>
</html>
|
- Create another file in the public directory called
incentiveSearch.groovy.
This file will contain event handlers that respond to client events for the
incentiveSearch.groovy file.
Figure 98.
- Enter the code in Listing 10 (or paste it from
C:\ProjectZeroArticleSeries\Part2Artifacts\ConsumerAppArtifacts\conSnippet4.txt).
The onInitialize method gets called when the template is loaded.
We use the local API for the resource model to get the customer and
we put it in the client zone. See Programmatic Model API for more about the Resource API.
There's one event handler for each of the events. It looks for the existence of
a request parameter to determine to do a provider search or location search. (For more
information on the connection, see
Using the Connection API.)
We also use the JSON API to convert the payload into the client zone. The client zone is accessible to both
the JavaScript client and the server groovy code.
Listing 10
import zero.core.connection.*;
import zero.resource.*;
def onInitialize()
{
def collection = TypeCollection.retrieve('consumer');
def result = collection.list();
def items = [];
for(resultItem in result)
{
def item =
[name:resultItem.name ,id:resultItem.id ,state:resultItem.state,
provider:resultItem.provider ]
items.add(item);
}
client.data.consumer = items;
client.data.incentives = [];
}
def onIncentiveSearch()
{
if(event.input.location[])
{
Connection.Response response =
Connection.doGET("http://localhost:8080/resources/incentive?location=
${event.input.location[]}");
client.data.incentives =
zero.json.java.JSONArray.parse(response.getResponseBodyAsString());
}
else if(event.input.provider[])
{
Connection.Response response =
Connection.doGET("http://localhost:8080/resources/provider/
${event.input.provider[]}/incentive");
client.data.incentives =
zero.json.java.JSONArray.parse(response.getResponseBodyAsString());
}
else
{
client.data.incentives = [];
}
}
|
- Create another file in the public directory and name it
incentiveSearch.js. This will
have the client events. These callbacks are used to render the results from the server call.
Figure 99.
- Enter the code in Listing 11 below. The Dojo FileStore and Grid API are used to load the
data locally. Refer to the Grid documentation for details on using the Dojo Grid.
Listing 11
formatDate = function(date) {
var d = new Date();
d.setTime(date);
return d;
}
formatLocationLink = function(location) {
var locationLink = '<input type="submit" name="location"
class="button linktd" value="';
locationLink += location;
locationLink += '"/>';
return locationLink;
}
formatProviderLink = function(provider) {
var providerLink = "<input type='submit' name='provider'
class='button linktd' value='";
providerLink += provider;
providerLink += "'/>";
return providerLink;
}
var consumerLayout = [{
cells: [[
{name: 'Id', field: "id", width:"10%"},
{name: 'Name', field: "name", width:"30%"},
{name: 'State', field: "state",
width:"35%",formatter:formatLocationLink},
{name: 'Provider', field: "provider",
width:"25%",formatter:formatProviderLink}
]
]
}];
var incentiveLayout = [{
cells: [[
{name: 'Provider', field: "providername",width:"20%"},
{name: 'Type', field: "incentive_type",width:"20%"},
{name: 'Provider Location', field: "location",width:"20%"},
{name: 'Valid From', field: "validfrom",width:"20%",formatter:formatDate},
|--10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
{name: 'Valid To', field: "validto",width:"20%",formatter:formatDate}
]
]
}];
var consumerMeta =
{
id:'id',
items:[],
label:'consumer'
}
var incentiveMeta =
{
id:'incentiveid',
items:[],
label:'incentive'
}
function onLoad(){
zfire("renderConsumers");
}
function onRenderConsumers()
{
consumerMeta.items = dojo.clone(zget("/client/data/consumer"));
console.debug(consumerMeta.items);
var consumerStore = new dojo.data.ItemFileWriteStore({data: consumerMeta});
var consumerModel = new dojox.grid.data.DojoData();
consumerModel.store = consumerStore;
consumerModel.query = {id:'*'};
var consumerGrid = dijit.byId('consumerGrid');
consumerGrid.setModel(consumerModel);
}
function onIncentiveRender()
{
incentiveMeta.items = dojo.clone(zget("/client/data/incentives"));
console.debug(incentiveMeta.items);
var incentiveStore = new dojo.data.ItemFileWriteStore({data: incentiveMeta});
var incentiveModel = new dojox.grid.data.DojoData();
incentiveModel.store = incentiveStore;
incentiveModel.query = {incentiveid:'*'};
var incentiveGrid = dijit.byId('incentiveGrid');
incentiveGrid.setModel(incentiveModel);
}
|
- The incentiveSearch.css file is provided to enhance the UI. Import
C:\ProjectZeroArticleSeries\Part2Artifacts\ConsumerAppArtifacts\incentiveSearch.css
into the public directory.
Figure 100.
- Launch the UI by opening a browser and going to
http://localhost:8081/locationSearch.zhtml.
Your client should render as shown below.
Figure 101.
- Click on the Texas link to trigger an incentive search.
Figure 102.
- Similarly, click on each provider to trigger a provider search.
Figure 103.
Figure 104.
 |
Conclusion
This article introduces several new concepts in
Zero. We furthered the discussion of application-centric design, focusing
on exposing more complex data and securing URL-based resources.
You also learned about the new resource modeling and Zero client APIs.
The next
installment will further discuss resource modeling, and will focus on using Dojo 1.0
and the Zero client programming model to assemble more solutions.
Download | Description | Name | Size | Download method |
|---|
| Sample code for this article | Part2Artifacts.zip | 10KB | HTTP |
|---|
Resources Learn
-
Learn all about the Project Zero
simple environment for creating, assembling and executing applications based on
popular Web technologies.
- The Project Zero
Developer's Guide
explains the core concepts that define the architecture of a Zero application.
- Use the Project Zero
Forum for help,
feedback, alerts, discussion of ongoing development, and more.
-
"Why do non-functional requirements matter?" (developerWorks, Jan 2006) is a good
practical treatment on nonfunctional requirements.
- Learn about Groovy, an agile
dynamic language for the Java Platform.
- Read how to
install and configure PHP
to develop Project Zero applications and new PHP extensions for Project Zero.
- See additional developerWorks
articles and tutorials about Project Zero.
- See additional developerWorks
articles and tutorials about REST.
- Learn all about Dojo, the
JavaScript toolkit, and Dojo widgets.
- Read about
Representational State Transfer (REST)
in Roy Thomas Fielding's dissertation, "Architectural Styles and the Design of
Network-based Software Architectures."
- Get details on JSON and
JSON support
in Project Zero.
- Learn all about Eclipse, an open
development platform.
- Get more details about the Web browser
Firefox by Mozilla and the
Poster plug-in.
- Read about Apache Derby, an open
source relational database implemented entirely in Java.
- Find out more about caching with a
caching tutorial for Web authors
and webmasters.
- Learn about
Community-driven commercial development
(CD/CD).
- Read
"Resource-oriented vs. activity-oriented Web services"
(developerWorks, Oct 2004) for a quick look at the relationship of REST-style and SOAP-style Web services.
-
RSS feed for this series.
(Find out more about RSS.)
- Browse the
technology bookstore
for books on these and other technical topics.
- In the
Architecture area on developerWorks,
get the resources you need to advance your skills in the architecture arena.
- Browse the
technology bookstore
for books on these and other technical topics.
Get products and technologies
Discuss
About the authors
 | 
|  | Steve Ims is a senior technical staff member and lead for the Project Zero
run-time "core." You'll find Steve on the
Project Zero forums. |
Rate this page
|  |