Supongamos que tenemos un servicio web para obtener un listado de facturas de una fecha específica en la siguiente url

api.server.com/facturas/2014-11-24

nuestro controlador sería

    @RequestMapping(value = "/facturas/{fecha}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable Date fecha){
        List<Factura> facturas = service.searchFacturas(fecha);
        return facturas;
    }

Ya vimos como convertir parámetros de tipo fecha

Pero que pasa si necesitamos crear otro servicio para obtener las facturas de un cliente con la siguiente URL

api.server.com/facturas/CQRO01

si implementamos el servicio tal como el anterior tendríamos un conflicto con la URL ya que no le hemos indicado de alguna forma como distinguir entre un id de cliente y una fecha. Para distinguir podemos utilizar expresiones regulares

    @RequestMapping(value = "/facturas/{fecha:d{4}-d{2}-d{2}}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable Date fecha){
        List<Factura> facturas = service.searchFacturas(fecha);
        return facturas;
    }

    @RequestMapping(value = "/facturas/{cliente:w{6}}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable String cliente){
        List<Factura> facturas = service.searchFacturas(cliente);
        return facturas;
    }

Fuentes:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates-regex
http://stackoverflow.com/questions/17889604/how-to-use-a-regex-path-mapper-variable-in-a-requestmapping
http://stackoverflow.com/questions/18422368/regex-in-spring-controller

Anuncios

A diferencia de AngularJS, ionic utiliza el módulo ui-router en vez de ngRoute para el mecanismo de enrutamiento de las páginas.
En nuestra configuración tendremos estados en vez de rutas. Por ejemplo,

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

se convertiría en

    phonecatApp.config(function ($stateProvider, $urlRouterProvider) {
        $stateProvider

            .state('phones', {
                url: '/phones',
                templateUrl: 'partials/phone-list.html',
                controller: 'PhoneListCtrl'
            })

            .state('phones.detail', {
                url: '/:phoneId',
                templateUrl: 'partials/phone-detail.html',
                controller: 'PhoneDetailCtrl'
            })

        $urlRouterProvider.otherwise('/phones')
    })

Y para navegar a otra página, supongamos que tenemos un botón en nuestra plantilla

<button class="button button-block button-positive" ng-click="goTo(phoneId)">
                Detail
</button>

nuestro controlador quedaría

    .controller('PhoneListCtrl', ['$scope', '$state', function($scope, $state){
        $scope.goTo = function(phoneId){
            $state.go('phones.detail', {phoneId: phoneId})
        }
    }])

notesé el paso del parámetro ‘phoneId’

Fuentes:

Lo más común es desarrolllar nuestras aplicaciones en un ambiente de desarrollo, es decir, utilizar servidores de prueba para después implementarlo en un ambiente de produción. Si el código de tu proyecto se encuentra en un sistema de control de código (git, svn, etc.) es díficil, si no tedioso, cambiar entre un ambiente y otro. Tienes que modificar los archivos de configuración para que se conecte a la base de datos de producción cada vez que actualizas el sistema y debes tener cuidado de no incluir en el control de código las conexiones de prueba o visceversa.

Para resolver este problema podemos utilizar JNDI, pero se vuelve complicado el mantener un entorno replicado en nuestra máquina de desarrollador. Eixsten plataformas para desarrollar y ejecutar aplicaciones web de forma escalable -tales como- Heroku, Google App Engine o Microsoft Azure. Para resolver este problema de contextos, Heroku ha optado por utilizar variables de ambiente para cambiar entre el contexto de desarrollo y el de producción.

Esta solución me pareció adecuada y decidí implementarla en un proyecto Java. Realmente es muy sencillo, normalmente creamos una conexión a la base de datos de la siguiente forma:

DriverManager.getConnection(dbUrl, dbUser,dbPassword);

Necesitamos 3 cosas: url a la base de datos, usuario y contraseña.
Opcionalmente podemos incluir el usuario y contraseña en la URL de la base de datos si el controlador lo permite.En el caso de Oracle es de la siguiente manera:

jdbc:oracle:thin:[USER/PASSWORD]@[HOST][:PORT]:SID
jdbc:oracle:thin:[USER/PASSWORD]@//[HOST][:PORT]/SERVICE

Para leer la configuración de las variables de ambiente sería

DriverManager.getConnection(System.getenv(“DATABASE_URL”));

En nuestro caso utilizamos diferentes conexiones de acuerdo al usuario y la base de datos, por lo que seguimos una nomenclatura de la siguiente manera

USUARIO_AT_DATABASE

así por ejemplo, podríamos conectarnos a distintas bases de datos

   DriverManager.getConnection(System.getenv("RH_AT_QUERETARO"));
   DriverManager.getConnection(System.getenv("RH_AT_AMEALCO"));
   DriverManager.getConnection(System.getenv("USRCIVQRO_AT_QUERETARO"));

Por último necesitas configurar estas variables de ambiente en tu sistema operativo.

En Ubuntu lo puedes hacer de diferentes formas.
A mi me funcionó creando un archivo env.sh en la carpeta /etc/profile.d

   #!/usr/bin/bash
   export RH_AT_CENTRO_DE_JUSTICIA=jdbc:oracle:thin:rh/secret@127.0.0.1:1521:dbtest
   export COMPRAS_AT_AMEALCO=jdbc:oracle:thin:compras/secret@127.0.0.1:1521:dbtest

Fuentes:
https://devcenter.heroku.com/articles/heroku-postgresql#connecting-in-java
http://stackoverflow.com/questions/5547224/username-password-in-jdbc-connection-url
https://help.ubuntu.com/community/EnvironmentVariables
http://askubuntu.com/questions/307023/command-not-working-in-profile

wget

wget

Para realizar esta tarea utilicé el comando wget. Para especificar o dar formato a la URL encontré 3 opciones: awk, sed y el mismo comando wget.

awk me pareció el más complejo por eso no lo utilicé. La opción wget -i es la más senciclla pero necesitas tener en el archivo las url’s  fijas de los archivos que quieres descargar, yo tenía una URL dinámica para cada archivo. El comando sed me pareció la opción mas viable para resolver mi problema.

La url de los archivos que quería descargar era

http://127.0.0.1:8080/ReportViewer/run?__format=xls&__report=rptInformeMensual.rptdesign&REPORTE=ID_REPORTE

la variable ID_REPORTE la tenía en un archivo CSV de la siguiente manera

35ac0194-6992-402d-aa8e-79ae9866b90a COMPRAS
66fbf25f-74ab-412c-abf9-87769582cf48 DIRECCIÓN DE PSICOLOGÍA
7b781032-e529-4d2d-b497-b47b39195688 DIRECCIÓN JURÍDICA
76612f8a-944d-4ce3-bfa6-e9a83a787480 ACTIVO FIJO
...

la primer columna es el id del reporte y la segunda la descripción.

El comando que utilicé fue el siguiente

sed 's/\(.*\)\t\(.*$\)/"http:\/\/127.0.0.1:8080\/ReportViewer\/run?__format=xls\&__report=rptInformeMensual\.rptdesign\&REPORTE=\1" -O "\2\.xls"/' reportes.txt | xargs -l1 wget

Puedes ejecutar sólo la primera parte del comando para revisar que estas pasando los parámetros correctos a wget.

sed recibe como parámetro una cadena con el formato ‘s/regex/substitution/’, regex es una expresión regular que en nuestro caso es el texto hasta el carácter de tabulación ‘\t’ y para el segundo grupo el texto restante hasta el fin de línea indicado por ‘$’. En la expresión regular y en la cadena de substitución hay que tener cuidado de escapar los carácteres especiales como la diagonal, el punto y el ampersand. Los grupos de la expresión regular los insertamos de acuerdo a su posición en la exresión regular \1, \2, \3, etc.

Fuentes:
http://askubuntu.com/questions/103623/download-files-from-a-list
http://unix.stackexchange.com/questions/41598/using-csv-line-as-command-parameters
http://unix.stackexchange.com/questions/32907/what-characters-do-i-need-to-escape-when-using-sed-in-a-sh-script

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.

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

En los reportes que creó con BIRT tengo un encabezado “estándar” que utiliza el valor de un parámetro para desplegar el título.
Hace poco me tope con un título que contenía acentos y noté que en vez de “Archivo Histórico” me ponía “Archivo HistÃ3rico”.

Para pasar un parámetro con acentos al ReportViewer, y que este lo despliegue correctamente, necesitas primero sustituir los carácteres acentuados por su entidad html correspondiente y luego codificar la url (yo utilizo la función URL.encodeQueryString() de GWT)

url.append("Titulo1=").append(URL.encodeQueryString(escapeHTML(tituloOficina)));

Para escapar el html tuve que crear mi propia función ya que los métodos StringUtil.asHTML() y SafeHtmlUtils.htmlEscape() no sustituyen los caracteres acentuados.

public static final String escapeHTML(String s) {
		StringBuffer sb = new StringBuffer();
		int n = s.length();
		for (int i = 0; i < n; i++) {
			char c = s.charAt(i);
			switch (c) {
			// á &aacute;
			case 225:
				sb.append("á");
				break;
			// Á &Aacute;
			case 193:
				sb.append("Á");
				break;
			// é &eacute;
			case 233:
				sb.append("é");
				break;
			// É &Eacute;
			case 201:
				sb.append("É");
				break;
			// í &iacute;
			case 237:
				sb.append("í");
				break;
			// Í &Iacute;
			case 205:
				sb.append("Í");
				break;
			// ó &oacute;
			case 243:
				sb.append("ó");
				break;
			// Ó &Oacute;
			case 211:
				sb.append("Ó");
				break;
			// ú &uacute;
			case 250:
				sb.append("ú");
				break;
			// Ú &Uacute;
			case 218:
				sb.append("Ú");
				break;

			default:
				sb.append(c);
				break;
			}
		}
		return sb.toString();
	}

actualización (16/10/2017)

en el ejemplo anterior se realiza la codificación y paso de parámetros mediante GWT. Recientemente tuvimos el mismo problema pero en una aplicación con javascript y angular.

Para realizar el mismo proceso en javascript utilizamos la librería he.js (que también codifica los caracteres con acentos) para codificar primero a HTML y luego, la función [encodeURIComponent], para codificar a los caracteres permitidos en la URL.

url += '&Titulo3=' + encodeURIComponent(he.encode($scope.reporte.descripcion, {decimal: true}))
windows.open(url)

notesé el parámetro {decimal: true} en la función he.encode, que es necesario para que BIRT viewer lo decodifique correctamente.

para entender porque se utiliza la función encodeURIComponent en vez de encodeURI, puedes ver este post

Fuentes:
birt-exchange forum
string-functions.com – html encode
meyerweb.com – URL decoder/encoder