I want to expose a RESTfull webservice using camel. I have used URI templates to define my service contracts. I want to know how should I route the requests to the relevant method of my ServiceProcessor based on the URI template.
As an example, take the following two operations:
#GET
#Path("/customers/{customerId}/")
public Customer loadCustomer(#PathParam("customerId") final String customerId){
return null;
}
#GET
#Path("/customers/{customerId}/accounts/")
List<Account> loadAccountsForCustomer(#PathParam("customerId") final String customerId){
return null;
}
Following is the route I have used:
<osgi:camelContext xmlns="http://camel.apache.org/schema/spring">
<route trace="true" id="PaymentService">
<from uri="`enter code here`cxfrs://bean://customerCareServer" />
<process ref="customerCareProcessor" />
</route>
</osgi:camelContext>
Is there any way that I can match the uri header(Exchange.HTTP_PATH) to an existing template uri in my service definition?
Following is the Service Contract for my service:
#Path("/customercare/")
public class CustomerCareService {
#POST
#Path("/customers/")
public void registerCustomer(final Customer customer){
}
#POST
#Path("/customers/{customerId}/accounts/")
public void registerAccount(#PathParam("customerId") final String customerId, final Account account){
}
#PUT
#Path("/customers/")
public void updateCustomer(final Customer customer){
}
#PUT
#Path("/accounts/")
public void updateAccount(final Account account){
}
#GET
#Path("/customers/{customerId}/")
public Customer loadCustomer(#PathParam("customerId") final String customerId){
return null;
}
#GET
#Path("/customers/{customerId}/accounts/")
List<Account> loadAccountsForCustomer(#PathParam("customerId") final String customerId){
return null;
}
#GET
#Path("/accounts/{accountNumber}")
Account loadAccount(#PathParam("accountNumber") final String accountNumber){
return null;
}
}
The cxfrs endpoint consumer provides a special exchange header operationName that contains a service method name (registerCustomer, registerAccount, etc).
You could decide what to do with a request using this header like following:
<from uri="cxfrs://bean://customerCareServer" />
<choice>
<when>
<simple>${header.operationName} == 'registerCustomer'</simple>
<!-- registerCustomer request processing -->
</when>
<when>
<simple>${header.operationName} == 'registerAccount'</simple>
<!-- registerAccount request processing -->
</when>
....
</choice>
Related
I'm using this as a reference to create a REST only configuration on Struts2:
https://cwiki.apache.org/confluence/display/WW/REST+Plugin
I have one model, Receipt with a few test fields: title, body.
Currently to create a receipt, I send a request in this way:
POST /receipt/?body=new_body&title=new_title
and it creates me a receipt with the new body and title passed in.
This doesn't work:
POST /receipt/
{
"body": "new_body",
"title": "new title"
}
Here's some code:
struts.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<bean type="org.apache.struts2.rest.handler.ContentTypeHandler" name="jackson" class="org.apache.struts2.rest.handler.JacksonLibHandler"/>
<constant name="struts.rest.handlerOverride.json" value="jackson"/>
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>
<constant name="struts.devMode" value="true"/>
<constant name="struts.rest.content.restrictToGET" value="false"/>
<constant name="struts.rest.defaultExtension" value="json"/>
<constant name="struts.rest.handlerOverride.EXTENSION" value="json"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<constant name="struts.action.extension" value="xhtml,,xml,json,action"/>
<constant name="struts.mapper.class" value="org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper" />
<constant name="struts.mapper.prefixMapping" value="/receipt:rest,:struts"/>
<constant name="struts.convention.action.suffix" value="Controller"/>
<constant name="struts.convention.action.mapAllMatches" value="true"/>
<constant name="struts.convention.default.parent.package" value="receipto"/>
<constant name="struts.convention.package.locators" value="controllers,actions"/>
</struts>
ReceiptController.java:
public class ReceiptController implements ModelDriven<Object> {
private ReceiptManager receiptManager = new ReceiptManager();
private String id;
private Receipt model = new Receipt();
private Collection list;
public Object getModel()
{
return (list==null ? model : list);
}
public HttpHeaders create()
{
receiptManager.save(model);
return new DefaultHttpHeaders("create");
}
public HttpHeaders show()
{
model = receiptManager.find(id);
return new DefaultHttpHeaders("show");
}
public HttpHeaders update()
{
receiptManager.save(model);
return new DefaultHttpHeaders("update");
}
public HttpHeaders destroy()
{
model = receiptManager.destroy(id);
return new DefaultHttpHeaders("destroy");
}
public HttpHeaders index()
{
list = receiptManager.list();
return new DefaultHttpHeaders("index").disableCaching();
}
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
}
Is it supposed to work as I want it to, or is it just how the plugin works?
I guess that postman is sending JSON in the body of the request and sets the content type application/json. Struts can parse the request if you add json interceptor to the stack.
<interceptor-stack name="myStack">
<interceptor-ref name="json"/>
<interceptor-ref name="myInterceptor"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
The description for "json" interceptor in the JSON Plugin:
If the interceptor is used, the action will be populated from the JSON content in the request, these are the rules of the interceptor:
The "content-type" must be "application/json"
The JSON content must be well formed, see json.org for grammar.
Action must have a public "setter" method for fields that must be populated.
Supported types for population are: Primitives (int,long...String), Date, List, Map, Primitive Arrays, Other class (more on this later), and Array of Other class.
Any object in JSON, that is to be populated inside a list, or a map, will be of type Map (mapping from properties to values), any whole number will be of type Long, any decimal number will be of type Double, and any array of type List.
Resources:
Kickstart FAQ
Getting Started
FAQs
Other Resources
We would usually define POST and PUT verbs as different service APIs.
#POST
#Path("/getbook")
#Produces({"application/xml","application/json"})
#Consumes({"application/xml","application/json","application/x-www-form-urlencoded"})
public Response getBucket() {
... }
#PUT
#Path("/getbook/{name}")
#Produces({"application/xml","application/json"})
#Consumes({"application/xml","application/json","application/x-www-form-urlencoded"})
public Response getBucket(#PathParam("name") String name) {
... }
Would there be a way to combine these verbs into a single method - and then drive different logic based on the type of the verb ?
Hypothetically
#POST
#PUT
#Path("/getbook/{name}")
#Produces({"application/xml","application/json"})
#Consumes({"application/xml","application/json","application/x-www-form-urlencoded"})
public Response getBucket(#PathParam("name") String name) {
if(verb=POST){
... }
else{
}
}
You may try like this using MessageContext. You need the context injected into the service method like below for updateCustomer method and then you can check for the method type as you like (here I am checking for PUT):
#Path("/customer")
public class CustomerService {
#Context
private org.apache.cxf.jaxrs.ext.MessageContext mc;
#PUT
public Response updateCustomer(#Context MessageContext context, Customer c) {
HttpServletRequest request = context.getHttpServletRequest();
boolean isPut = "PUT".equals(request.getMethod());
}
}
Within a Unit/Integration Test, I'm trying to use the RESTEasy embedded server TJWSEmbeddedJaxrsServer or POJOResourceFactory inorder to simulate through a MockHttpRequest.get("/data") a resource call for test purpose.
My problem is that based on the use of the server or the Resource factory I'm not able to have a non null instance of spring beans which are injected normally within my resources.
Here's some code for clarification, thanks in advance.
Spring application context :
<context:annotation-config />
<context:component-scan base-package="com.cdcfast.service" />
<bean id="simpleResource" class="com.cdcfast.rest.SimpleResource" />
SimpleResource.java :
#Component
#Path("/data")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class SimpleResource {
#Autowired
private SimpleService service;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Data> getData() {
return MockDataBase.getInstance().getRows();
}
Unit Test :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath*:/test/spring/testApplicationContext.xml" })
public class FakeTest {
private Dispatcher dispatcher;
#Before
public void before() {
dispatcher = MockDispatcherFactory.createDispatcher();
POJOResourceFactory noDefaults = new POJOResourceFactory(SimpleResource.class);
dispatcher.getRegistry().addResourceFactory(noDefaults);
}
#Test
public void aTestThatAlwaysPass() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/data");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assertions.assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
Assertions.assertThat(response.getContentAsString()).isNotNull().isNotEmpty();
}
}
I've had this before because the RESTEasy factories create the POJO rather than Spring so they don't get wired up which can be worked around in the full container but is less easy in a test. The best way around this is to get a handle to your POJO once the factory creates it and then do something similar to this:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(myPojo);
I personally ended up having Spring create the RESTEasy beans using the RESTEasy-Spring plugin and then launching my tests using Jetty, not sure if that is an option for you though.
I exeprienced same problem and i'have solved in similar way as James did:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:spring-context-test.xml" })
public class TestMyService {
Dispatcher dispatcher;
private String username = "user";
#Autowired
ApplicationContext context;
#Before
public void setUp() {
MyService g = new MyService(); //rest service with #autowired spring beans
context.getAutowireCapableBeanFactory().autowireBean(g);
dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(g);
}
#Test
public void TestRest() {
MockHttpRequest request;
try {
request = MockHttpRequest.get("/rest/service").header("LOGON_USER", username);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertTrue("Error, unexpected status code: " + response.getStatus(), response.getStatus() == 200);
LoggerFactory.getLogger(this.getClass()).info("********** " + response.getContentAsString());
} catch (URISyntaxException e) {
Log.error(e.getMessage(), e);
fail(e.getMessage());
}
}
}
I have worked on Web services using Jaxb earlier. I geneated Java from xsd, and then I used to post the xml request to the specified URL using HTTP post. Recently I heard about this Restful web services, on reading I felt that what I had been doing earlier is the restful web service only. But, I am not sure about it if its the same thing.
Can anyone explain please.
It sounds like you have been creating the same types of RESTful services. You may be referring to is JAX-RS with is a standard that defines an easier way of creating RESTful services where JAXB is the standard binding layer for the application/xml media type. Below is an example service:
package org.example;
import java.util.List;
import javax.ejb.*;
import javax.persistence.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
#Stateless
#LocalBean
#Path("/customers")
public class CustomerService {
#PersistenceContext(unitName="CustomerService",
type=PersistenceContextType.TRANSACTION)
EntityManager entityManager;
#POST
#Consumes(MediaType.APPLICATION_XML)
public void create(Customer customer) {
entityManager.persist(customer);
}
#GET
#Produces(MediaType.APPLICATION_XML)
#Path("{id}")
public Customer read(#PathParam("id") long id) {
return entityManager.find(Customer.class, id);
}
#PUT
#Consumes(MediaType.APPLICATION_XML)
public void update(Customer customer) {
entityManager.merge(customer);
}
#DELETE
#Path("{id}")
public void delete(#PathParam("id") long id) {
Customer customer = read(id);
if(null != customer) {
entityManager.remove(customer);
}
}
}
For More Information
http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-45.html
When it comes to say 'RESTful', it's just an convention of HTTP methods and url patterns.
CRUD METHOD URL RESPONSE DESCRIPTION
----------------------------------------------------------------
CREATE POST http://www.doma.in/people 202 Creates a new person with given entity body
READ GET http://www.doma.in/people 200
READ GET http://www.doma.in/people/1 200 404 Reads a single person
UPDATE PUT http://www.doma.in/people/2 204 Updates a single person with given entity body
DELETE DELETE http://www.doma.in/people/1 204 Deletes a person mapped to given id(1)
You can even implement those kind of contracts with Sevlets. Actually I had done with Sevlets before the era of JAX-RS.
And your life will be much more easier when you use JAX-RS.
Here comes a slightly modified version of Mr. Blaise Doughan's.
Nothing's wrong with Mr. Blaise Doughan's code.
I just want to add more for above url patterns.
One of great things that JAX-RS can offer is that you can serve XMLs and JSONs as clients want if you have those fine JAXB classes. See #Producess and #Consumess for those two formats in same method.
When client want to receive as XML with Accept: application/xml, they just get the XML.
When client want to receive as JSON with Accept: application/json, they just get the JSON.
#Path("/customers");
public class CustomersResource {
/**
* Reads all person units.
*/
#POST
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response read() {
final List<Customer> listed = customerBean.list();
final Customers wrapped = Customers.newInstance(listed);
return Response.ok(wrapped).build();
}
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createCustomer(final Customer customer) {
entityManager.persist(customer);
return Response.created("/" + customer.getId()).build();
}
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/{id: \\d+}")
public Response read(#PathParam("id") final long id) {
final Customer customer = entityManager.find(Customer.class, id);
if (customer == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok(customer).build();
}
#PUT
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void updateCustomer(final Customer customer) {
entityManager.merge(customer);
}
#DELETE
#Path("/{id: \\d+}")
public void deleteCustomer(#PathParam("id") final long id) {
final Customer customer = entityManager.find(Customer.class, id);
if (customer != null) {
entityManager.remove(customer);
}
return Response.status(Status.NO_CONTENT).build();
}
}
Say you want to serve some images?
#GET
#Path("/{id: \\d+}")
#Produces({"image/png", "image/jpeg"})
public Response readImage(
#HeaderParam("Accept") String accept,
#PathParam("id") final long id,
#QueryParam("width") #DefaultValue("160") final int width,
#QueryParam("height") #DefaultValue("160") final int height) {
// get the image
// resize the image
// make a BufferedImage for accept(MIME type)
// rewrite it to an byte[]
return Response.ok(bytes).build();
// you can event send as a streaming outout
return Response.ok(new StreamingOutput(){...}).build();
}
I wish to catch an XML (HashMap) of this format in my POST handler
<entries>
<entry>
<id>1</id>
<labels>
<label>label1</label>
<label>label2</label>
...
</labels>
<entry>
...
<entries>
I wish my POST handler using Apache Jersey to look like this
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createEntries(#MagicAnnotation HashMap<id, List<label>> entryMap){
}
What is the closest I can get to this?
I am open to a better representation of a HashMap to XML. I just don't wish to parse xml manually and want to catch equivalent JSON as well.
I am not sure if some JAXBElement can be used instead of jersey annotation.
Your are not supposed to access the POST body this way.
You have to define several classes to let Jersey parse the XML response.
Your code will look like:
#POST
public Response post(Entries entries) {
Storage.put(entries);
return Response.ok().build();
}
To make this work Entries have to be JAXB compatible:
#XmlRootElement
public class Entries {
#XmlElement List<Entry> entries;
}
#XmlType
public class Entry {
#XmlAttribute String id;
#XmlElement Labels labels;
}
#XmlType
public class Labels {
#XmlElement List<String> label;
}
Ah, and Jersey is not from Apache but from Sun.