Cuando tienes varios reportes, es buena idea crear una librería para reutilizar elementos en los reportes. Un ejemplo común es cuando necesitamos usar el mismo encabezado y pie de página para los reportes.

BIRT nos permite agregar algunos elementos dinámicos -tales como el número de páginas, la fecha e incluso un valor obtenido mediante código javascript en un DynamicText- El problema es que esto dependerá de cada reporte, así que ¿cómo agregamos elementos dinámicos desde una librería a nuestro reporte?

Pues habrá que crear una “master page” en la librería donde pongamos elementos “DynamicText” en el encabezado y que estos desplieguen el valor de un parámetro. Después podemos modificar el valor del parámetro en cada reporte, y así, tener un título personalizado pero manteniendo el formato.

Si además, queremos obtener el título mediante una consulta a la base de datos. Lo que tenemos que hacer es crear el DataSource a la base de datos y un DataSet con la consulta que obtiene el título, por ejemplo


SELECT DESCRIPCION
FROM OFICINAS
WHERE ID=:OFICINA

Luego, debemos poner el siguiente código en el evento “beforeFactory” de nuestro reporte


//public final static int MODE_GENERATION = 1; //This mode is for generate the report document. Typically used in Report Engine run task
//public final static int MODE_PRESENTATION = 2; //This mode is for present data that saved in report document, without further data manipulation operation. Typically used in report engine render task
//public final static int DIRECT_PRESENTATION = 3;//This mode is to execute the data query without create report document. It is used in report engine runAndRender task.
//public final static int MODE_UPDATE = 4; //This mode is used to update the existing report document with new queries. Typicially used in IV.
importPackage( Packages.org.eclipse.birt.report.model.api );
importPackage(Packages.java.lang);
importPackage(Packages.java.util);
importPackage(Packages.org.eclipse.birt.report.data.adapter.api);
importPackage(Packages.org.eclipse.birt.report.model.api);
importPackage(Packages.org.eclipse.birt.data.engine.api.querydefn);
var myconfig = reportContext.getReportRunnable().getReportEngine().getConfig();
var des = DataRequestSession.newSession(myconfig, new DataSessionContext(3));
var dsrc = reportContext.getDesignHandle().findDataSource("dsrcOficina");
var dset = reportContext.getDesignHandle().findDataSet("dsetOficina");
des.defineDataSource(des.getModelAdaptor().adaptDataSource(dsrc));
des.defineDataSet(des.getModelAdaptor().adaptDataSet(dset));
var paramBinding = new InputParameterBinding("param_1", new ScriptExpression("\""+ params['IDOFICINA'].value +"\"") );
queryDefinition = new QueryDefinition( );
queryDefinition.setDataSetName( "dsetOficina" );
queryDefinition.addInputParamBinding( paramBinding );
queryDefinition.setAutoBinding(true);
var pq = des.prepare( queryDefinition );
var qr = pq.execute( null );
var ri = qr.getResultIterator( );
if ( ri.next( ) )
{
params["Titulo"].value = ri.getString("DESCRIPCION");
}
ri.close( );
qr.close( );
des.shutdown( );

Es importante poner los nombres exactamente igual que en el diseñador.
En nuestro caso, el parámetro “IDOFICINA” es de tipo String, así que lo ingresamos de la siguiente manera


var paramBinding = new InputParameterBinding( "param_1",new ScriptExpression("\""+ params['IDOFICINA'].value +"\"") );

si fuera un Integer tendría que ser de la siguiente manera


var paramBinding = new InputParameterBinding( "param_1",new ScriptExpression(params['IDOFICINA'].value) );

El valor de ScriptExpression debe ser un bloque de javascript válido, cómo si lo ingresaramos en la ventana “ExpressionBuilder” 🙂

Fuentes:
eclipse forums
birt exchange

Anuncios

Al crear un DataSet con una consulta con parámetros, BIRT me arrojaba el error

org.eclipse.birt.data.engine.core.DataException: Failed to prepare the query execution for the data set: t_host
Cannot get the type for parameter: 1.
org.eclipse.birt.report.data.oda.jdbc.JDBCException: Cannot get parameter type.
SQL error #1:Unsupported feature

Al parecer el problema sólo se presenta con la base de datos de Oracle y ha sido resuelto a partir de la versión 4.2.1 de BIRT. Así que, si tienes una versión anterior, basta con actualizar para resolver el problema (Help->Check for Updates).

Fuentes:
eclipse forums

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

Cuando ejecutas una aplicación GWT en Eclipse -ya sea Debug as Web Application ó Run as Web Application- utiliza Jetty como servidor web.

En una aplicación necesitaba poner una serie de archivos disponibles en dicho servidor. Como los archivos ya los tenía en una carpeta, no quise copiarlos a una carpeta en el servidor web (src/main/webapp), así que creé un enlace simbólico a la carpeta. Al querer acceder a los recursos me aparecía el siguiente mensaje en el log

[WARN] Aliased resource: file:/home/cirovladimir/projects/java/videohaus/videohaus-gwt/target/videohaus-gwt-0.0.1-SNAPSHOT/storage/videos/demo.mp4==file:/home/cirovladimir/Videos/videohaus/demo.mp4
[WARN] 404 – GET /storage/videos/demo.mp4 (127.0.0.1) 1446 bytes

El problema es que, por cuestiones de seguridad, Jetty no permite acceder a los recursos mediante un alias (mayúsculas y minúsculas, nombres cortos, enlaces simbólicos y carácteres extras).

Para desactivar esta carácteristica y permitir los enlaces simbólicos, podemos agregar un parámetro a la configuración de ejecución. Selecciona el proyecto y da clic en el menú “Run->Debug Configurations..”, luego agrega el parámetro “-Dorg.mortbay.util.FileResource.checkAliases=false” en la pestaña de argumentos en la sección de argumentos de la máquina virtual.

UPDATE: Existe una opción con el comando “mount” para montar una carpeta local en otra, y con esto ya no es necesario configurar Jetty para seguir los enlaces simbólicos. El comando es

 sudo mount --bind origen nueva_carpeta
 

si quieres que el montaje permanezca después de reiniciar tu computadora, agrega la siguiente línea en el archivo /etc/fstab

 origen &nbsp; nueva_carpeta &nbsp; none   bind   0   0
 

curiosamente esta solución la encontré cuando investigaba como montar una carpeta mediante NFS.

Fuentes:
https://groups.google.com/d/msg/google-web-toolkit/kMZv6jkYitw/yYfM54duu3wJ
http://docs.codehaus.org/display/JETTY/How+to+enable+serving+aliased+files
https://help.ubuntu.com/community/SettingUpNFSHowTo

 

Normalmente cuando quieres utilizar el texto que se muestra al usuario para un parámetro, en vez del valor, utilizas la propiedad params[“parameterName”].displayText en un “dynamic text” en tu reporte. Si intentas hacer esto con un parámetro que permite la selección de múltiples valores, sólo obtendrás un valor nulo o en blanco. La opción es que utilices un script en el método onFetch del DataSet para reemplazar los valores del parámetro con los valores del campo que utilizas para mostrarlo al usuario.

Necesitas declarar una variable en el método “initialize” del reporte que contendrá las claves de los valores seleccionados, separados por una coma.

parameterSelectedValues=params["parameterName"].value.join(",");

 

después reemplazamos las claves en el método “onFetch” del DataSet

if(parameterSelectedValues.indexOf(row["id"].toString()) != null){
&nbsp; &nbsp; parameterSelectedValues=parameterSelectedValues.replace(row["id"].toString(),row["name"].toString());
}&nbsp;

 

y finalmente colocar un DynamicText en tu reporte con el valor

parameterSelectedValues;

 

El ejemplo completo lo encuentras aquí.

 

En un reporte de BIRT necesitaba desplegar en el encabezado el mes de acuerdo a un parámetro. Dicho parámetro se recibe como una cadena del “01”-“12” porque para la consulta necesito que sean 2 dígitos. Para determinar el mes, realizaba lo siguiente en el evento “initialize” del reporte:

meses="ENERO","FEBRERO","MARZO","ABRIL","MAYO"
    ,"JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE"
    ,"NOVIEMBRE","DICIEMBRE"];
params["Titulo"]=meses[parseInt(params["MES"])-1]+" del "+params["ANYO"];

Y funcionaba, eso creía yo, hasta que me dijeron que en Agosto(“08”) y Septiembre(“09”) aparecía la leyenda “undefined” en el título del reporte. Pues la causa de esto, es la interpretación que hace la función parseInt cuando le pasamos una cadena que comienza con “0” (recordemos que el código del reporte es Javascript), aquí encuentras la explicación. Para resolverlo, podemos hacer lo siguiente:

params["Titulo"]=meses[parseInt(params["MES"]
    ,10)-1]+" del "+params["ANYO"];

Simplemente le pasamos un segundo parámetro a la función parseInt para indicarle que la cadena(número) que le estamos pasando es en base 10 y no intente interpretarlo como un número octal.
Listo, ya podemos generar el reporte para Agosto y Septiembre 😉

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