Apache CXF – Contexts

noviembre 8, 2012

Apache CXF – Contexts

Si necesitas información del contexto en un servicio web basado en Apache CXF, lo puedes hacer mediante la anotación @Resource y las clases WebServiceContext (JAX-WS) y MessageContext (JAX-RS).

@Path("/customers")
@WebService
public class CustomerService {

   @Resource WebServiceContext jaxwsContext;
   @Resource MessageContext jaxrsContext;

   @WebMethod
   @POST
   public void doIt(String b) {
       isUserInRole();
   };

   private void isUserInRole() throws WebApplicationException {
       if (jaxwsContext.getSecurityContext() != null) {
           // soap invocation
           jaxwsContext.getSecurityContext().isUserInRole(theRole);
       } else {
           // http-only jaxrs one
           jaxrsContext.getSecurityContext().isUserInRole(theRole);
       }  
   }
}

via Apache CXF — JAX-RS and JAX-WS.

Anuncios

Apache CXF – Ejemplo de firma de un documento XML

Los beneficios de firmar digitalmente un documento XML en nuestros servicios web son que nos provee autenticación, integridad de los datos y el no repudio de la fuente que invoca el servicio.

Para ver un ejemplo de cómo puedes configurar Apache CXF para que utilice y verifique las firmas en un servicio web, sigue el enlace.

via Adding X.509 security headers to Apache CXF SOAP calls | Glen Mazza’s Weblog.

Para configurar Apache CXF en una aplicación web, lo puedes hacer de muchas formas. Esto me confunde, ya que hay distintos ejemplos en internet.

Mi forma preferida es configurar un servlet en el archivo web.xml y configurar los servicios en un archivo beans.xml. Un ejemplo básico de estos archivos sería como el siguiente:

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<init-param>
<param-name>config-location</param-name>
<param-value>/WEB-INF/beans.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>

beans.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml" />

<jaxws:endpoint id="HelloSOAP" address="/ws/helloService"
implementor="com.acme.ws.soap.HelloServiceImpl" />

<jaxrs:server id="rest-api" address="/rest">
<jaxrs:serviceBeans>
<bean id="HelloREST" class="com.acme.ws.rest.HelloREST" />
</jaxrs:serviceBeans>
</jaxrs:server>

</beans>

Las dependencias necesarias en Maven son las siguientes:


<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>

La dependencia a la librería spring-web es necesaria desde la versión 2.6 de Apache CXF. Las dependencias de SLF4J son opcionales, pero muy recomendables para ver la información del Log. Si quieres saber como configurar SLF4J puedes verlo aquí.

Fuentes:
Apache CXF — Servlet Transport.
http://stackoverflow.com/questions/6349424/apache-cxf-rs-extensions-issue-in-2-4-0

En Apache CXF JAX-RS existen los ParameterBeans que nos permiten crear una instancia de un objeto a partir de los parámetros enviados al servicio.

@POST
public String create(@FormParam("")Empleado empleado){
 try {
 DAO dao=new DAO();
 dao.ofy().put(empleado);
 Map<String, Object> response = new HashMap<String, Object>();
 Map<String, Object> body = new HashMap<String, Object>();
 body.put("status", 0);
 body.put("data", empleado);
 response.put("response", body);
 return json.writeValueAsString(response);
 } catch (Exception e) {
 log.error("", e);
 }
 return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al guardar el Empleado\"}}";
}

En el ejemplo anterior se contruye un objeto Empleado a partir de los parámetros @FormParam enviados al servidor.

Los parámetros pueden ser de tipo String o cualquier tipo que tenga un constructor que reciba como parámetro una cadena o un método estático valueOf(String s). Adicionalmente CXF JAXRS puede utilizar el método fromString(String s) si existiera.


public class Empleado {

 Long id;
 String nombre;
 Date fechaNacimiento;

 public Empleado() {
 }

//getters y setters
}

Al utilizar la clase Empleado cómo parámetro, me arrojaba el siguiente error
ERROR [org.apache.cxf.jaxrs.utils.InjectionUtils] – Class java.util.Date can not be instantiated using a constructor with a single String argument
El mensaje del error es muy explícito. Pero entonces, ¿cómo le indicamos a Apache CXF que genere un objeto de tipo Date a partir de una cadena?
Bueno, pues tenemos que utilizar un ParameterHandler como el siguiente


/**
 * Converts ISO8601 parameters into date objects.
 *
 */
@Provider
public class ISO8601DateParamHandler implements ParameterHandler<Date> {

/**
 * Coerce an ISO8601 time string to a date object.
 *
 * @param s the string to parse.
 * @return the date object or null if the string cannot be parsed.
 */
 @Override
 public Date fromString(String s) {
 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
 try {
 return new Date(dateFormat.parse(s).getTime());
 } catch (ParseException e) {
 return null;
 }
 }
}

y configurarlo en nuestro archivo beans.xml

<jaxrs:server id="rest-api" address="/rest/gwt-audiencias">
 <jaxrs:providers>
 <bean class="com.mycompany.server.api.rest.ISO8601DateParamHandler" />
 </jaxrs:providers>
 <jaxrs:serviceBeans>
 <bean id="empleados"
 class="com.mycompany.server.api.rest.EmpleadosResource"></bean>
 </jaxrs:serviceBeans>
</jaxrs:server>

Fuentes:
http://cxf.547215.n5.nabble.com/ParameterHandler-not-invoked-for-Date-parameter-td2267734.html
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Parameterbeans
http://cxf.apache.org/docs/jaxrs-services-configuration.html

Para configurar Apache CXF en mis proyectos, me bastaba con agregar la siguiente dependencia en el archivo pom.xml

<dependency>
 <groupId>org.apache.cxf</groupId>
 <artifactId>cxf-rt-frontend-jaxrs</artifactId>
 <version>2.6.2</version>
</dependency>

agregar la configuración en el archivo web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 version="2.5">

<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>WEB-INF/beans.xml</param-value>
 </context-param>

<listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

<servlet>
 <servlet-name>CXFServlet</servlet-name>
 <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
 </servlet>

<servlet-mapping>
 <servlet-name>CXFServlet</servlet-name>
 <url-pattern>/api/*</url-pattern>
 </servlet-mapping>
</web-app>

y configurar los servicios en el archivo beans.xml

<?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:jaxrs="http://cxf.apache.org/jaxrs"
 xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml" />
 <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<jaxrs:server id="arp-rest" address="/rest/arp">
 <jaxrs:serviceBeans>
 <ref bean="arp-rest-empleados" />
 </jaxrs:serviceBeans>
 </jaxrs:server>

<bean id="arp-rest-empleados" class="mx.com.apestudio.gwt.server.api.rest.EmpleadosResource" />
</beans>

Pero la ultima vez que quise hacer esto, me arrojaba las siguientes excepciones:

[WARN] Could not instantiate listener org.springframework.web.context.ContextLoaderListener
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener

Caused by: java.lang.ClassNotFoundException: org.springframework.context.ApplicationListener
El problema es que para las versiones 2.6.x las dependencias de spring fueron marcadas como opcionales. Y estas son utilizadas por el Servlet CXFServlet. Para corregir el problema basta con agregar la dependencia a spring-web en nuestro archivo pom.xml

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 <version>3.1.1.RELEASE</version>
</dependency>

Si no quieres o no puedes utilizar spring, tendrías que utilizar el servlet CXFNonSpringServlet y configurar los servicios programáticamente.

public class MyServiceServlet extends CXFNonSpringServlet {

private static final long serialVersionUID = 1L;

@Override
 // Called at startup time to register this web service.
 public void loadBus(ServletConfig servletConfig) {
 super.loadBus(servletConfig);

Bus bus = getBus();
 BusFactory.setDefaultBus(bus);

createFactoryBean();
 }

private void createFactoryBean() {
 JaxWsServerFactoryBean fb = new JaxWsServerFactoryBean();
 fb.setWsdlLocation("myservice.wsdl");
 fb.setAddress("/");
 fb.setServiceBean(new MyServicePortImpl());
 fb.setServiceClass(MyServicePort.class);
 fb.setServiceName(new QName("http://www.example.com/MyService", "MyService"));
 fb.create();
 }
}

Fuentes:
http://cxf.apache.org/docs/26-migration-guide.html
http://cxf.apache.org/docs/jaxrs-services-configuration.html
http://cxf.apache.org/docs/jax-rs.html#JAX-RS-Projectsetupandconfiguration
http://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html
http://www.mkyong.com/spring/spring-error-classnotfoundexception-org-springframework-web-context-contextloaderlistener/
http://www.axlrosen.net/stuff/cxf-without-spring.html

Hace poco escribí sobre como buscar/filtrar recursos en el servidor, para ello utilizamos un objeto Criteria -del mismo tipo que el recurso- para crear una consulta JDOQL que nos permitiera obtener los resultados.

En este artículo mostraré una forma de utilizar la clase AdvancedCriteria de SmartGwt para realizar una consulta más compleja. Al igual que la vez anterior, vamos a utilizar el framework Apache CXF para nuestros servicios REST, y Jackson para la serialización y deserialización de los objetos en formato JSON. También utilizamos Datanucleus para la persistencia de datos.

Me base en este artículo que explica cómo integrar SmartGwt con ASP.Net.

Lo primero que vamos a necesitar es una clase AdvancedCriteria del lado del servidor


public class AdvancedCriteria {

static ObjectMapper om;

{

om=new ObjectMapper();

om.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

}

public static class JDOQLData{

public String filter="";

public Map<String,Object> params=new HashMap<String, Object>();

public Class<?> type;

&nbsp;

public JDOQLData(Class<?> type) {

this.type=type;

}

}

&nbsp;

String _constructor;

Integer _startRow;

Integer _endRow;

String fieldName;

String operator;

Object value;

AdvancedCriteria[] criteria;

&nbsp;

public AdvancedCriteria() {

}

&nbsp;

public static AdvancedCriteria fromString(String value){

try {

return om.readValue(value, AdvancedCriteria.class);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

&nbsp;

// Getter's y Setter's ...

&nbsp;

public void toJDOQL(JDOQLData data){

if(this.criteria==null){

if(this.operator.equals(OperatorId.EQUALS.getValue())){

data.filter+=this.fieldName+" == ";

}else if(this.operator.equals(OperatorId.NOT_EQUAL.getValue())){

data.filter+=this.fieldName+" != ";

}else if(this.operator.equals(OperatorId.LESS_THAN.getValue())){

data.filter+=this.fieldName+" < ";

}else if(this.operator.equals(OperatorId.GREATER_THAN.getValue())){

data.filter+=this.fieldName+" > ";

}else if(this.operator.equals(OperatorId.LESS_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" <= ";

}else if(this.operator.equals(OperatorId.GREATER_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" >= ";

}

data.params.put(String.valueOf(data.params.size()+1), fixType(getPropertyType(data.type, this.fieldName), this.value) );

data.filter+=":"+data.params.size();

}else{

data.filter+="(";

for(int index=0;index<this.criteria.length;index++){

this.criteria[index].toJDOQL(data);

if(index+1<this.criteria.length){

if(this.operator.equals(OperatorId.AND.getValue())){

data.filter+=" && ";

}else if(this.operator.equals(OperatorId.OR.getValue())){

data.filter+=" || ";

}else if(this.operator.equals(OperatorId.NOT.getValue())){

data.filter+=" ! ";

}

}

}

data.filter+=")";

}

}

&nbsp;

private Object fixType(Class<?> type, Object value) {

return om.convertValue(value, type);

}

&nbsp;

private Class<?> getPropertyType(Class<?> clazz,String property){

try{

LinkedList<String> properties=new LinkedList<String>();

properties.addAll(Arrays.asList(property.split("\\.")));

Field field = null;

while(!properties.isEmpty()){

field = FieldUtils.getField(clazz,properties.removeFirst(),true);

clazz=field.getType();

}

return field.getType();

}catch(Exception e){

throw new RuntimeException(e);

}

}

}

Lo interesante de esta clase es que tiene un método estático fromString(String) que será utilizado por Apache CXF para crear un objeto de esta clase a partir de una cadena. También tiene un método recursivo toJDOQL(JDOQLData) que nos regresa un filtro y el listado de parámetros en un objeto JDOQLData para poder realizar la consulta en datanucleus. El método fixType y getPropertyType son para utilizar el tipo correcto -de acuerdo a la clase- en nuestro listado de parámetros.

Nuestro servicio REST quedaría de la siguiente forma


@GET

public String retrieve(@QueryParam("")Libro criteria,@QueryParam("")AdvancedCriteria advancedCriteria){

try {

List<Libro> libros = null;

if(advancedCriteria.get_constructor()!=null){

libros = Libro.getByAdvancedCriteria(advancedCriteria);

}else{

libros = Libro.getByCriteria(criteria);

}

Map<String, Object> response = new HashMap<String, Object>();

Map<String, Object> body = new HashMap<String, Object>();

body.put("status", 0);

body.put("data", libros);

response.put("response", body);

return json.writeValueAsString(response);

} catch (Exception e) {

log.error("", e);

}

return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al buscar los Libros\"}}";

}

El framework Apache CXF se encarga de crear los objetos criteria(Libro) y advancedCriteria. Para saber cuál fue el que recibimos basta con verificar la propiedad _constructor, si es diferente de null sabemos que hemos recibido un AdvancedCriteria.

Finalmente realizamos la consulta en nuestra clase Libro


public static List<Libro> getByAdvancedCriteria(AdvancedCriteria criteria) {

List<Libro> libros;

PersistenceManager pm = PMF.get().getPersistenceManager();

Transaction tx = pm.currentTransaction();

try{

tx.begin();

JDOQLData data=new JDOQLData(Libro.class);

criteria.toJDOQL(data);

Query query = pm.newQuery(Libro.class,data.filter);

libros=(List<Libro>) query.executeWithMap(data.params);

tx.commit();

}finally{

if(tx.isActive()){

tx.rollback();

}

pm.close();

}

return libros;

}

En el cliente, utilizamos la clase AdvancedCriteria de SmartGwt para realizar la consulta


&nbsp;

AdvancedCriteria criteria=new AdvancedCriteria(OperatorId.OR,

new AdvancedCriteria[]{

new AdvancedCriteria("isbn",OperatorId.EQUALS, txtISBN.getValueAsString()),

new AdvancedCriteria(OperatorId.AND, new AdvancedCriteria[]{

new AdvancedCriteria("autor.nombre", OperatorId.EQUALS, "Gabriel García Márquez"),

new AdvancedCriteria("titulo", OperatorId.EQUALS, "Crónica de una muerte anunciada")

})

});

librosListGrid.fetchData(criteria);

&nbsp;

Finalmente al realizar el ‘fetch’ se realiza una petición a nuestro servicio con los siguientes párametros. Los cuales serán transformados a un objeto de nuestra clase AdvancedCriteria


&nbsp;

__gwt_ObjectId:793

operator:or

criteria:{"__gwt_ObjectId":788,"fieldName":"isbn","operator":"equals","value":"978-1400034956"}

criteria:{"__gwt_ObjectId":791,"operator":"or","criteria":[{"__gwt_ObjectId":789,"fieldName":"autor.nombre","operator":"equals","value":"Gabriel García Márquez"},{"__gwt_ObjectId":790,"fieldName":"titulo","operator":"equals","value":"Crónica de una muerte anunciada"}]}

_constructor:AdvancedCriteria

_operationType:fetch

_startRow:0

_endRow:75

_textMatchStyle:exact

_componentId:isc_ListGrid_0

_dataSource:isc_LibrosModule_3_0

isc_metaDataPrefix:_

isc_dataFormat:json

&nbsp;

Fuentes:
http://wiki.smartclient.com/display/Main/Integrating+with+ASP.Net+MVC
http://wiki.smartclient.com/display/Main/5.+Adding+support+for+AdvancedCriteria
http://www.objectdb.com/database/jdo/manual/chapter7#Query_Parameters
http://www.objectdb.com//database/jdo/manual/chapter7
http://stackoverflow.com/questions/935762/how-to-dynamically-build-jdo-queries-on-multiple-parameters

Cuando diseñas la URL de un recurso, no queda muy claro cómo vas a realizar una búsqueda o aplicar un filtro. Por ejemplo, si tuvieramos el recurso “Productos” tendríamos el siguiente esquema para las URL’s

GET   http://localhost:8080/rest/productos   Regresa un listado de productos en formato JSON

Pero resultaría muy inefeiciente cargar TODO el listado de productos en una página. Resulta más conveniente aplicar un filtro al listado y regresar solamente un subconjunto de productos al cliente, por ejemplo, de acuerdo al tipo de producto. Para ello la forma más recomendable sería pasando un listado de parámetros en la URL de la siguiente forma

http://localhost:8080/rest/productos?tipo=OFICINA&marca=SCRIBE

Si solamente quieres realizar la paginación del listado de productos, puedes agregar párametros como startRow=0 y endRow=75 ó start=0 y limit=75 o utilizar un encabezado personalizado cómo “X-Range: 0-75”

A continuación mostrare la implementación de una búsqueda simple en un servicio REST. Para ello utilicé SmartGwt para la interfaz de usuario, Apache CXF para los servicios REST, Jackson para la serialización y Datanucleus (JDO) para la persistencia de los datos.

Primero creamos la interfaz que tendrá como fuente de datos un servicio REST

ListGrid grdProductos=new ListGrid();
grdProductos.setDataSource(new RestDataSource(){
{
setDataURL("/rest/productos");
setDataFormat(DSDataFormat.JSON);

DataSourceField fldId=new DataSourceField("id", FieldType.TEXT);
fldId.setPrimaryKey(true);
fldId.setHidden(true);
DataSourceField fldNombre=new DataSourceField("nombre", FieldType.TEXT, "Producto");
DataSourceField fldTipo=new DataSourceField("tipo", FieldType.TEXT, "Tipo");
DataSourceField fldMarca=new DataSourceField("marca", FieldType.TEXT, "Marca");
setFields(fldId,fldNombre,fldTipo,fldMarca);
}
});
Criteria criteria=new Criteria();
criteria.addCriteria("tipo","OFICINA");
criteria.addCriteria("marca", "SCRIBE");
grdProductos.setInitialCriteria(criteria);
grdProductos.setAutoFetchData(true);

Simplemente creamos un Grid para desplegar los productos y establecemos un filtro inicial (para mantener breve el ejemplo, en realidad, estos parámetros los seleccionaría el usuario).

Cuando se cargué el Grid automáticamente (autoFecthData=true) se hara un Request a la URL de nuestro servicio REST parecida a la siguiente

http://localhost:8080/rest/productos?__gwt_ObjectId=170&amp;tipo=OFICINA&marca=SCRIBE&_operationType=fetch&_startRow=0&_endRow=75&_textMatchStyle=exact&_componentId=isc_ListGrid_0&_dataSource=isc_ProductosModule_2_0&isc_metaDataPrefix=_&isc_dataFormat=json

Cómo podemos ver el Grid automáticamente envía los parámetros del filtro en la URL -incluídos los de paginación-.

Ahora vamos a ver como implementamos el servicio REST con la ayuda de Apache CXF y Jackson

@Path("productos")
public class ProductosResource {
{
json=new ObjectMapper();
log=LoggerFactory.getLogger(ProductosResource.class);
}

ObjectMapper json;
Logger log;

@GET
public String retrieve(@QueryParam("")Producto criteria){
try {
List<Producto> Productos = Producto.getByCriteria(criteria);
Map<String, Object> response = new HashMap<String, Object>();
Map<String, Object> body = new HashMap<String, Object>();
body.put("status", 0);
body.put("data", Productos);
response.put("response", body);
return json.writeValueAsString(response);
} catch (Exception e) {
log.error("", e);
}
return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al obtener los Productos\"}}";
}
}

CXF construye automáticamente una instancia de un Producto en base a los parámetros, esta instancia la utilizaremos para filtrar el listado al comparar sus propiedades con los registros en la base de datos.

Finalmente tenemos que implementar nuestra clase Producto de la siguiente manera

@PersistenceCapable(detachable="true")
public class Producto {

@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.UUIDHEX)
String id;
String nombre;
String tipo;
String marca;

public Producto() {
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getNombre() {
return nombre;
}

public void setNombre(String nombre) {
this.nombre = nombre;
}

public String getTipo() {
return tipo;
}

public void setTipo(String tipo) {
this.tipo = tipo;
}

public String getMarca() {
return marca;
}

public void setMarca(String marca) {
this.marca = marca;
}

public static List<Producto> getByCriteria(Producto criteria) {
List<Producto> Productos;
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try{
tx.begin();
StringBuffer filter=new StringBuffer();
List<Object> parameters=new ArrayList<Object>();
if(criteria.getId()!=null){
filter.append("id == :id");
parameters.add(criteria.getId());
}
if(criteria.getNombre()!=null){
if(filter.length()>0)filter.append(" && ");
filter.append("nombre == :nombre");
parameters.add(criteria.getNombre());
}
if(criteria.getMarca()!=null){
if(filter.length()>0)filter.append(" && ");
filter.append("marca == :marca");
parameters.add(criteria.getMarca());
}
Query query = pm.newQuery(Producto.class,filter.toString());
Productos=(List<Producto>) query.executeWithArray(parameters.toArray());
tx.commit();
}finally{
if(tx.isActive()){
tx.rollback();
}
pm.close();
}
return Productos;
}

}

Lo interesante es que creamos un filtro dinámico de acuerdo a los valores de los campos en el objeto Criteria.

Conclusión:

Aquí mostré como podemos implementar una búsqueda o filtro básico en nuestros servicios REST. Rara vez vamos a requerir solamente esto, usualmente se presentan casos en los que el filtro o la búsqueda es mucho más complejo. Si utilizas SmartGWT para el código del cliente, existe la clase AdvancedCriteria, que nos permite realizar filtros complejos. El detalle es mapear esta clase a una que podamos utilizar en el lado del servidor y hacer la búsqueda correspondiente mediante JDOQL. Pero esa….. esa es otra historia……

Fuentes:
http://stackoverflow.com/questions/5020704/how-to-design-restful-search-filtering
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Parameterbeans
https://developers.google.com/appengine/docs/java/datastore/jdo/relationships?hl=es
http://stackoverflow.com/questions/935762/how-to-dynamically-build-jdo-queries-on-multiple-parameters
http://db.apache.org/jdo/jdoql.html
http://www.elasticsearch.org/guide/reference/query-dsl/
http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/data/AdvancedCriteria.html
http://cxf.apache.org/docs/jax-rs-advanced-features.html#JAX-RSAdvancedFeatures-FIQLsearchqueries