This is a little lame. It's really a place to put notes until I get better
organized. Much isn't about ReST at all, but more about Jersey. Also, peruse
JSON Notes for additional and very relevant comments
directly related to ReST.
Sample Jersey JAX-RS ReST suite with Spring and Hibernate
Here's the template of a ReST suite from the ReST service implementation all
the way down to the POJO/bean. It includes also ample annotations from
Spring and Hibernate, as well configuration files for same. And, it includes
the SQL load script for the database table in question.
Note that I advise against using Spring at all. It's too heavy and
wasteful for a lean and mean restlet.
This stuff is from work I've been doing and private details have been
excised. Some mistakes may have been made and breakages created. It's
only for giving an idea. Anyway, these are my private notes.
Files
AccountService.java —ReST entry points
AccountManager.java —business logic
BaseDao.java —Hibernate-Spring glue
AccountDao.java —data-access object
Account.java —bean
account-table.sql —SQL script
spring-configuration.xml —Spring configuration
database.properties —Hibernate/Spring configuation
web.xml —web descriptor
AccountService.java :
These methods are the ReST entry points for a ReST web service. They call
down to the business logic manager. In the code below,
accountManager is @Autowired . In order for this to
succeed, it must not be static (circa line 40 below).
Autowired annotation is not supported on static fields. No compilation error
will inform you of this; you'll see something like (logging statement from the
Eclipse console):
14:33:36,072 WARN AutowiredAnnotationBeanPostProcessor:337 - Autowired annotation is not supported \
on static fields: private static com.etretatlogiciels.web.user.manager.AccountManager \
com.etretatlogiciels.web.user.service.AccountService.accountManager
...and HTTP will probably return 500 Internal Server Error .
package com.etretatlogiciels.web.user.service;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.etretatlogiciels.web.user.entity.Account;
import com.etretatlogiciels.web.user.manager.AccountManager;
/**
* Handles CRUD requests surrounding the abstract Account with special handling for
* reading (listing) all user accounts.
*/
@Path( "/v1/account" )
@Component( "accountService" )
@Scope( "request" )
public class AccountService
{
private static Logger log = Logger.getLogger( AccountService.class );
@Autowired
private AccountManager accountManager;
@POST
@Consumes( { "application/json", "application/xml" } )
@Produces( { "application/json", "application/xml" } )
public Response create( Account account )
{
try
{
account = accountManager.create( account );
}
catch( Exception e )
{
log.error( "Error creating account " + account.getUsername() + " (" + account.getFullname() + ") as " + account.getId(), e );
throw new WebApplicationException( Response.Status.INTERNAL_SERVER_ERROR );
}
URI createdUri = URI.create( "/usermgr/v1/account/" + account.getId() );
return Response.created( createdUri ).entity( account ).build();
}
@GET
@Produces( { "application/json", "application/xml" } )
@Transactional( readOnly = true, propagation = Propagation.REQUIRED )
public List< Account > read()
{
List< Account > accounts = new ArrayList< Account >();
try
{
accounts = accountManager.findAll();
}
catch( Exception e )
{
log.error( "Error reading all accounts", e );
throw new WebApplicationException( Response.Status.INTERNAL_SERVER_ERROR );
}
if( accounts.isEmpty() )
throw new WebApplicationException( Response.Status.NOT_FOUND );
return accounts;
}
@GET
@Path( "/{id}" )
@Produces( { "application/json", "application/xml" } )
public Response read( @PathParam( "id" ) Integer id )
{
Account account;
try
{
account = accountManager.findById( id );
}
catch( Exception e )
{
log.error( "Error reading account " + id, e );
throw new WebApplicationException( Response.Status.INTERNAL_SERVER_ERROR );
}
if( account == null )
throw new WebApplicationException( Response.Status.NOT_FOUND );
return Response.ok( account ).build();
}
@GET
@Path( "/{property}/{propertyvalue}" )
@Produces( { "application/json", "application/xml" } )
public List< Account > readByPropertyAndValue( @PathParam( "property" ) String property,
@PathParam( "propertyvalue" ) String propertyvalue )
{
List< Account > accounts = new ArrayList< Account >();
try
{
accounts = accountManager.findByPropertyAndValue( property, propertyvalue );
}
catch( Exception e )
{
log.error( "Error reading account " + propertyvalue, e );
throw new WebApplicationException( Response.Status.INTERNAL_SERVER_ERROR );
}
if( accounts == null )
throw new WebApplicationException( Response.Status.NOT_FOUND );
return accounts;
}
@PUT
@Path( "/{id}" )
@Consumes( { "application/json", "application/xml" } )
@Produces( { "application/json", "application/xml" } )
public Response update( Account account )
{
try
{
accountManager.update( account );
}
catch( Exception e )
{
log.error( "Error updating account " + account.getId(), e );
throw new WebApplicationException( Response.Status.INTERNAL_SERVER_ERROR );
}
return Response.noContent().build();
}
@DELETE
@Path( "/{id}" )
@Produces( { "application/json", "application/xml" } )
public Response delete( @PathParam( "id" ) Integer id )
{
try
{
accountManager.delete( id );
}
catch( Exception e )
{
log.error( "Error deleting account " + id, e );
throw new WebApplicationException( Response.Status.INTERNAL_SERVER_ERROR );
}
return Response.ok().build();
}
}
AccountManager.java :
This class defines business logic, of which there is precious little here.
package com.etretatlogiciels.web.user.manager;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.etretatlogiciels.web.user.entity.Account;
import com.etretatlogiciels.web.user.entity.AccountDao;
@Transactional
@Service( "accountManager" )
public class AccountManager
{
private static Logger log = Logger.getLogger( AccountManager.class );
@Autowired( required = true )
private AccountDao accountDao;
public Account create( Account account )
{
log.info( "create new account " + account.getUsername() + " (" + account.getFullname() + ") as " + account.getId() );
return accountDao.create( account );
}
public List< Account > findAll()
{
log.info( "list all supported accounts" );
return accountDao.read();
}
public Account findById( Integer id )
{
log.info( "find account whose id is " + id );
return accountDao.readById( id );
}
public List< Account > findByPropertyAndValue( String propertyName, String propertyValue )
{
log.info( "find account whose " + propertyName + " is " + propertyValue );
return accountDao.readByProperty( propertyName, propertyValue );
}
public void update( Account account )
{
log.info( "update existing account" + account.getName() );
accountDao.update( account );
}
public void delete( Integer id )
{
log.info( "delete existing account by id" + id );
Account account = findById( id );
accountDao.delete( account );
}
}
BaseDao.java :
This is some of the more magical glue that make Spring and Hibernate work
and play well together.
package com.etretatlogiciels.web.user.entity;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
public class BaseDao
{
@Autowired
public SessionFactory sessionFactory;
@Autowired
public HibernateTemplate hibernateTemplate;
public BaseDao() { super(); }
}
AccountDao.java :
This class is the data-access object that sits atop the POJO (bean) mirroring
the database table.
package com.etretatlogiciels.web.user.entity;
import java.util.List;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Repository( "accountDao" )
public class AccountDao extends BaseDao
{
@SuppressWarnings( "unused" )
private static Logger log = Logger.getLogger( AccountDao.class );
@Transactional( readOnly = false, propagation = Propagation.MANDATORY )
public Account create( Account account )
{
sessionFactory.getCurrentSession().save( account );
return account;
}
@SuppressWarnings( "unchecked" )
@Transactional( readOnly = true, propagation = Propagation.MANDATORY )
public List< Account > read()
{
Criteria criteria = sessionFactory.getCurrentSession().createCriteria( Account.class );
criteria.isReadOnly();
return criteria.list();
}
@Transactional( readOnly = true, propagation = Propagation.MANDATORY )
public Account readById( Integer id )
{
Criteria criteria = sessionFactory.getCurrentSession().createCriteria( Account.class );
criteria.isReadOnly();
criteria.add( Restrictions.idEq( id ) );
return ( Account ) criteria.uniqueResult();
}
@SuppressWarnings( "unchecked" )
@Transactional( readOnly = true, propagation = Propagation.MANDATORY )
public List< Account > readByProperty( String propertyName, String propertyValue )
{
Criteria criteria = sessionFactory.getCurrentSession().createCriteria( Account.class );
criteria.add( Restrictions.ilike( propertyName, propertyValue, MatchMode.EXACT ) );
return criteria.list();
}
@Transactional( readOnly = false, propagation = Propagation.MANDATORY )
public void update( Account account )
{
Account accountToModify = readById( account.getId() );
accountToModify.setCode( account.getCode() );
accountToModify.setName( account.getName() );
sessionFactory.getCurrentSession().update( accountToModify );
}
@Transactional( readOnly = false, propagation = Propagation.MANDATORY )
public void delete( Account account )
{
Account accountToModify = readById( account.getId() );
sessionFactory.getCurrentSession().delete( accountToModify );
}
}
Account.java :
This is the bean that describes what's in the database table. This simulates
a reasonable user account record. Addresses, credit card information, etc.
would be in yet other tables.
package com.etretatlogiciels.web.user.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@Entity
@Table( name = "account" )
public class Account implements Serializable
{
private static final long serialVersionUID = -7095851069703408708L;
private Integer id;
private String username;
private String firstname;
private String lastname;
private String fullname;
private String email;
private String telephone;
private String language;
private Date birthdate;
private boolean loggedin;
private Date lastlogin;
private Date created;
@Id
@GeneratedValue( strategy=GenerationType.AUTO, generator="account_seq_gen")
@SequenceGenerator( name="account_seq_gen", sequenceName="account_sequence" )
@Column( name = "id", unique = true, updatable = false )
public Integer getId() { return id; }
public void setId( Integer id ) { this.id = id; }
@Column( username="username", nullable=false, length=128 )
public String getUsername() { return username; }
public void setUsername( String username ) { this.username = username; }
@Column( firstname="firstname", nullable=true, length=64 )
public String getFirstname() { return firstname; }
public void setFirstname( String firstname ) { this.firstname = firstname; }
@Column( lastname="lastname", nullable=true, length=64 )
public String getLastname() { return lastname; }
public void setLastname( String lastname ) { this.lastname = lastname; }
@Column( fullname="fullname", nullable=true, length=256 )
public String getFullname() { return fullname; }
public void setFullname( String fullname ) { this.fullname = fullname; }
@Column( email="email", nullable=false, length=128 )
public String getEmail() { return email; }
public void setEmail( String email ) { this.email = email; }
@Column( telephone="telephone", nullable=true, length=32 )
public String getTelephone() { return telephone; }
public void setTelephone( String telephone ) { this.telephone = telephone; }
@Column( language="language", nullable=true, length=2 )
public String getLanguage() { return language; }
public void setLanguage( String language ) { this.language = language; }
@Column( name="birthdate", nullable=true )
public Date getBirthdate() { return birthdate; }
public void setBirthdate( Date birthdate ) { this.birthdate = birthdate; }
@Column( name="loggedin", nullable=false )
public boolean isLoggedin() { return loggedin; }
public void setLoggedin( boolean loggedin ) { this.loggedin = loggedin; }
@Column( name="lastlogin", nullable=true )
public Date getLastlogin() { return lastlogin; }
public void setLastlogin( Date lastlogin ) { this.lastlogin = lastlogin; }
@Column( name="created", nullable=false )
public Date getCreated() { return created; }
public void setCreated( Date created ) { this.created = created; }
/**
* This just facilitates displaying an Account object in the Eclipse debugger.
*/
public final String toString()
{
return "id = " + this.id + ", code = " + this.code + ", name = " + this.name;
}
}
account-table.sql :
I much prefer MySQL, which doesn't demand all this sequence crap just to
work perfectly. The harder case is PostgreSQL, so I show it instead.
DROP SEQUENCE IF EXISTS account_sequence CASCADE;
CREATE SEQUENCE account_sequence --PostgreSQL doesn't support MySQL's AUTOINCREMENT
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1; -- you'd think more, but it ends up affecting the sequence*
DROP TABLE IF EXISTS account;
CREATE TABLE account
(
id INTEGER NOT NULL PRIMARY KEY DEFAULT NEXTVAL( 'account_sequence' ),
username VARCHAR(128) NOT NULL,
password VARCHAR(256) NOT NULL,
firstname VARCHAR(64) NULL,
lastname VARCHAR(64) NULL,
fullname VARCHAR(256) NULL,
email VARCHAR(128) NOT NULL,
telephone VARCHAR(32) NULL,
language VARCHAR(2) NULL,
birthdate DATE NULL,
loggedin BOOLEAN NULL DEFAULT FALSE,
lastlogin TIMESTAMP NULL,
created TIMESTAMP NULL,
);
-- Put in some test data (a few rows of account)...
START TRANSACTION;
INSERT INTO account (id,username,password,firstname,lastname,fullname,email,phone,birthdate,loggedin,lastlogin,created)
VALUES ('zeke','h2342$!!s','Ezekiel','Mordecai','Ezekiel "Zeke" Mordecai','[email protected] ',
'339-555-1212','en','1984-08-11',FALSE,'2011-08-08 12:00:00','2011-08-08 12:00:00');
INSERT INTO account (id,username,password,firstname,lastname,fullname,email,phone,birthdate,loggedin,lastlogin,created)
VALUES ('trumpet','password','Sergei','Elephant',u&'\0441\043B\043E\043D','[email protected] ',
'8412.555.1212','ru','1955-06-18',FALSE,'2011-08-08 12:00:00','2011-08-08 12:00:00');
INSERT INTO account (id,username,password,firstname,lastname,fullname,email,phone,birthdate,loggedin,lastlogin,created)
VALUES ('ryota','pa$$xyz','Ryota','Natsume',u&'\68ee\9dd7\5916','[email protected] ',
'81 3 555-1212','jp','1970-05-08',FALSE,'2011-08-08 12:00:00','2011-08-08 12:00:00');
COMMIT;
-- Reset the sequencer or programmatically adding rows will not work until after a few attempts
-- fail since the sequencer will generate conflicting ids at first. MySQL doesn't need this
SELECT setval( 'auth_sequence', ( SELECT MAX( id ) FROM auth ) + 1 );
(* Setting the number of sequences to cache to other than 1 results in the
next OID generated being that many beyond the previous one—not sure
that's supposed to be, but it's my observation that it is.)
src/resources/spring-configuration.xml :
This is Spring hocus-pocus. It's not my objective to explain much of it.
WEB-INF/classes/resources/database.properties
${hibernate.dialect.class}
true
thread
org.hibernate.cache.NoCacheProvider
true
false
false
com.etretatlogiciels.web.user.entity.Account
src/resources/database.properties :
This is the variable part of what needs to be included in the
Spring/Hibernate configuration because it's going to be different
when running and testing in Eclipse versus Jenkins not to mention
an real production deployment.
database.username=russ
database.password=test123
database.url=jdbc:postgresql://localhost/usermgrdb
jdbc.driver.class=org.postgresql.Driver
hibernate.dialect.class=org.hibernate.dialect.PostgreSQLDialect
WEB-INF/web.xml :
As this is a web server, there's a web.xml too. Mostly, it sets up
a listener ready to initialize Spring. We're calling our servlet,
"usermgr".
usermgr
org.springframework.web.context.ContextLoaderListener
org.springframework.web.context.request.RequestContextListener
contextConfigLocation
/WEB-INF/classes/resources/spring-configuration.xml
Jersey REST Service
com.sun.jersey.spi.spring.container.servlet.SpringServlet
com.sun.jersey.config.property.packages
com.etretatlogiciels.web.user.service
com.sun.jersey.spi.container.ResourceFilters
com.etretatlogiciels.web.user.util.SharedSecurityFilter
1
Jersey REST Service
/*
src/log4j.properties :
log4j.rootLogger=TRACE,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
### direct log messages to stdout
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.logger.org.hibernate=debug
### make Hibernate log its SQL
log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters
log4j.logger.org.hibernate.type=debug
### log schema export/update
log4j.logger.org.hibernate.tool.hbm2ddl=debug
Libraries
For completeness, here is the list of third-party libraries consumed
in this solution.
russ ~/dev/usermgr/lib $ tree
.
|-- aopalliance
| |-- aopalliance.jar
| `-- cglib-2.2.2.jar
|-- apache
| |-- commons-collections-3.2.1.jar
| |-- commons-configuration-1.7.jar
| |-- commons-lang-2.6.jar
| |-- httpclient-4.1.3.jar
| |-- jakarta-oro-2.0.8.jar
| |-- log4j-1.2.16.jar
| `-- servlet-api.jar
|-- gson
| `-- gson-1.7.1.jar
|-- hibernate
| |-- antlr-2.7.6.jar
| |-- c3p0-0.9.1.jar
| |-- dom4j-1.6.1.jar
| |-- hibernate3.jar
| |-- hibernate-jpa-2.0-api-1.0.1.Final.jar
| |-- javassist-3.12.0.GA.jar
| `-- jta-1.1.jar
|-- jersey
| |-- asm-3.1.jar
| |-- jersey-client-1.6.jar
| |-- jersey-core-1.6.jar
| |-- jersey-json-1.6.jar
| |-- jersey-server-1.6.jar
| |-- jersey-spring-1.12-b01.jar
| |-- oauth-server-1.10.jar
| `-- oauth-signature-1.10.jar
|-- junit
| `-- junit-4.10.jar
|-- postgres
| `-- postgresql-9.1-901.jdbc4.jar
|-- slf4j
| |-- slf4j-api-1.6.1.jar
| `-- slf4j-nop-1.6.1.jar
`-- spring
|-- org.springframework.aop-3.1.1.RELEASE.jar
|-- org.springframework.asm-3.1.0.RC2.jar
|-- org.springframework.beans-3.1.0.RC2.jar
|-- org.springframework.context-3.1.0.RC2.jar
|-- org.springframework.core-3.1.0.RC2.jar
|-- org.springframework.expression-3.1.0.RC2.jar
|-- org.springframework.jdbc-3.1.0.RC2.jar
|-- org.springframework.orm-3.1.0.RC2.jar
|-- org.springframework.transaction-3.1.0.RC2.jar
`-- org.springframework.web-3.1.0.RC2.jar
Sample URIs to this service
To the path http://<hostname>:<port>/usermgr , add the URLs
below:
Verb
URL
Operation
Representation
Sample payload
POST
/account/
Create a new account
http://<hostname>:<port>/usermgr/account/
{"username":"ryota", etc. }
GET
/account/
Read all account
http://<hostname>:<port>/usermgr/account/
(no payload)
GET
/account/{id}
Read one account
http://<hostname>:<port>/usermgr/account/552
(no payload)
PUT
/account/{id}
Update a account
http://<hostname>:<port>/usermgr/account/35
{"username":"trumpet", etc. }*
DELETE
/account/{id}
Delete an account
http://<hostname>:<port>/usermgr/account/552
(no payload)
Quick-start for writing a ReST application/server
This is a complete template using all of the annotations discussed here. To
get them all in one place, here are the imports used throughout this pseudocode.
import org.apache.log4j.Logger;
import org.bson.types.ObjectId;
import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.DELETE;
import javax.ws.rs.Path;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.query.Query;
Service entry points
@Path( "/service" )
public class Service
{
private static Logger log = Logger.getLogger( Service.class );
private Manager manager = new Manager();
@POST
@Consumes( { APPLICATION_JSON, APPLICATION_XML } )
@Produces( { APPLICATION_JSON, APPLICATION_XML } )
public Response create( PojoDto proposed )
{
Pojo pojo = null;
try
{
pojo = manager.create( proposed );
}
catch( Exception e )
{
return buildResponse();
}
catch( RuntimeException e )
{
return buildRuntimeResponse( e );
}
PojoDto dto = pojo.dto();
URI createdUri = URI.create( pojo.getOid().toString() );
return Response.created( createdUri ).entity( dto ).build();
}
@GET
@Path( "/{oid}" )
@Produces( { APPLICATION_JSON, APPLICATION_XML, TEXT_HTML } )
public Response readByOid( @PathParam( "oid" ) ObjectId oid )
{
Pojo pojo = null;
try
{
pojo = manager.readByOid( oid );
}
catch( Exception e )
{
return buildResponse();
}
catch( RuntimeException e )
{
buildRuntimeResponse( e );
}
PojoDto dto = pojo.dto();
return Response.ok( dto ).build();
}
@GET
@Path( "/{identity}" )
@Produces( { APPLICATION_JSON, APPLICATION_XML, TEXT_HTML } )
public Response readByIdentity( @PathParam( "identity" ) String Identity )
{
List< Pojo > pojos = null;
try
{
pojos = manager.readByIdentity( identity );
}
catch( Exception e )
{
return buildResponse();
}
catch( RuntimeException e )
{
buildRuntimeResponse( e );
}
GenericEntity< List< T > > list = new GenericEntity< List< T > >( pojos ) { };
return Response.ok( list ).build();
}
@PUT
@Path( "/{oid}" )
@Consumes( { APPLICATION_JSON, APPLICATION_XML } )
@Produces( { APPLICATION_JSON, APPLICATION_XML } )
public Response update( @PathParam( "oid" ) ObjectId oid, PojoDto proposed )
{
Pojo pojo = null;
try
{
pojo = manager.update( oid, proposed );
}
catch( Exception e )
{
return buildResponse();
}
catch( RuntimeException e )
{
return buildRuntimeResponse( e );
}
PojoDto dto = pojo.dto();
return Response.ok( dto ).build();
}
@DELETE
@Path( "/{oid}" )
@Produces( { APPLICATION_JSON, APPLICATION_XML } )
public Response delete( @PathParam( "oid" ) ObjectId oid )
{
try
{
manager.delete( oid );
}
catch( AppException e )
{
return buildResponse();
}
catch( RuntimeException e )
{
return buildRuntimeResponse( e );
}
return Response.noContent().build();
}
}
Business-level logic (manager)
public class Manager
{
private static Logger log = Logger.getLogger( Manager.class );
private Dao dao;
public Manager( Dao dao )
{
this.dao = dao;
}
public Pojo create( PojoDto proposed ) throws Exception
{
return dao.create( proposed );
}
public Pojo readByOid( ObjectId oid ) throws Exception
{
return dao.readByOid( oid );
}
public Pojo readByIdentity( PojoDto dto ) throws Exception
{
return dao.readByIdentity( dto.getIdentity() );
}
public Pojo update( ObjectId oid, PojoDto proposed ) throws Exception
{
dao.update( oid, proposed );
return proposed;
}
public Pojo delete( ObjectId oid ) throws Exception
{
dao.delete( oid );
}
}
POJOs
@XmlAccessorType( XmlAccessType.FIELD )
public abstract class PojoCommonBase implements Serializable
{
ObjectId oid;
String identity;
}
@XmlAccessorType( XmlAccessType.FIELD )
@XmlRootElement
@Entity( value=DatabaseProperties.COLLECTION, noClassnameStored=true )
public class Pojo extends PojoCommonBase implements Cloneable
{
}
DTOs
@XmlAccessorType( XmlAccessType.FIELD )
@XmlRootElement( name="pojo" )
@JsonSerialize( include=JsonSerialize.Inclusion.NON_NULL )
@JsonAutoDetect( fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE )
public class PojoDto extends PojoCommonBase
{
}
DAOs
The example here happens to be MongoDB/Morphia-flavored. MongoDB
is privately implemented and not shown here, but the MongoDB details are
here only to suggest an implementation and are not germane to the ReST
template.
public class Dao
{
private static final Logger log = Logger.getLogger( Dao.class );
private MongoDB mongoDB;
private DBCollection collection;
private Datastore datastore;
private static class DaoHolder
{
public static final Dao INSTANCE = new Dao();
}
private Dao()
{
MongoDBManager manager = MongoDBManager.getInstance();
mongoDB = manager.getMongoByDatabaseName( DatabaseProperties.DATABASE );
collection = mongoDB.getCollection( DatabaseProperties.DATABASE, DatabaseProperties.COLLECTION );
datastore = mongoDB.getDatastore( DatabaseProperties.DATABASE );
}
public static Dao getInstance()
{
log.trace( "Dao getInstance()" );
return DaoHolder.INSTANCE;
}
public DBCollection getCollection() { return this.collection; }
public Datastore getDatastore() { return this.datastore; }
public Class< Pojo > getClazz() { return Pojo.class; }
public Pojo create( Pojo pojo )
{
datastore.save( pojo );
return pojo;
}
public Pojo readByOid( ObjectId oid )
{
Query< T > query = datastore.createQuery( Pojo.class )
.filter( "_id", oid );
return query.get();
}
public Pojo readByIdentity( String identity )
{
Query< Pojo > query = datastore.createQuery( Pojo.class );
query.and( query.criteria( "identity" ).equal( identity );
return query.get();
}
public void update( Pojo modify )
{
datastore.merge( modify );
}
public void delete( Pojo entity )
{
datastore.delete( entity );
}
}