So far, I have gotten some very useful feedback for RESTEasy. One excellent set of questions came from the rest-discuss group on Yahoo!:
How do you map different parts of the namespace to the same component? What, for instance, do GETs to the following resources result in:
 http://localhost/resteasy/contacts/12345/something  http://localhost/resteasy/contacts/12345?myparam=whatever
The short answer is that you could map the URI to the following Java method:
@HttpMethod(GET) public Something getSomethingFromContact(URIParam("contactId") Long id);
This would return just the “something” attribute for the contact 12345.
While this approach works just fine, it could become cumbersome if the Contact contians additional members which represent complex entities. The Contact class already contains multiple entities:
- a collection of Addresses
- a collection of EmailAddresses
- a collection of PhoneNumbers
- a Company if the Contact is a Person instance and the Person is employed
This list could easily expand depending on what your needs are. But you can see that this would add a lot of extra code to your service.
I started thinking there there might be a better way to this without the need to even write a service class. In the service examples I have been working on are effectively DAOs that return Java objects. The code is so generic and repetative accross most services, that it might be possible to just expose the entity as a service.
To achive this, I have started playing around with the idea of a @RestfulEntity
annotatation. At this point, it is just a thought an no code has been written yet, but I thought it would be a good idea to share what I’m thinking. When RESTEasy processes this annotation, it will create a DAO-style service that will accept GET,POST,PUT, and DELETE. An Entity Bean could be annoated as follows:
@Entity @RestfulEntity(baseURI="/contacts", entityURI="/contacts/{contactId}", readOnly=false, persistenceStrategy=PersistenceStrategy.JAXB_HIBERNATE, excludeSearchProperties={"id","isActive"}, ignoreCase=true) @MediaTypes( types={ @MediaType("application/xml"), @MediaType("application/contact+xml") }) public class Contact implements Auditable { @Id @Column(name = "contact_id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) @XmlAttribute @URIParam("contactId") private Long id; private String firstName; private String lastName; private boolean isActive; @XmlElementWrapper(name="addresses") @XmlElement(name="address",required = true, nillable = true) @OneToMany(cascade = CascadeType.ALL, mappedBy = "contact") private Set addresses = new HashSet(); [...]
The annotation requires URI values:
- baseURI: the base resources that contains all entities of this type
- entityURI: the URI that represents a specific entity
In this example, the entityURI
is used to handle all updates, deletes, and retrieval by ID operations. The baseURI
determines where all contacts are stored. You can create a new resource by issuing a PUT with the entity data to the following URI:
http://localhost/contacts
A 201 response is issued by the server and the Location of the new entity is returned. The URI in the Location header will be one that matches the entityURI.
You can also perform search operations using a GET or a POST opertation and adding the approiate query parameters to the URI:
http://localhost/contacts?firstName=ry&lastName=mcd
By default, you can search for a contact using the property names. You can exclude specific properties by adding them to the excludeSearchProperties
value. The idea is very silimar to Hibernates “query by example” Criteria query.
Since since the Contact contains a collection of Addresses, you can access an address independently of the Contact:
@RestfulEntity(baseURI="/contacts/{contactId}/addresses", entityURI="/contacts/{contactId}/addresses/{addressId}", readOnly=false, persistenceStrategy=PersistenceStrategy.HIBERNATE) public class Address implements Auditable { @Id @Column(name = "contact_id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) @XmlAttribute @URIParam("addressId") private Long id; @ManyToOne @JoinColumn(name = "contact_id") @URIParam("contactId") @XmlTransient private Contact contact; [...]
The persistenceStrategy
would be used to determine how the data is persisted and retrieved. Currently, I am thinking about the following types of options:
- FILE: Uses the file system to persist objects.
- JPA: uses dynamic queries to return the necessary portions of an object graph
- HIBERNATE: Similar to JPA, but leverages the Hibernate Criteria API.
Lastly, the @MediaTypes
annotation determines the mime types that are applicable for this type entity.
This is just in the idea phase at the the moment and it’s not a finished idea. It’s still half-baked and I’m sure I forgot something along the way. But if you have any suggestions on the idea, please feel free to post a comment.
Pingback: DamnHandy : » RESTEasy and Seam