понедельник, марта 17, 2014

RESTFul with Apache Camel

После длительного перерыва, рассмотрим как реализовать с помощью Apache Camel простейший REST-сервис. Реализовать подобное с помощью Apache Camel можно навскидку 3-мя способами. Мне как раз был необходим сервис по хранению и управлению настройками в формате key=value. REST необходим чтобы в дальнейшем сделать на AngularJS страницу для управления настройками.
Итак Apache Camel предоставляет следующие возможности для реализации REST сервиса
  1. CXF Bean Component
  2. Restlet Component
  3. CXFRS Component
CXF Bean Component и CXFRS Component очень похожи друг на друга, и строятся на основе отдельного java-bean помеченного специальными аннотациями, и отличаются лишь обвязкой и инициализацией.Лично меня привлек CXF Bean Component который поддерживает из коробки одновременно два стандарта JAXRS, JAXWS. Что позволяет управлять выводом XML-JSON, плюс есть возможность использовать разработанный код для реализации SOAP WebService.
Сам код на Apache Camel очень лаконичен:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camel="http://camel.apache.org/schema/spring"
 xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

 <context:component-scan base-package="com.bssys.*" />
 
 <bean id="itemRestService" class="com.bssys.rest_service.ItemRestService" />
 
 <camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
   <from uri="jetty:http://localhost:8787?matchOnUriPrefix=true" />
   <to uri="cxfbean:itemRestService" />
  </route>
 </camelContext>
</beans>

Детали скрываются в реализации ItemRestService. Ниже я опишу полную последовательность разработки
Сам сервис я попытался сделать захватывающим разные аспекты связывания данных.
Общий путь к REST-сервису:
http://localhost:8787/example
GET http://localhost:8787/example/getAllItem
GET http://localhost:8787/example/getItem/{key}
PUT http://localhost:8787/example/updateItem
PUT http://localhost:8787/example/addItem
DELETE http://localhost:8787/example/removeItem/{key}

getAllItem- GET запрос без параметров
getItem/{key}- GET запрос с запросе указывающий имя получаемого свойства
updateItem - PUT запрос обновления свойства. В теле запроса передается JSON
addItem - PUT запрос добавления свойства. В теле запроса передается JSON
removeItem/{key} - DELETE запрос на удаление свойства.

Изначально, для прозрачной работы с данными нам понадобится модель данных. У меня она предоставлена в виде  XSD-схемы.
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"  targetNamespace="http://bssys.com/item"
 xmlns:tns="http://bssys.com/item" elementFormDefault="qualified">
 
 <xsd:element name="itemRequest" type="tns:itemRequest"/>
 <xsd:element name="itemResponse" type="tns:itemResponse"/>
 
 <xsd:complexType name="item">
  <xsd:sequence>
   <xsd:element name="key" type="xsd:string"/>
   <xsd:element name="value" type="xsd:string"/>
  </xsd:sequence>
 </xsd:complexType>
 
 <xsd:complexType name="itemRequest">
  <xsd:sequence>
   <xsd:element name="item" type="tns:item"/>
  </xsd:sequence>
 </xsd:complexType>
 
 <xsd:complexType name="itemResponse">
  <xsd:sequence>
   <xsd:element name="item" minOccurs="0" maxOccurs="unbounded" type="tns:item"/>
   <xsd:element name="errorMessage" type="xsd:string"/>
  </xsd:sequence>
 </xsd:complexType>
</xsd:schema>

На основе который мы сгенерируем JAXB классы. Именно знакомый нам JAXB отвечает за "marshaling" из разных форматов. На самом деле, без этого этапа можно запросто обойтись. XSD-схема далее применяться не будет. Конечно можно разработать нужные классы вручную, но в таком подходе сервис получается стройнее, и появляется возможность в дальнейшем переработать его в SOAP Webservice.

После генерации имеем следующие классы:
  • Item
  • ItemRequest
  • ItemResponse
  • ObjectFactory
Классы уже анотированы атонациями JAXB. Для возможности использования в составе REST сервиса, необходимо только добавить анотацию @XmlRootElement в классы ItemRequest ItemResponse.
На их основе разработаем интерфейс нашего сервиса:

package com.bssys.service;

import org.springframework.stereotype.Service;
import com.bssys.jaxb.model.ItemRequest;
import com.bssys.jaxb.model.ItemResponse;

@Service
public interface ItemService {
 public ItemResponse addItem(ItemRequest request);
 public ItemResponse updateItem(ItemRequest request);
 public ItemResponse getItem(String key);
 public ItemResponse getAllItem();
 public ItemResponse removeItem(String key);
 public ItemRequest stubRequest();
}
Слой DAO:
package com.bssys.dao;

import java.util.List;

import org.springframework.stereotype.Service;

import com.bssys.jaxb.model.Item;

@Service
public interface ItemManagerDao {
 public Item getItem(String name);
 public void deleteItem(String name);
 public List getAllItems();
 public void insertItem(Item item);
 public void updateItem(Item item);
}
Далее простейшая реализация "in memory":

package com.bssys.dao;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Component;

import com.bssys.jaxb.model.Item;


@Component
public class ItemManagerMemoryDao implements ItemManagerDao {
 
 private List items = new ArrayList();

 @Override
 public Item getItem(String name) {
  // TODO Auto-generated method stub
  for (Item item: items){
   String key = item.getKey();
   if (key.equals(name)){
    return item;
   }
  }
  throw new RuntimeException("Item Not Found: " + name);
 }

 @Override
 public List getAllItems() {
  // TODO Auto-generated method stub
  return items;
 }

 @Override
 public void insertItem(Item item) {
  // TODO Auto-generated method stub
  items.add(item);
 }

 @Override
 public void updateItem(Item item) {
  // TODO Auto-generated method stub
  Item storeItem = getItem(item.getKey());
  storeItem.setValue(item.getValue());
 }

 @Override
 public void deleteItem(String name) {
  // TODO Auto-generated method stub
  Item storeItem = getItem(name);
  items.remove(storeItem);
 }
}
И сам REST-сервис:
package com.bssys.rest_service;

import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

import org.springframework.beans.factory.annotation.Autowired;

import com.bssys.dao.ItemManagerMemoryDao;
import com.bssys.jaxb.model.Item;
import com.bssys.jaxb.model.ItemRequest;
import com.bssys.jaxb.model.ItemResponse;
import com.bssys.service.ItemService;

@Path("/example")
public class ItemRestService implements ItemService{
 @Autowired
 private ItemManagerMemoryDao dao;

 @PUT
 @Path("addItem")
 @Consumes("application/json")
 @Produces("application/json")
 public ItemResponse addItem(ItemRequest request) {
  // TODO Auto-generated method stub
  ItemResponse userResponse = new ItemResponse();
  try{
   dao.insertItem(request.getItem());
   userResponse.setErrorMessage("Ok");
  }catch(Exception e){
   userResponse.setErrorMessage("Error: "+e.getMessage());
  }
  return userResponse;
 }

 @PUT
 @Path("updateItem")
 @Consumes("application/json")
 @Produces("application/json")
 public ItemResponse updateItem(ItemRequest request) {
  // TODO Auto-generated method stub
  ItemResponse userResponse = new ItemResponse();
  try{
   dao.updateItem(request.getItem());
   userResponse.setErrorMessage("Ok");
  }catch(Exception e){
   userResponse.setErrorMessage("Error: "+e.getMessage());
  }
  return userResponse;
 }

 @GET
 @Path("getItem/{key}")
 @Produces("application/json")
 public ItemResponse getItem(@PathParam("key") String key) {
  // TODO Auto-generated method stub
  ItemResponse userResponse = new ItemResponse();
  try{
   Item item = dao.getItem(key);
   userResponse.getItem().add(item);
   userResponse.setErrorMessage("Ok");
  }catch(Exception e){
   userResponse.setErrorMessage("Error: "+e.getMessage());
  }
  return userResponse;
 }

 @GET
 @Path("getAllItem")
 @Produces("application/json")
 public ItemResponse getAllItem() {
  // TODO Auto-generated method stub
  ItemResponse userResponse = new ItemResponse();
  try{
   List allItems = dao.getAllItems();
   List item = userResponse.getItem();
   item.addAll(allItems);
   userResponse.setErrorMessage("Ok");
  }catch(Exception e){
   userResponse.setErrorMessage("Error: "+e.getMessage());
  }
  return userResponse;
 }

 @DELETE
 @Path("removeItem/{key}")
 @Consumes("application/json")
 @Produces("application/json")
 public ItemResponse removeItem(@PathParam("key") String key) {
  // TODO Auto-generated method stub
  ItemResponse userResponse = new ItemResponse();
  try{
   dao.deleteItem(key);
   userResponse.setErrorMessage("Ok");
  }catch(Exception e){
   userResponse.setErrorMessage("Error: "+e.getMessage());
  }
  return userResponse;
 }

 @Override
 @GET
 @Path("stubRequest")
 @Produces("application/json")
 public ItemRequest stubRequest() {
  // TODO Auto-generated method stub
  ItemRequest itemRequest = new ItemRequest();
  Item item = new Item();
  item.setKey("key");
  item.setValue("value");
  itemRequest.setItem(item);
  return itemRequest;
 }
}
Все это собирается как OSGI-модуль готовый к развертыванию в ServiceMi. Исходники на BitBucket https://Hibernate2009@bitbucket.org/Hibernate2009/restful-with-apache-camel.git

Комментариев нет: