angular – passing a complex (object or array) parameter to a popup dialog

In a jhipster generated app We created a component to confirm an action that is to be done on a batch of selected documents.

You can pass parameters as path params or query params, but most examples only tell you how to pass an id and then resolve it to an object. We already had an array of objects which we want to pass as parameters.
I suppose you could stringify them and pass them as one of the options above, but that would increase the length of the URL; with the advantage it can be shared and state would be restored. Nonetheless we decided to opt for the fourth option described in this answer to the stackoverflow question ‘Send data through routing paths in Angular’.

First we created our component to select the documents that are going to be processed, when the button is pressed we should confirm the action.
We used a PrimeNG table to allow the user to select the documents which we store in our ‘documentosSeleccionados’ variable.
The routerLink specifies it should navigate to a popup dialog, the trick here is we also specified a click action where we tell it to update the value on a Service property -as explained in the answer above-.

lote-firmar.component.html

        <button class="btn btn-primary float-right" (click)="documentoService.documentosSeleccionados = documentosSeleccionados"
                [routerLink]="['/', { outlets: { popup: 'lote/'+ lote.id + '/firmar/confirma'} }]"
                replaceUrl="true"
                queryParamsHandling="merge">
            <fa-icon [icon]="'file-signature'"></fa-icon>
            <span>
            Firmar
            </span>
        </button>
    <br/>
    <p-table #dt [value]="documentos" dataKey="id" selectionMode="multiple" [(selection)]="documentosSeleccionados">
        <ng-template pTemplate="header">
            <tr>
                <th style="width: 3em">
                    <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
                </th>
                <th>Archivo</th>
                <th>
                    <p-dropdown [options]="estatusDocumentos" [style]="{'width':'100%'}" (onChange)="dt.filter($event.value, 'estatus', 'equals')"></p-dropdown>
                </th>
            </tr>
        </ng-template>
        <ng-template pTemplate="body" let-documento>
            <tr>
                <td>
                    <p-tableCheckbox [value]="documento"></p-tableCheckbox>
                </td>
                <td>{{documento.archivo}}</td>
                <td>{{documento.estatus}}</td>
            </tr>
        </ng-template>
    </p-table>

lote-firmar.component.ts

@Component({
    selector: 'jhi-lote-firmar',
    templateUrl: './lote-firmar.component.html'
})
export class LoteFirmarComponent implements OnInit {
    lote: ILote;
    documentos: IDocumento[];
    documentosSeleccionados: IDocumento[];
    estatusDocumentos: SelectItem[];

    ... we inject the Service
    constructor(private activatedRoute: ActivatedRoute, private documentoService: DocumentoService) {}

The service is pretty straightforward, we just need to add the property where we’d like to pass to the popup dialog. We could even specified the type, but we didn’t in this case. Remember services are singletons, so we have just one instance of this class.

documento.service

@Injectable({ providedIn: 'root' })
export class DocumentoService {
    ... other properties
    public documentosSeleccionados;

    constructor(private http: HttpClient) {}

    ... we also have the method to send the objects to process to the server
    firmar(documentos: IDocumento[], keyPassword: string): Observable<EntityArrayResponseType> {
        const options = createRequestOption({ keyPassword });
        return this.http.post<IDocumento[]>(this.resourceUrl + '/firmar', documentos, {
            params: options,
            observe: 'response'
        });
    }
}

Finally, the popup dialog

lote-firmar-dialog.component.html

<form name="loteFirmarForm" autocomplete="off" (ngSubmit)="firmarDocumentos()">
    <div class="modal-header">
        <h4 class="modal-title">Se requiere contraseña</h4>
        ×
    </div>
    <div class="modal-body">

        <p id="jhi-delete-documento-heading">Para firmar los documentos seleccionados, ingresa la contraseña de tu archivo .key</p>
        <div class="col-md-8">
            <div class="form-group">

            </div>
        </div>
    </div>
    <div class="modal-footer">

             <span>Cancelar</span>


             <span>Firmar</span>

    </div>
</form>

since we inject the Documentos service already in this component, we can access the value passed as parameter trough the service and initialize our model in the NgOnInit method.

lote-firmar-dialog.component.ts

@Component({
    selector: 'jhi-lote-firmar-dialog',
    templateUrl: './lote-firmar-dialog.component.html'
})
export class LoteFirmarDialogComponent implements OnInit {
    ... other properties
    documentosSeleccionados: IDocumento[];

    ... this is where we inject the service
    constructor(private documentoService: DocumentoService, public activeModal: NgbActiveModal, private eventManager: JhiEventManager) {}

    ... this is where we read the array passed through the service
    ngOnInit() {
        this.documentosSeleccionados = this.documentoService.documentosSeleccionados;
    }

    ... then we can send this data to the server to be processed in this service
    firmarDocumentos() {
        this.documentoService.firmar(this.documentosSeleccionados, this.keyfilePassword).subscribe(
            response => {
                console.log('se enviaron los documentos a firmar, response:', response);
                this.eventManager.broadcast({
                    name: 'documentosListModification',
                    content: 'Los documentos se firmaron correctamente'
                });
                this.activeModal.dismiss(true);
            },
            error => {
                console.log('ocurrio un error al enviar los documentos a firmar', error);
                this.eventManager.broadcast({
                    name: 'documentosListModification',
                    content: 'Ocurrio un problema al firmar los documentos [' + error + ']'
                });
            }
        );
    }
}

done!

Anuncios

Spring – Utilizar una expresión regular para especificar la ruta del controlador

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

AngularJS – Invocar un servicio REST y adaptar la respuesta a nuestro modelo

Desarrollé un servicio web que regresa una respuesta al estilo de SmartGWT

{
"response": {
"status": 0,
"startRow": 0,
"endRow": 76,
"totalRows": 546,
"data": [
{"field1": "value", "field2": "value"},
{"field1": "value", "field2": "value"},
… 76 total records …
]
}
}

Queremos utilizar estos datos en nuestra plantilla

distritos.html

...
<label class="item item-input item-select">
                <div class="input-label">
                    Distrito
                </div>
                <select>
                    <option ng-repeat="distrito in distritos">
                        {{ distrito.descripcion }}
                    </option>
                </select>
            </label>
...

Hay dos formas para poder consumir esta respuesta en nuestra plantilla -a través de un servicio de AngularJS-

Una es utilizar una función transformResponse en la configuración del servicio de la siguiente manera

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: true, transformResponse: function(data){
                return angular.fromJson(data).response.data
            }}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        $scope.distritos = Distritos.query()

    }])

y la segunda forma es utilizar la respuesta tal y como viene del servidor

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: false}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        Distritos.query().$promise.then(function(data){
            $scope.distritos = data.response.data
        })
    }])

Fuentes:

Spring – Permitir peticiones de otros dominios (CORS)

Al invocar un servicio web (REST) desde una aplicación móvil (ioniccordova/phonegap), me aparecía el siguiente error:

XMLHttpRequest cannot load http://localhost:8080/equinox/catalogos/distritos. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin http://localhost:8100 is therefore not allowed access.

El servidor se está ejecutando mediante mvn jetty:run (localhost:8080) mientras que el cliente está ejecutandose mediante grunt serve (localhost:8100). Siendo dominios diferentes, el navegador bloquea la invocación del servicio por cuestiones de seguridad.

La solución es permitir el acceso CORS. El servicio web esta hecho con Spring, así que para permitir esto necesitamos crear y configurar un filtro.

SimpleCORSFilter.java

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

y para configurarlo

web.xml

...
<filter>
        <filter-name>SimpleCORSFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>simpleCorsFilter</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>SimpleCORSFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
...

applicationContext.xml

...
<bean class="mx.gob.tribunalqro.equinox.filters.SimpleCORSFilter" name="simpleCorsFilter"></bean>
...

Nota:
Esta es una implementación muy sencilla y que además agrega el encabezado a todas las peticiones, no sólo a los servicios web. Para ello, necesitamos colocar nuestros servicios en una url como api/v1/resource y mapear el filtro a esas url’s

Fuentes:

Java – Convertir una página específica de un TIFF multipágina a JPG

Para la digitalización de documentos utilizamos escáneres que producen archivos TIFF, estos permiten almacenar varias imágenes en el mismo archivo. Estas imágenes se guardan en una base de datos para su posterior consulta.

Para poder desplegar estas imágenes en una aplicación web, necesitamos convertirlas a un formato estándar para la web (JPG, PNG ó GIF). Esto se puede lograr utilizando la librería image magick. Para usarla en Java existen 2 opciones: JMagick e im4java. Decidí utilizar im4java debido a los riesgos de usar JNI en una aplicación web (http://im4java.sourceforge.net/docs/faq.html)

Lo primero es agregar la dependencia de im4java a nuestro proyecto, archivo pom.xml si usas maven o agregar el jar a tu build path.

   <dependency>
      <groupId>org.im4java</groupId>
      <artifactId>im4java</artifactId>
      <version>1.4.0</version>
   </dependency>

Utilizamos un arreglo de bytes para cargar la imágen desde la base de datos y lo pasamos como un stream a la librería im4java para seleccionar la página que deseamos convertir al formato jpg.

    public byte[] getPagina(Integer pagina) throws Exception{
        byte[] imgPagina = null;
        byte[] imagen = loadImageFromDatabase();
        try{
        IMOperation op = new IMOperation();
        op.addImage(String.format("-[%d]", pagina - 1)); // read from stdin and specify page number with 1-based index
        op.addImage("jpg:-");               // write to stdout in jpg-format
        ByteArrayInputStream fis = new ByteArrayInputStream(imagen);
        ByteArrayOutputStream fos = new ByteArrayOutputStream();
        // Pipe pipe = new Pipe(fis,fos);
        Pipe pipeIn  = new Pipe(fis,null);
        Pipe pipeOut = new Pipe(null,fos);
        // set up command
        ConvertCmd convert = new ConvertCmd();
        convert.setInputProvider(pipeIn);
        convert.setOutputConsumer(pipeOut);
        convert.run(op);
        imgPagina = fos.toByteArray();
        }catch(Exception e){
            throw new Exception(
                    "Error al obtener la imágen de la página " + pagina, e);
        }
        return imgPagina;
    }

este método lo podemos utilizar en un servicio web/servlet para servir la imágen a través de una url

    @GET
    @Path("documento/{id}/imagen/pagina/{numero}")
    @Produces("image/jpeg")
    public Response getPaginaPromocion(@PathParam("id")Integer id, @PathParam("numero")Integer numero){
        Response response = null;
        try{
            Documento doc = Documento.get(id);
            response = Response.ok(doc.getPagina(numero), "image/jpeg").build();
        }catch(Exception e){
            String error = String.format("Error al obtener la página %d del documento %s", numero, id);
            log.error(error, e);
            response = Response.serverError().entity(String.format("{\"response\": {\"status\": -1, \"data\": \"%s\"}}", error)).build();
        }
        return response;
    }

Fuentes:

http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=16615#p61020
http://stackoverflow.com/questions/4809314/imagemagick-is-converting-only-the-first-page-of-the-pdf
http://im4java.sourceforge.net/docs/dev-guide.html#piping
http://stackoverflow.com/questions/2091454/byte-to-inputstream-or-outputstream
http://im4java.sourceforge.net/api/

Jackson JSON – Deserializar un array en una petición REST

En un servicio REST (jax-rs) puedes enviar los parámetros de una petición de varias formas: en la URL (@QueryParam), en el cuerpo del mensaje con el encabezado “application/x-www-form-urlencoded” (@FormParam), como parte de la URL (@PathParam).

Pero que pasa si el cliente envía toda la información en el cuerpo del mensaje en formato JSON.
Por ejemplo:

    {
        "tipo":"URGENTE",
        "actores":[
        {
            "nombre":"Juan",
            "apellidos":"Peréz",
            "idEstado":null,
            "idMunicipio":null,
            "idColonia":null,
            "calle":"Calle 2",
            "numero":"155",
            "codigoPostal":"76800"
        }
        ],
        "demandados":[
        {
            "nombre":"María",
            "apellidos":"López",
            "idEstado":null,
            "idMunicipio":null,
            "idColonia":null,
            "calle":"Calle 4",
            "numero":"45",
            "codigoPostal":"76800"
        }
        ]
    }

Podríamos crear una clase que reflejará esta estructura, pero si solo nos interesa algún campo o la petición no tiene nada que ver con nuestro modelo, no parece la mejor opción.

En este caso, en nuestro modelo existiría una clase Actor y Demandado que comparten los mismos campos, por lo que podríamos usar la herencia.

    public class Parte {
        String id;
        String nombre;
        String apellidos;
        String idEstado;
        String idMunicipio;
        String idColonia;
        String calle;
        String numero;
        String codigoPostal;
        public Parte() {
        }
        //... getter's y setter's, métodos, etc.
    }
    public class Actor extends Parte{
        //... otras propiedades
        public Actor() {
        }
        //... otros métodos
    }
    public class Demandado extends Parte{
        //... otras propiedades
        public Actor() {
        }
        //... otros métodos
    }

en nuestro servicio REST (Apache CXF) podríamos deserializar el arreglo de Actores de la siguiente forma

    @Path("solicitar/")
    public String solicitar(String data){
        String response = null;
        try{
            ObjectMapper mapper=new ObjectMapper();
            mapper.configure(SerializationConfig.Feature.WRITE<em>DATES</em>AS<em>TIMESTAMPS, false);
            mapper.configure(Feature.FAIL</em>ON<em>UNKNOWN</em>PROPERTIES, false);
            Map<String, Object> map = mapper.readValue(data, new TypeReference<Map<String, Object>>(){});
            String tipo = (String) map.get("tipo");
            List
<Parte> actores = mapper.convertValue(map.get("actores"), new TypeReference
<List<Parte>>() {});
            //... registrar la solicitud
            response = mapper.writeValueAsString(actores);
        }catch(Exception e){
            String error = "Ocurrió un problema al hacer la solicitud";
            log.error(error, e);
            response = String.format("{\"response\":{\"status\": -1, \"data\": \"%s\"}}", error);
        }
        return response;
    }

Fuentes:
http://wiki.fasterxml.com/JacksonDataBinding
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-DealingwithParameters

Apache CXF – Ejemplo de firma de un documento XML

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.