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;
public JDOQLData(Class<?> type) {
this.type=type;
}
}
String _constructor;
Integer _startRow;
Integer _endRow;
String fieldName;
String operator;
Object value;
AdvancedCriteria[] criteria;
public AdvancedCriteria() {
}
public static AdvancedCriteria fromString(String value){
try {
return om.readValue(value, AdvancedCriteria.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Getter's y Setter's ...
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+=")";
}
}
private Object fixType(Class<?> type, Object value) {
return om.convertValue(value, type);
}
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
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);
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
__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
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