Web Services – Interoperabilidad Java (Server) y .Net (Clients)

Por razones de trabajo me he encontrado algunas veces con este escenario, y después de haber tenido un par de problemas no muy graves pero que consumían tiempo se me ocurrió un esquema que hasta el momento me funciona, y es lo que voy a compartir en el post.
Minimalismo, si esa es la respuesta a esto, ya que regresamos a lo mas simple al exponer los web services retornando un string siempre como resultado del método, y así mismo recibir datos primitivos como parámetros, reemplazando a la estructuras de datos complejas, pero no en su totalidad ya que en ese dato primitivo en realidad contiene una estructura compleja serializada en Json y comprimida con un algoritomo Zip estándar y convertido a Hexadecimal.
La solución puede ser aplicada no solo a cliente .Net sino también a clientes Java, pero vamos a enfocar el post en clientes .net.
Para el lado del server Java vamos a necesitar los siguientes componentes adicionales al JDK:

  • Google Gson (Ya he utilizado esta librería en otros posts, recomendada hasta que el JDK no tenga incluya una implementación estándar de manipulación de Json)
  • Apache Commons Codec

Para los clientes de .net van los siguientes componentes adicionales al framework:

Ya con esto listo, creamos un proyecto web en java en su IDE preferido, yo voy a utilizar eclipse.

Para el ejemplo tengo 3 clases; un bean con una propiedad, luego un bean un poco mas complejo y finalmente uno que contiene a todo para mostrar una clase con una estructura no tan simple como prueba de concepto, aquí el código de cada uno:

public class SuperSimpleBean {
	private int number;

	public SuperSimpleBean(int number){
		this.number = number;
	}

	public SuperSimpleBean(){

	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}
}
public class SimpleBean{

	private String code;
	private Date datetime;
	private List superSimpleBeans;

	public SimpleBean(){
		superSimpleBeans = new ArrayList();
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public Date getDatetime() {
		return datetime;
	}

	public void setDatetime(Date datetime) {
		this.datetime = datetime;
	}

	public List getSimpleBeans() {
		return superSimpleBeans;
	}

	public void setSimpleBeans(List superSimpleBeans) {
		this.superSimpleBeans = superSimpleBeans;
	}

}
public class ComplexBean {
	private List simpleBeans;
	private String name;

	public ComplexBean(){
		simpleBeans = new ArrayList();
	}

	public List getSimpleBeans() {
		return simpleBeans;
	}

	public void setSimpleBeans(List simpleBeans) {
		this.simpleBeans = simpleBeans;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

El objeto que va a devolver el web service en string va a pasar por los siguientes pasos:

  • Serializacion a Json
  • Compresion del Json
  • Convertir a un String hexadecimal los bytes comprimidos

Las clases que nos van a ayudar con ese cometido son las siguientes:

public class ZipUtil {

	private static String toHexString(byte[] bytes) {
		return Hex.encodeHexString(bytes);
	}

	private static byte[] toByteArray(String hexString) {
		try {
			return Hex.decodeHex(hexString.toCharArray());
		} catch (DecoderException e) {
			e.printStackTrace();
		}
		return null;
	}

	private static byte[] fromGByteToByte(byte[] gbytes) {
		ByteArrayOutputStream baos = null;
		ByteArrayInputStream bais = new ByteArrayInputStream(gbytes);
		try {
			baos = new ByteArrayOutputStream();
			GZIPInputStream gzis = new GZIPInputStream(bais);
			byte[] bytes = new byte[1024];
			int len;
			while ((len = gzis.read(bytes)) > 0) {
				baos.write(bytes, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (baos.toByteArray());
	}

	private static byte[] fromByteToGByte(byte[] bytes) {
		ByteArrayOutputStream baos = null;
		try {
			ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
			baos = new ByteArrayOutputStream();
			GZIPOutputStream gzos = new GZIPOutputStream(baos);
			byte[] buffer = new byte[1024];
			int len;
			while ((len = bais.read(buffer)) >= 0) {
				gzos.write(buffer, 0, len);
			}
			gzos.close();
			baos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (baos.toByteArray());
	}

	public static String compressAndEncodeText(String text) {
		return toHexString(fromByteToGByte(text.getBytes()));
	}

	public static String decompressAndDecode(String data) {
		data = data.replace("-", "");
		byte[] zippedBytes;
		try {
			zippedBytes = toByteArray(data);
			byte[] decompressed = fromGByteToByte(zippedBytes);
			return new String(decompressed);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";

	}
}
public class ParserUtil {

	public static String parseAndCompress(Object o){
		String json = new Gson().toJson(o);
		return ZipUtil.compressAndEncodeText(json);
	}

}

Y ahora en la clase que se va a exponer como web service creamos objetos dummy con datos y devolvemos el resultado serializado y comprimido:

public class WebService {

	public String getData(){

		ComplexBean cBean = new ComplexBean();
		cBean.setName("A complex bean");

		for(int i=0; i<=1000; i++){
			SimpleBean sBean = new SimpleBean();
			sBean.setCode(String.format("Code %s", i));
			sBean.setDatetime(Calendar.getInstance().getTime());

			for(int j=0; j<=10;j++){
				SuperSimpleBean ssBean = new SuperSimpleBean(j);
				sBean.getSimpleBeans().add(ssBean);
			}

			cBean.getSimpleBeans().add(sBean);
		}

		return ParserUtil.parseAndCompress(cBean);
	}
}

Publicamos el web service, lo probamos con soapUI y el resultado es el siguiente:

soapUI

1f8b0800000000000000dddcbd8aa4d71586d15b693a9ea0cefe39679fce2c83338……… un string hexadecimal.

Si devolviera json en el web service, el tamaño de los datos de respuesta seria de 216kb, mientras que la respuesta comprimida es de 6.34kb, ustedes pueden calcular el porcentaje de compresión.

Del lado del cliente .net simplemente hay que hacer las clases con la misma estructura, algo que aun no he tenido tiempo para revisar es que al momento de deserializar el json las clases de .net en compact framework deben tener declarados los campos, en lugar de tener propiedades automáticas, honestamente no he encontrado en la librería Json for CF algún parámetro para que sea case insensitive, pero para el framework estándar no hay problemas:

public class SuperSimpleClassInfo
    {
        public int Number { get; set; }

        public SuperSimpleClassInfo()
        {
        }

        public SuperSimpleClassInfo(int number)
        {
            this.Number = number;
        }
    }
public class SimpleClassInfo
    {
        public string Code { get; set; }
        public DateTime DateTime { get; set; }
        public List<SuperSimpleClassInfo> SuperSimpleBeans { get; set; }

        public SimpleClassInfo()
        {
            SuperSimpleBeans = new List<SuperSimpleClassInfo>();
        }

    }
public class ComplexClassInfo
    {
        public string Name { get; set; }
        public List<SimpleClassInfo> SimpleBeans { get; set; }

        public ComplexClassInfo()
        {
            SimpleBeans = new List<SimpleClassInfo>();
        }
    }

Necesitamos una clase para convertir el texto hexadecimal a bytes, descomprimir y luego “parsear” el json a nuestra estructura de objetos, para descomprimir el texto creamos una clase ZipUtil similar a la que tenemos del lado de java:

public class ZipUtil
    {
        public static byte[] Decompress(byte[] compressed)
        {
            byte[] buffer = new byte[4096];
            using (MemoryStream ms = new MemoryStream(compressed))
            using (GZipStream gzs = new GZipStream(ms, CompressionMode.Decompress))
            using (MemoryStream uncompressed = new MemoryStream())
            {
                for (int r = -1; r != 0; r = gzs.Read(buffer, 0, buffer.Length))
                    if (r > 0) uncompressed.Write(buffer, 0, r);
                return uncompressed.ToArray();
            }
        }

        public static byte[] Compress(byte[] raw)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                using (GZipStream gzip = new GZipStream(memory, CompressionMode.Compress, true))
                {
                    gzip.Write(raw, 0, raw.Length);
                }
                return memory.ToArray();
            }
        }

        public static string CompressAndEncode(string text)
        {
            byte[] compressed = Compress(System.Text.ASCIIEncoding.UTF8.GetBytes(text));
            string hex = BitConverter.ToString(compressed);
            return hex;
        }

        public static byte[] ToByteArray(string hexString)
        {
            hexString = hexString.Replace("-", "");
            int NumberChars = hexString.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
            return bytes;
        }

        public static string DecompressAndDecode(string text)
        {
            byte[] textBytes = ToByteArray(text);
            byte[] uncompressed = Decompress(textBytes);
            string result = System.Text.ASCIIEncoding.ASCII
                    .GetString(uncompressed, 0, uncompressed.Length);
            return result;
        }
    }

Listo, con todo ya preparado agregamos la referencia al web service:

addreference

Una vez agregada la referencia es recomendable actualizar en el archivo de configuración el tamaño máximo de datos permitidos en las propiedades: maxBufferSize, maxReceivedMessageSize y maxStringContentLength con el valor 2147483647.

Listo, procedemos a invocar el metodo del web service, a descomprimir y luego con la libreria Json a convertir los datos a un objeto:

JavaWS.WebServiceClient client = new JavaWS.WebServiceClient();
            string data = client.getData();
            string json = ZipUtil.DecompressAndDecode(data);
            ComplexClassInfo complex = JsonConvert.DeserializeObject<ComplexClassInfo>(json);

El resultado es el objeto complex cargado con toda la información  para enviar datos en el mismo formato se aplica el proceso inverso desde .net a java.

Descargar fuentes

Happy Coding!

Saludos,
gish@c

Exponiendo y consumiendo servicios de datos con mybatis, play framework 2.0 y jersey

Mucho tiempo sin  bloggear pero bueno hay que devolver un poco a la comunidad así que voy a escribir en esta ocasión sobre como exponer servicios de datos con play, utilizando mybatis como mapeador de datos y la librería Jersey para consumir los servicios expuestos.

El modelo de datos con el que vamos a trabajar es el siguiente:

Como ven en el modelo no hago uso de claves foráneas, ni PK compuestas, eso es una práctica (buena o mala) personal, así que no se alarmen si no ven un modelo ER típico.

Empezando con mybatis (anteriormente ibatis); es un mapeador de datos muy simple de utilizar y muy flexible; he sido fan de hibernate desde sus primeras versiones pero me he encontrado con la simplicidad de este framework que no te quita el control del SQL que escribes (algo que para algunos desarrolladores es importante), pero a su vez hace realmente fácil el mapeo entre las tablas y los objetos.

Hay varias opciones para el mapeo de datos con mybatis, en mi caso me gusta tener la conexión a la base de datos en XML y los mapeos con anotaciones (también se pueden hacer con XML al estilo de los .hbml), así que lo primero es crear un archivo de configuración, en este caso lo llamamos dbconfig.xml.

Ahora En el archivo de configuración dbconfig.xml, como estoy utilizando jdbc de SQL Server la configuracion va de la siguiente manera

<pre><configuration>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
				<property name="url" value="jdbc:sqlserver://localhost\sql2008:51085;databaseName=dummy" />
				<property name="username" value="user" />
				<property name="password" value="secret" />
			</dataSource>
		</environment>
	</environments>
</configuration>

Tomando como ejemplo del modelo una de las clases:

public class Genre implements Serializable{

	private static final long serialVersionUID = -9130550406579029310L;
	private int id;
	private String name;

	public Genre(){
	}

	public int getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return String.format("%s - %s", this.id, this.name);
	}

}

Debemos definir una interfaz, la cual tiene las sentencias SQL y los metodos con las anotaciones correspondientes para el mapeo de los datos entre los objetos y la base de datos, los mapeos son para cualquier operación (Select, Update, Delete); para nuestro ejemplo vamos solo a hacer uso de Selects; de la clase Genre vamos a definir 2 métodos: Uno para consultar todos los registros de la base y otro para hacer una consulta por ID

public interface IGenreMapper {
	final String SELECT = "select id,name from genre";
	final String SELECT_BY_ID = "select id,name from genre where id=#{id}";

	@Select(SELECT)
	@Results(value = {
			@Result(column = "id", property = "id", javaType = Integer.class),
			@Result(column = "name", property = "name", javaType= String.class) })
	public List selectAll();

	@Select(SELECT_BY_ID)
	@Results(value = {
			@Result(column = "id", property = "id", javaType = Integer.class),
			@Result(column = "name", property = "name", javaType= String.class) })
	public Genre selectByID(@Param("id") int id);
}

Como se puede ver sin mucho esfuerzo es que sobre los métodos está definida la anotacion @Select que hace uso de las sentencias SQL que tenemos como Strings, y que de esas columnas seleccionadas tenemos el mapeo hacia que propiedades de la clase Genre corresponde cada una; y cuando estamos introduciendo parámetros #{nombre_párametro} en el comando sql como el caso del SELECT_BY_ID los podemos relacionar fácilmente con la anotación @Param con los párametros que se reciben en el método.

Una vez creada la interfaz hay que crear la clase que la implemente ya que ese es el objeto que finalmente se va a instanciar para hacer las consultas; la implementación es poca cosa, es simplemente invocar a una sesion de mybatis que nos devuelve un mapeador para que  internamente haga uso de lo definido en la interfaz de mapeo y obtener los resultados de la ejecución.

public class GenreDAO extends BaseDAO implements IGenreMapper{

public GenreDAO(SqlSession session){
		super(session);
	}

	@Override
	public List selectAll() {
		List result = session.getMapper(IGenreMapper.class).selectAll();
		closeSession();
		return result;
	}

	@Override
	public Genre selectByID(int id) {
		Genre result = session.getMapper(IGenreMapper.class).selectByID(id);
		closeSession();
		return result;
	}
}

Hay ciertos detalle en la clase mostrada, como por ejemplo heredar de un BaseDAO que tiene un constructor que recibe un objeto que implemente SqlSession de mybatis pero ya eso es como lo desees implementar, para mayor detalle pueden descargar el fuente relacionado al post en el link que se encuentra al final para que vean la implementación completa.

Algo que si voy a detallar es el caso cuando tenemos una propiedad compleja en nuestra clase; por ejemplo la clase Song tiene las propiedades Album y Genre que son del tipo de las clases Album y Genre respectivamente:

public class Song implements Serializable{

	private static final long serialVersionUID = -3728048783934199398L;
	private int id;
	private String name;
	private String duration;
	private Album album;
	private Genre genre;
        .
        .
        .
}

Así que para cargar las propiedades, en nuestra interface de mapeo utilizamos la anotación @One que nos permite llenar una propiedad compleja a partir de un método select que devuelva ese tipo de objeto. Como se mostro unas líneas arriba, el mapeador IGenreMapper tiene un método selectByID, del cual vamos a hacer uso en el mapeador Song para llenar la propiedad Genre, pasandole el id para que realice la consulta:

public interface ISongMapper {
	final String SELECT = "select id,album,name,duration,genre from song";

	@Select(SELECT)
	@Results( value= {
			@Result(column="id", property="id"),
			@Result(column="album", property="album", one=@One(select="com.musicservices.datamodel.mapper.IAlbumMapper.selectByID"),javaType=Album.class),
			@Result(column="name", property="name"),
			@Result(column="duration", property="duration"),
			@Result(column="genre", property="genre", one=@One(select="com.musicservices.datamodel.mapper.IGenreMapper.selectByID"), javaType=Genre.class)
	})
	public List selectAll();

}

La misma estrategia se sigue para cargar la propiedad Album, de esta manera el mapeo es completo desde nuestra base de datos relacional hacia el modelo de objetos aunque tengamos propiedades complejas.
Adicionalmente he creado una clase DataManager en la cual están las líneas donde se instancian los objetos de mybatis y el acceso a todos mis objetos DAO para centralizar el llamado a las clases, nos vamos a centrar en el código para instanciar el objeto SqlSessionFactory que es la interfaz entre nuestros objetos y la base de datos:

private DataManager(){
		if(sessionFactory == null){
			try {
				Reader reader = Resources.getResourceAsReader(DataManager.class.getClassLoader(),"dbconfig.xml");
				sessionFactory = new SqlSessionFactoryBuilder().build(reader);
				sessionFactory.getConfiguration().addMapper(IGenreMapper.class);
				sessionFactory.getConfiguration().addMapper(IAlbumMapper.class);
				sessionFactory.getConfiguration().addMapper(ISongMapper.class);
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
	}

Aquí cargamos el archivo dbconfig.xml para poder crear la sesión de mybatis a través de la clase SessionFactoryBuilder, y luego de eso registramos todas las interfaces de mapeo que hayamos creado, las cuales le sirven a mybatis para hacer su trabajo, ya que como vimos en la implementación de las interfaces hacemos uso del metodo getMapper para hacer referencia a los mapeadores registrados.

Una vez con la capa de datos lista exportamos los binarios como un .jar y procedemos a exponerla como servicios Rest a traves del framework play, lo primero es descargar los binarios desde playframework.org descomprimirlo en un directorio (recomendado sin espacios en los nombres de las carpetas) y con el comando play new [NombreAplicacion] crear la estructura de un proyecto Java:

Para mayor comodidad y modificar los archivos creados por play podemos importarlo como un proyecto para Eclipse, para esto es recomendable agregar la ruta de instalación de play a las variables de ambiente para ubicarnos dentro del directorio de nuestra aplicación y ejecutar los comandos play y luego eclipsify, y luego de eso importamos el proyecto dentro de nuestro workspace de Eclipse (el paso es opcional ya que puedes editar los archivos desde cualquier editor de texto)


Navegando dentro de los directorios de nuestra aplicación play nos vamos a encontrar con las carpetas app y conf, que son las que vamos a utilizar para el ejemplo ya que solo vamos a hacer uso del framework para exponer servicios de datos, no vamos a abarcar toda la funcionalidad, solamente el enrutamiento por url hacia los controladores que van a hacer uso de nuestro modelo de datos para realizar las consultas a la base de datos.

Empezamos creando una carpeta lib en el proyecto de play y agregamos todas las dependencias de mybatis, jdbc, y el .jar creado con nuestro modelo de datos.

Ahora sí, lo primero es dentro de /app/controllers creamos un nuevo controlador Album.java que hereda de  play.mvc.controller y le agregamos un método estático public static Result all(), esta nueva notación es parte del nuevo play 2.0, en resumen vamos a crear un método que controle todos los requests realizados por HTTP GET a la ruta /album y los devuelva en formato Json, finalmente el código con todas las consideraciones antes mencionadas queda así:

public class Album extends Controller{

	@play.mvc.BodyParser.Of(Json.class)
	public static Result all(){
		ObjectMapper mapper = new ObjectMapper();
		String json = null;
		try {
			List<com.musicservices.datamodel.entities.Album> albumList = DataManager.Instance().albumDao().selectAll();
			json = mapper.writeValueAsString(albumList);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return ok(json);
	}
}

Una vez que tenemos el controlador debemos decirle a play cual es la url de la que este método del controlador se va a hacer responsable, modificamos el archivo /conf/routes y agregamos la línea para indicarle que todo lo que pase por /album y sea un HTTP GET va a ser manejado por nuestro controlador:

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index()
GET	    /album                      controllers.Album.all()

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)

Listo, ahora procedemos a levantar nuestra aplicación en play con el comando run

*En dias previos a la redaccion de este post aparecio la versión 2.0 de play y al tratar de referenciar al archivo xml de configuración de mybatis que se encuentra dentro del jar da un error de classpath, asi que como workaround deben copiar el archivo dbConfig.xml dentro de su aplicación play en la ruta [AplicacionPlay]\target\scala-2.9.1\classes 

Una vez que tenemos todo listo accedemos por URL a localhost:9000/album y vemos el resultado es un string con notación Json de la colección de resultados obtenidos:


Bajo el mismo esquema creamos el controlador para hacer consultas de Albumes por código, donde ahora el método debe recibir un parámetro, y llamamos al metodo selectByID del mapeador:

@play.mvc.BodyParser.Of(Json.class)
	public static Result getByID(Integer id){
		ObjectMapper mapper = new ObjectMapper();
		String json = null;
		try {
			com.musicservices.datamodel.entities.Album album = DataManager.Instance().albumDao().selectByID(id.intValue());
			json = mapper.writeValueAsString(album);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return ok(json);
	}

Y en la configuración de rutas debemos indicar el mapeo hacia el nuevo metodo controlador de la siguiente manera:

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index()
GET	    /album                      controllers.Album.all()
GET	    /album/:id                 controllers.Album.getByID(id: Integer)

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)

Accedemos nuevamente por url a la ruta /album/{id} donde {id} en este caso es el valor de la columna id en la base de datos por la cual estamos consultando en al tabla Album, los resultados son los siguientes:

Y así podemos replicar el mismo esquema para todos los controladores que deseemos publicar. Ahora para consumir estos servicios Json vamos a hacer un simple cliente Java que haciendo uso de la libreria Jersey va a obtener via HTTP GET los resultados de la consulta y con la librería Gson de google vamos a regresarlos a su estado inicial de una Lista de objetos.

public static void main(String[] args) throws UnsupportedEncodingException{

		String albumName = URLEncoder.encode("Mane Attraction","UTF-8");
		String url = String.format("http://localhost:9000/song/album/%s",albumName);

		Client client = Client.create();
		WebResource res = client.resource(url);
		String jsonData = res.get(String.class);

		List<Song> songs = new ArrayList<>();
		Type listType = TypeToken.get(new TypeToken<Collection<Song>>(){}.getType()).getType();
		songs = new Gson().fromJson(jsonData, listType);
		for(Song s: songs){
			System.out.println(s);
		}
	}

En las primeras 2 líneas armamos la url al servicio expuesto de play, utilizando la clase URLEncoder ya que si no lo hacemos los espacios o cualquier caracter especial no se va a formatear correctamente, luego haciendo uso de las clases de Jersey generamos un WebResource de la url le hacemos un HTTP Get sólo con invocar el método del mismo nombre que nos devuelve el Json generado por nuestro servidor Rest; luego de esto tenemos que transformar este resultado en objetos manejables para java; por lo que hacemos uso de Gson, al cual tenemos que informarle el tipo de objeto al que debe transformar el String que le vamos a pasar, al pasar el tipo de la instancia de TypeToken<Collection<Song>> le indicamos que queremos transformar el Json a una lista de tipo Song, invocamos el metodo fromJson y listo estamos consumiendo ya los servicios expuestos de nuestro modelo de datos.
Los Rest services son una alternativa a los tradicionales Web Services, con play es tan simple como ya lo hemos visto; la flexibilidad de mybatis para mapear datos es fantastica y que podemos decir de las apis de Jersy y Gson que con no más de 9 líneas de código nos encapsulan una capa de comunicación y transformación de datos que debemos tener en cuenta y bajo nuestra amplia gama de frameworks para dar soluciones tecnológicas.

Saludos,
gish@c

 

Descargar fuentes

La falta de respeto a los Proyectos de Software

Trabajas como consultor de proyectos de software, y te llaman de un potencial cliente para una reunión ya que requieren un desarrollo de un sistema para automatizar un proceso de negocio; después de una reunión de una hora y de conocer (si tuviste suerte de que el que te explica el tema lo maneja en su totalidad) el proceso en macro, lo primero que te preguntan después de la reunión es: “Y cuánto tiempo va a tomar desarrollar el proyecto?” Oh si, es la típica pregunta que te hacen incluso antes de preguntarte el referente económico, obviamente si ya has pasado por situaciones similares dirás que tienes que analizar con tu equipo de trabajo, ver la disponibilidad de tus colaboradores, aterrizar el requerimiento, y que luego enviarás un documento con un cronograma en macro, advirtiendo con letra en negritas que va a variar obviamente después de levantar el detalle del proceso. Y sí, es totalmente razonable que la empresa o cliente que requiere el desarrollo del proyecto quiera saber cuanto va a demorar el trabajo a realizar, pero veamos los siguientes puntos que no siempre se toman en cuenta:

  • Por Dios es la primera reunión!
  • Por lo general en esa etapa no participa el personal que va a utilizar realmente el sistema (conocido en el mundo de sistemas como el personal operativo), quien realmente tiene el detalle del día a día de lo que se realiza, y todos los escenarios que la herramienta debe contemplar para que Él, el usuario final sienta que tiene una herramienta que realmente lo ayuda en su trabajo
  • No se puede estimar a ciencia cierta cuanto puede tomar todo un proyecto en esa instancia, y Dios te proteja de no abrir la boca equivocadamente y dar un número de días/meses (porque si dices años, olvidalo… nunca te asignarán el proyecto incluso en algunos casos si estás participando por desarrollar un ERP) ya que si das un estimado equivocado, cuando propongas un cronograma más aterrizado que se pasa en uno o dos meses, vas a tener el estimado inicial pegado en la frente, y va a ser el arma principal con la cual van a arrinconarte para que reduzcas los tiempos de tu proyecto (No lo olviden, los desarrolladores de software se sacan una pantalla de mantenimiento del bolsillo)
  • En muchos de los casos no necesariamente es personal técnico el que participa en la reunión, sino gente del área del negocio y que presiona muchísimo sin conocer realmente todo lo que implica desarrollar un proyecto de software

Entonces… Que implica desarrollar un proyecto de software? Al igual que en muchas otras profesiones donde el trabajo intelectual y la creatividad prevalecen, el desarrollar un proyecto de software implica plasmar a través del uso de la tecnología la automatización de algún proceso, crear una nueva herramienta de trabajo, una plataforma de entretenimiento, en fin es todo un gran proceso que varia de acuerdo a la metodología de trabajo que se tenga, pero por lo general un proyecto con un grado aceptable de formalidad está dirigido mas o menos así:

  • Recopilación de información sobre lo que se va a desarrollar (Al detalle)
  • Análisis de las tecnologías que pueden utilizarse para facilitar el desarrollo del proyecto
  • Planteamiento de la arquitectura en base a la tecnología a utilizar
  • Identificar los módulos del proyecto y sus dependencias
  • Definir prioridades
  • Establecer hitos con entregables parciales (Claro, porque si el cliente no ve algo pronto cree que no estás haciendo nada)
  • Presentación al cliente de toda tu fantástica propuesta y conseguir su aceptación
  • Al fin, empezar el desarrollo
  • Pruebas internas de desarrollo
  • Pruebas verticales y horizontales
  • Estabilización (A corregir todo lo que dejaste mal hecho por tus propios errores)
  • Pruebas con usuarios finales
  • Estabilización II (A corregir todo lo que te dijeron que era de una manera, pero que finalmente se hacia de otra)
  • Implementación (El día que no duermes por poner en producción el proyecto)
  • Soporte en producción (Las semanas que no duermes ya que te llaman todo el día a cualquier hora por todo lo que pase en el sistema)
  • Documentación (Suele ir en paralelo, pero aceptemos la realidad, por lo general dejamos la documentación para el final)
  • Cierre del proyecto (El día que no duermes porque finalmente estás festejando con tu equipo de trabajo que terminaron el proyecto)

Aún sigues creyendo que la gente de sistemas estornuda y aparece una dll? (Es un eufemismo a como realmente se dice… ustedes entienden)… Y por qué está en negritas la palabra arquitectura? Porque sí, estimad@s lectores… Un proyecto de software es un símil a un proyecto de construcción civil:

  • Se hace todo el dimensionamiento y el estudio de la obra
  • Se escoge y se calcula el material a utilizar
  • Se estima el tiempo que va a llevar la obra
  • Se construyen las bases, se rellena el terreno, etc etc
  • Se monta la estructura que va a sostener la obra
  • Se empieza a construir
  • Se compra material si la estimacion fue errónea, se ajustan detalles etc.

No soy conocedor de como llevar una obra de construcción, pero estoy tratando de darle un orden lógico no necesariamente el óptimo ni el deber ser, sólo es un ejemplo para llevarlo al símil del tema del post, así que favor ahorrarse el troleo 🙂

Existe el término, muy utilizado, y ampliamente conocido por el universo de usuarios y x todos nosotros: “SE CAYÓ EL SISTEMA“? Porque (perdonen si lo repito tanto) si volvemos a la obra civil, y está mal construida… Se cae la construcción!!! No les parece algo sumamente grave? Desconozco la ley de mi país con respecto a éste tema, pero por lo que he escuchado en las noticias en otros países, si a un Arquitecto se le cae una obra puede ir a parar a prisión!

Por eso para nosotros, la gente que está detrás de un proyecto de software, estimar el tiempo necesario, hacer nuestro trabajo asegurando un grado de calidad sobre el trabajo que va a reflejar nuestra imagen, es muy importante (obvio no queremos ir a prisión 🙂 ). Por eso nos estresamos si por motivos fuera de nuestras manos hay que acortar los plazos, cambian los requerimientos a mitad del proyecto, u ocurre cualquier eventualidad que nos afecta en nuestro trabajo.

Estimad@s tod@s, yo les pregunto:

  • Si un médico lo está operando luego de indicarle que el proceso va a durar 3 horas… Sus familiares se meten a la sala de operación a pedirle que acorte el tiempo de la operación?
  • Si un Arquitecto ya tiene los planos de un edificio, y cuando ha construido la mitad de los pisos… Usted se arrepiente de lo que pidió y le pide que cambie el diseño y los planos? O le llama a decirle que se olvidó de que quería unas escaleras en la mitad?

Si la respuesta es no (Si es si, cierre ésta ventana y siga jugando farmville)… Entonces por qué se cree que a los proyectos de software se los puede exprimir, acortar, desarmar, etc etc etc sin pensar en el impacto que puede llegar a tener no sólo en los tiempos de entrega, sino también en la calidad del trabajo QUE ES NUESTRA RESPONSABILIDAD entregar?

El motivo de éste post es el acumulado de experiencias propias y ajenas en las cuales he visto como se trata de minimizar o faltarle el respeto a todo un trabajo que es mucho más que “un simple programita” como le suelen llamar… La gente que desarrolla proyectos de software a diferencia de otros profesionales tiene que ponerse en los zapatos de otros y entender el área sobre lo que está trabajando:

  • Si hace un software contable, tiene que saber de contabilidad
  • Si hace un software para forecasting, tiene que entender los modelos matemáticos
  • Si hace un software para graficar planos, tiene que entender de arquitectura

Desarrollar proyectos de software es una actividad dinámica, muy exigente y a su vez satisfactoria cuando sabes que resolviste un problema, que entregaste un trabajo de calidad, que el cliente que respetó tu trabajo es un cliente feliz que te volverá a llamar para desarrollar otro proyecto juntos; por eso y muchas otras razones la gente que trabajamos en y con tecnología, y nuestro trabajo, estamos en un nivel que merece un alto respeto por parte de cualquier mortal 🙂

Feliz día del programador!

Saludos,

gish@c

Java Series – Authentication y Authorization en J2EE

Si hay algo de lo que no se pueden quejar las personas que desarrollan en web con asp.net es sobre lo fácil que es el manejo de seguridad a recursos protegidos (authorization) y lo intuitivo de los tipos de autenticación que ofrece .net (authentication), pero suele ocurrir que por razones laborales/educativas etc dan el salto a otra plataforma de desarrollo y oh! sorpresa… donde esta esa sección de configuración para proteger los accesos??? Y así empieza la odisea … El post de hoy expone justamente el manejo de seguridad en J2EE.
Para el ejemplo vamos a crear un proyecto web (El proyecto está con JSF 2) y vamos a definir directorios donde van a estar las páginas de administración para 2 roles de usuario: Administrador y Super Administrador, controlando el acceso al recurso que le corresponda. La estructura para el ejemplo es la siguiente:

Como se visualiza en el gráfico están claramente definidas las carpetas, en las cuales se supondría que están las páginas a las que sólo se puede tener acceso con ciertos privilegios.
Adicionalmente el proyecto cuenta con la página forbidden.xhtml que para el caso es la página a la que es redirigido el request cuando se trata de acceder a una sección protegida sin tener las credenciales requeridas, y la página index.xhtml que es la página de login.
El estándar casi por omisión para seguridad en J2EE es Spring Security, pero para alguien que viene de otra plataforma le introduce otro millón mas de conceptos (lo cual no es malo, sólo que a veces son tan amplios que nos apartan totalmente el camino de lo que realmente queremos implementar), así que desde la especificación de Servlets 2.3 tenemos un feature que permite interceptar los requests y de esa manera determinar quien está tratando de acceder a los recursos de la aplicación; este feature son los Filters.
Muchos son los casos de uso en los que nos sirve interceptar requests, pero vamos a enfocarnos en autenticacion y autorización.
Para definir un filtro simplemente, creamos una clase que implemente la interfaz javax.servlet.Filter, e implementamos los métodos que nos exige:

public void init(FilterConfig filterConfig);

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain);

public void destroy();

Tenemos definido el filtro y para interceptar los requests tenemos que configurarlo en el web.xml y mapear las rutas en las que va actuar el mismo, para hacerlo colocamos la siguiente configuración:


    <filter>
        <filter-name>AuthAdminFilter</filter-name>
        <filter-class>demo.security.AuthAdminFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AuthAdminFilter</filter-name>
        <url-pattern>/administration/*</url-pattern>
    </filter-mapping>

El primer elemento filter-name es referencial, lo que si es importante es en filter-class colocar el nombre completo de la clase que está implementando el filtro, luego en el siguiente filter-name se hace referencia a la declaración anterior y colocamos la ruta que queremos que el filtro administre, al colocar * cualquier página que esté dentro de ese contexto será validada.
Para validar en el filtro si el usuario está autenticado y el rol que tiene, en la pantalla de login, una vez validadas las credenciales vamos a almacenar una variable de sesión con un objeto de tipo UserBean, que basicamente tiene el id del usuario y el rol:

        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext extContext = context.getExternalContext();
        String url = "";
        if(isAdmin(user, password))
        {
            url = extContext.encodeActionURL(
                    context.getApplication().getViewHandler().getActionURL
                    (context, "/administration/adminPanel.jspx"));
            extContext.getSessionMap().put(USER_KEY, new UserBean(user, "admin"));
            extContext.redirect(url);
            return;

        }
        if(isSuperAdmin(user, password))
        {
            url = extContext.encodeActionURL(
                    context.getApplication().getViewHandler().getActionURL
                    (context, "/superadmin/superAdminPanel.jspx"));
            extContext.getSessionMap().put(USER_KEY, new UserBean(user, "superadmin"));
            extContext.redirect(url);
            return;
        }

        }

En el código mostrado se valida si las credenciales son válidas, si es así lo redirige a la sección de administración correspondiente, y finalmente pone en una variable de sesión el objeto de usuario.

Como tenemos 2 secciones que queremos validar los requests vamos a crear 2 filtros, uno para validar si es “admin” y otro para validar si es “super admin”, por lo tanto configuramos otro filtro en el web.xml:


     <filter>
        <filter-name>AuthSuperAdminFilter</filter-name>
        <filter-class>demo.security.AuthSuperAdminFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AuthSuperAdminFilter</filter-name>
        <url-pattern>/superadmin/*</url-pattern>
    </filter-mapping>

Hasta ahora tenemos el mapeo de las url’s a los filtros que van a validar los request, ahora veamos el desarrollo de las clases que implementan el Filtro de los requests:

Filtro para Sección de Administradores

public class AuthAdminFilter implements Filter {

    private FilterConfig configuration;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.configuration = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (((HttpServletRequest) request).getSession().getAttribute(
                AuthBean.USER_KEY) == null) {
            ((HttpServletResponse) response).sendRedirect("../forbidden.jspx");
        } else {
            chain.doFilter(request, response);
        }

    }

    @Override
    public void destroy() {
        configuration = null;
    }

Filtro para Sección de Super Administradores

public class AuthSuperAdminFilter implements Filter {

    private FilterConfig configuration;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.configuration = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        if (((HttpServletRequest) request).getSession().getAttribute(
                AuthBean.USER_KEY) == null || !((UserBean)((HttpServletRequest) request).getSession().getAttribute(
                AuthBean.USER_KEY)).getRole().equals("superadmin") ) {
            ((HttpServletResponse) response).sendRedirect("../forbidden.jspx");
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        this.configuration = null;
    }

}

Ambas implementaciones son casi idénticas salvo por el caso de que en la de superadmin validamos que además de que exista en sesión la variable que nos indica que está autenticado, el usuario tenga el rol de “superadmin”, en caso que no cumpla la condición se lo redirige a la sección de acceso restringido.
Bueno, eso es todo en el post 😀 para ver el ejemplo pueden descargar el código fuente desde aquí, es un proyecto netbeans 6.9 con glassfish.

Saludos,
gish@c

DVP 2010

Hoy quería agradecer a la gente de db4o por el reconocimiento de Db4o Most Valued Professional 2010 (DVP). Es muy interesante contribuir a ésta comunidad y aprender siempre algo nuevo del paradigma ODBMS, sobre todo si es un producto tan bueno como db4o (si aún no lo han revisado se los recomiendo al 100%).
Fue muy gratificante contribuir en alguno que otro feature del Object Manager (Java Version) (No nos fue tan mal, las 2 versiones liberadas suman 6000 descargas!!!), lamentablemente no da más el tiempo para seguir aportando de esa manera, y por otro lado desde hace bastante tiempo el Object Manager Enterprise es free, así que hay un buen producto de exploración de objetos; aunque sinceramente no estaría mal si algún miembro de la comunidad inicia un proyecto para crear otro explorer, en lo personal por el tipo de aplicación creo que encajaría perfecto con Netbeans Platform como base xD

En fin, muchas gracias por el reconocimiento!!! A partir de hoy agrego el badge de DVP 2010 al blog junto a los de los años anteriores 😀

Saludos,
gish@c

Java Series – Desmitificando Multithreading

Uuu ya con ese titulo ni ganas de leer este post verdad? xD  Pero tranquilidad que por la misma razón se llama “Desmitificando”, porque no es algo tan del otro mundo tener una aplicacion de esas caracteristicas en JAVA.
Hace algunos anhos cuando la tecnología de múltiples procesadores empezó a ubicarse en las maquinas de escritorio, no solo genero un impacto a nivel de hardware, sino también a nivel de software, ya que de alguna u otra manera nos dio la posibilidad de dar mas procesamiento a los equipos clientes, permitiéndonos mezclar el paradigma de Thin client, con un aprovechamiento real de todos los recursos de hardware sobre los que corren nuestras aplicaciones.
A estas alturas de la vida, el no utilizar al máximo los ciclos de CPU en nuestras aplicaciones no es solo un crimen tecnológico sino también un atentado al medio ambiente, y de esta premisa nacen conceptos como el Grid Computing, donde pones a disposición el tiempo ocioso de tu CPU para ser aprovechado por otros. En fin, el problema de trasfondo es que el hardware esta evolucionando mucho mas rápido que el software, y no todas las aplicaciones están preparadas para aprovechar la infraestructura sobre la que están siendo utilizadas, generando desuso de recursos.
En una experiencia previa, teníamos un cliente con un servidor de 8 procesadores y una cantidad considerable de RAM, y resulta que su personal de IT nos reporto como un Issue el hecho de que nuestra aplicación del lado servidor no estaba utilizando todos los recursos disponibles, es decir; le hicieron un monitoreo al uso de los procesadores cuando se ejecutaban procesos masivos y vieron que solo uno se utilizaba al máximo, el resto eran ciclos ociosos de cpu… Así que como buenos Ingenieros y Arquitectos de Software nos toco reestructurar la aplicación e identificar los procesos que se podían hacer de manera paralela para lanzar múltiples procesos y sacarle el jugo (como decimos por acá)  a los procesadores.
Si recordamos esas divertidas clases de Sistemas Operativos, se nos viene a la mente el gráfico del ciclo de un proceso donde la premisa era que un solo proceso corría a la vez, pero ahora eso varia dependiendo de la capacidad de los procesadores (Quad Core (4), I7(6), etc) y pues, como hacemos que nuestra aplicación utilice todos estos recursos??? A traves de Threads (Hilos).
(No podía omitir toda la historia anterior como introducción al tema, pero ya estamos en la parte interesante: el ejemplo! )
La mayoría de los lenguajes nos proveen las herramientas necesarias para cumplir con el objetivo de hacer aplicaciones multithreading (.Net, Scala, Google’s GO, etc, etc) y Java no es la excepción con sus clases para manejar threading.
El uso de threads implica básicamente identificar los procesos que se pueden ejecutar en paralelo, y una vez identificado no es complicado implementarlo; los dolores de cabeza se producen cuando tenemos que mezclar ese modelo con reacciones en UI (que levanten la mano los que alguna vez vimos una RED CROSS OF DEATH, o en .net el DataTable Index Corrupted) ya que los controles, y ciertas estructuras solo pueden ser manipuladas por un hilo a la vez.
El ejemplo de este post toma 2 procesos para realizar operaciones con threads:

  • Ejecutar asincronicamente una busqueda en iTunes de canciones por artista (Evita congelar el UI)
  • Simular un proceso de almacenamiento masivo de los resultados (Multithreading)

Es una molestia que se congele el UI mientras esperamos respuesta a la ejecución de un proceso, y un usuario es feliz si su aplicación le dice que esta haciendo, mientras que si se queda la pantalla congelada dirá “se colgó esta vaina” y seguramente le hara un kill como sea posible y llamara a service desk. Este caso esta representado en ejemplo de buscar a través del iTunes Store Search API, debido a que es una búsqueda en un servidor externo a través de la web, y dependemos básicamente de la latencia en la transmisión de datos para que la respuesta congele el UI hasta que se complete el request.
Hacemos la consulta esperando como resultado un texto en JSon y con la libreria Gson lo desarmamos en un abrir y cerrar de ojos, para luego mostrarlo en un JTable, así que fácilmente podemos identificar la parte del proceso que debe ser asincronica: La consulta al servidor web y el procesamiento de los resultados hasta que están en el formato esperado por el modelo del JTable.
Hasta ahí cumplimos con la primera parte de la premisa que era identificar la parte del proceso que podemos hacer asincronica, ahora la segunda parte es interactuar con el UI, para lo cual JAVA se ha encargado de abstraernos de este problema y desde su versión 6 incluye oficialmente al SwingWorker, el cual se encarga de ejecutarse en un contexto en el cual se ejecuta en background sin congelar el UI y permitiéndonos interactuar con los controles; la declaracion de la clase debe heredar de SwingWorker

public class iTunesSearchHandler extends SwingWorker<Object, Object>

Lo que queremos que se ejecute asincronicamente lo colocamos en el metodo doInBackground y para hacer alguna acción cuando termina el proceso utilizamos el metodo done

@Override
    protected Object doInBackground() throws Exception {
        label.setVisible(true);
        URL url = new URL(searchUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setReadTimeout(15 * 1000);
        connection.connect();
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        StringBuilder stringBuilder = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line + "\n");
        }
        parseJsonToList(stringBuilder.toString());

        return "";
    }

@Override
    protected void done() {
        super.done();
        label.setVisible(false);
    }

Las lineas donde cambia la visibilidad de un label es para mostrar el tipico “loading” en la pantalla mientras se ejecuta el request y así sabemos que esta haciendo la aplicación.
Como indique anteriormente la otra parte del ejemplo es simular un procesamiento masivo de la información que tenemos en memoria y típicamente es almacenarla en algún lugar; es importante identificar estas operaciones de base de datos como las típicas que podemos realizar paralelamente, (si son datos dependientes o referenciados entre si es vital manejar sincronizacion de procesos) ya que de nuestro DBMS esperamos un modelo ACID que nos garantice la integridad de la información. Por lo cual de manera descabellada vamos a lanzar un thread para grabar, el cual lanzara un nuevo thread por cada registro a guardar! 🙂

@Override
    protected Object doInBackground() throws Exception {
        label.setVisible(true);
        for(iTunesSearchResult item : data)
        {
            item.setRowIndex(data.indexOf(item));
            new iTunesSaveHandler(item).execute();
        }
        return "";
    }

Que conseguimos con eso? Pues que como ningún registro es dependiente del otro cada uno se ejecuta como una transacción independiente y ponemos a trabajar los procesadores al tener que ejecutar muchos procesos simultáneos 😛

Para mostrar el resultado del proceso, podemos ver con el jConsole los hilos que se ejecutan y ademas en el output vemos los mensajes donde el orden de almacenamiento no es necesariamente el orden en el que se fueron ejecutando los hilos:

Pantalla de aplicacion demo


Monitoreo antes de proceso masivo


Monitoreo despues de proceso masivo


Log de transacciones

Como conclusiones de los prints anteriores, podemos ver que en el monitoreo después de ejecutar la simulación de almacenamiento masivo se incrementan la cantidad de threads en la aplicación y el uso de cpu; mientras que en el log de transacciones podemos ver que los registros se van procesando no necesariamente en el orden que fueron lanzados, ya que cada transacción es independiente y finalmente depende de la prioridad que le asigne el cpu al thread para su ejecución.

Saludos,
gish@c

Descargar Proyecto Netbeans 6.8

Blackberry Java Development – El poder del paint() cap 2

Continuando con el post anterior, completaremos el control personalizado con estilo MSN, agregando en esta ocasión la imagen de status, la imagen del avatar y los textos de nick y subnick.

Los conceptos al vuelo del momento son:

  • Las imágenes se obtienen desde recursos internos (imágenes dentro del proyecto) o desde fuentes externas (streams de datos desde servidores web); para nuestro ejemplo vamos a utilizar sólo imágenes como recursos internos.
  • Recordemos que un dispositivo móvil tenemos recursos limitados (procesamiento, batería, etc); por lo cual debemos optimizar los procesos; así que cargaremos las imágenes únicamente en el constructor para hacerlo una sola vez.

Para el avatar utilizaremos una imagen de 40×40 que agregamos en una carpeta de recursos al mismo nivel de la carpeta de paquetes, en la misma ruta colocamos la imagen para el status:

Cargamos las imagenes en el constructor:

statusImage = Bitmap.getBitmapResource("resources/online_dot.png");
avatarImage = Bitmap.getBitmapResource("resources/dukeAvatar.png");

Dibujamos las imágenes en el método paint:

graphics.drawBitmap(x + 3, y + 3, statusImage.getWidth(), statusImage.getHeight(),statusImage, 0, 0);
graphics.drawBitmap(x2 - avatarImage.getWidth(), y, avatarImage.getWidth(), avatarImage.getHeight(),avatarImage, 0, 0);

Por ahora el resultado que tenemos es:

No nos podemos quejar, todo funciona como debería xD

El siguiente paso es simplemente mostrar el texto del nick y el subnick, para lo cual hacemos uso de la propiedad heredada text, y agregamos una nueva subnick.
La clase graphics nos provee el método drawText para cumplir con el objetivo, aquí debemos tener algunas consideraciones:

  • Los nicks no siempre son cortos, por lo cual debemos manejar en caso que sea algo demasiado extenso para el ancho de la pantalla (X lo general en nuestras amigas vemos nicks como: “[c=55][b]Maria- •  ✿•·.·´¯`·.·•Ƹ̵̡Ӝ̵̨̄ƷƸ̵̡Ӝ̵̨̄ƷƸ̵̡Ӝ̵̨̄Ʒ• -[/b].. [/b][/c]“)  😛
  • Podemos hacer uso de las fuentes instaladas en nuestro dispositivo (Ver en: Settings->Screen/Keyboard)
  • El color de fuente lo definimos con el método setColor de la clase graphics.
  • La sobrecarga del método drawText que utilizaremos es la siguiente:
    public int drawText(String text, int x, int y, int flags, int width)

Donde como flag utilizaremos DrawStyle.ELLIPSIS (que indica dibujar los si el texto no alcanza), y bueno la posicion X y el ancho es un simple cálculo del espacio que tenemos disponible menos el espacio utilizado por las imágenes que ya hemos dibujado.

graphics.drawText(getText(), 5 + statusImage.getWidth(), y,	DrawStyle.ELLIPSIS, x2 - statusImage.getWidth() - avatarImage.getWidth() - 5);
graphics.drawText(this.subnick, x + 1, y + getFont().getHeight(), DrawStyle.ELLIPSIS, x2 - avatarImage.getWidth() - 1);

Le agregamos un constructor a nuestro control y modificamos la pantalla para pasar como parámetros el nick y el subnick:

public LikeMsnLabel(String text, String subnick)
{
  this();
  setText(text);
  this.subnick = subnick;
}
LabelField label = new LikeMsnLabel("gish@c - http://gishac.wordpress.com","Blogging - http://gishac.wordpress.com");

Al ver en el emulador, lo que podemos notar a primera vista es que el subnick debe estar en un un tamaño de fuente menor al principal, así que hay que modificar un poco el código para lograr el cometido

Comenzamos definiendo las fuentes para cada texto en específico:

subnickFont = getFont().derive(Font.PLAIN, 13);
nickFont = getFont().derive(Font.BOLD, 15);

El método derive nos facilita a partir de la fuente por default hacer ciertas variaciones, en este caso el estilo y el tamaño; con las fuentes creadas hacemos el set de la fuente a la clase graphics antes de dibujar cada texto:

graphics.setFont(nickFont);
graphics.drawText(getText(), 5 + statusImage.getWidth(), y,DrawStyle.ELLIPSIS, x2 - statusImage.getWidth() - avatarImage.getWidth() - 5);
graphics.setFont(subnickFont);
graphics.drawText(this.subnick, x + 1, y + getFont().getHeight(), DrawStyle.ELLIPSIS, x2 - avatarImage.getWidth() - 1);

Y listo, tenemos nuestro control con un mejor estilo y teniendo en cuenta todos los aspectos referentes al texto 😀 el resultado es el siguiente

Para darle el toque final, sólo tenemos que exponer un evento click y manejar el focus del label para utilizar un color sólido de fondo.

El efecto de un color sólido de fondo lo logramos omitiendo el pintado con degradado y utilizando colores sin variación en el arreglo de colores:

private final int[] solidColors = new int[] { BACKGROUND, BACKGROUND,BACKGROUND, BACKGROUND };

Como en el constructor definimos el control como Focusable hacemos uso del metodo isFocus para saber cuando utilizamos los colores sólidos o el degradado:

private int[] getColors()
{
   if(isFocus())
      return solidColors;
   return colors;
}

Obviamente reemplazamos en el metodo drawShadedFilledPath por el llamado al método que nos da el estilo:

graphics.drawShadedFilledPath(xIndexes, yIndexes, null, getColors(),null);

Y por último sobrecargamos los metodos onFocus y onUnfocus para obligar a que el control se redibuje al presentarse alguno de estos eventos:

protected void onFocus(int direction) {
	super.onFocus(direction);
	invalidate();
}

protected void onUnfocus() {
	super.onUnfocus();
	invalidate();
}

Podemos probar agregando otro componente que obtenga el focus y vemos la variación en nuestro control:

Muy bonito, muy chevere… pero si no manejamos eventos nuestro control puede estar incompleto, así que simplemente sobrecargamos el método trackwheelClick para informar al change listener del control que se produjo un evento:

if (this.getChangeListener() != null) {
     this.getChangeListener().fieldChanged(this, 0);
}
return super.trackwheelClick(status, time);

Y x fin!!! Pensé en hacer 3 partes el tópico, pero ya quería pasar la página y pensar en otra cosa interesante para postear, así que eso es todo x ahora, ya tienen todas las herramientas para crear sus apps y crear interfaces a su gusto.

Salu2
gish@c

Descargas

Blackberry Java Development – El poder del paint() cap 1

Nunca voy a olvidar cuando hace más de 6 años, apenas estaba aprendiendo .net estudiando el material de desarrollador 5 estrellas y otros libros, entré a mi primer trabajo muy nervioso y ansioso de aprender cosas nuevas; al principio leyendo código y viendo los estándares del equipo de trabajo, hasta que un par de días después mi buen amigo John (en ese entonces uno de los líderes del equipo de work) sin tener piedad me dice: Gisbert necesitamos que hagas que a partir de una fuente de datos con jerarquía la sobrecarga del paint de un ListView con una imagen del lado izquierdo y con la identación como el treeview (no recuerdo xq no usamos un treeview, pero necesitabamos el listview), no se que cara habré puesto pero ahí tuve una explicación más detallada del tema y unas sabias hojas impresas de código fuente con el ejemplo de como sobrecargar el método, a partir de ese momento fueron gratas las experiencias con el GDI+, creando panels con gradients, mis propias listas, etc etc… en fin aprendiendo un poco más para hacerlo todo x mi cuenta.
Ahora se preguntarán y xq este pana poco más y nos cuenta la historia de cuando era chiquito?… Pues xq el post de hoy es exactamente sobre la creación de controles personalizados!!! esta vez enfocado en aplicaciones blackberry.
En fin, decidí no entrar en mucho detalle de posts sobre como agregar controles o hacer click en un botón de ya que es información que se puede buscar en google o en el millón de PDF’s que ofrece RIM, si es necesario revisar ciertos conceptos se verán al vuelo (como decimos x acá, u on the fly como dirían los gringos)
Como premisa, si las aplicaciones móviles no pueden ser taan poderosas en un dispositivo móvil y remplazar x completo una aplicación de escritorio o web, que les queda? pues, ser vistosas, funcionales y atractivas para el usuario; afortunadamente el api de desarrollo nos permite sobrecargar los métodos de dibujo y nos da acceso al objeto Graphics para poder hacer nuestros propios controles 😀

A estas alturas, tod@ usuari@ con un blackberry ha utilizado el Windows Live Messenger for Blackberry, el cual tiene una interfaz agradable, pero oh sorpresa… comienzo a hacer aplicaciones bb y no tengo una suite de controles con todos esos estilos 😥  así que el objetivo de los siguientes posts es hacer un control con un estilo como la cabecera del messenger, en el cual podemos notar que tiene 2 imágenes, es pintado con gradiente, cambia de color al hacer focus muestra el nombre y el estado, y de paso abre otra pantalla al hacerle click.

Empezamos haciendo el proyecto en Eclipse de tipo Blackberry y agregamos 3 clases, una extendiendo de UiApplication (el punto de entrada de la app), y otra clase que será la pantalla principal y es donde vamos a agregar los controles que extiende de MainScreen y por último el control que vamos a dibujar.

public class LikeMsnScreen extends MainScreen {
	public LikeMsnScreen(){
	}
}
public class ApplicationStart extends UiApplication {
	public ApplicationStart(){
		this.pushScreen(new LikeMsnScreen());
	}

	public static void main(String[] args) {
		ApplicationStart instanceApp = new ApplicationStart();
		instanceApp.enterEventDispatcher();
	}
}

Por ahora nada espectacular, simplemente estamos definiendo que al ejecutar la app inicie mostrando como pantalla una nueva instancia de la clase LikeMsnScreen, con lo cual podemos notar que el método pushScreen nos sirve para poner a la vista una pantalla.
Como siguiente paso vamos a crear una nueva clase, que va a ser nuestro control personalizado; los conceptos al vuelo del momento son:

  • Los controles heredan de la clase Field (ej: LabelField, ButonField, ListField… etc); para nuestro ejemplo el control que se parece más a la cabecera del msn bb es un LabelField pero con la característica de ser “Focusable” y “Clickeable”
  • Para definir cierto comportamiento por default podemos utilizar ciertos flags en el constructor de la superclase, por ejemplo LabelField.USE_ALL_WIDTH
  • Para dibujar el control debemos sobrescribir los métodos paint y layout
  • Los colores se manejan como variables int pero se tiene acceso a una lista de colores a través de la enumeración Color.

En nuestro ejemplo vamos a pintar un rectángulo el cual tiene los siguientes vértices:

El método de la clase Graphics que llena de color con gradiente un polígono tiene la siguiente nomenclatura:

public void drawShadedFilledPath(int[] xPts,int[] yPts,byte[] pointTypes,int[] colors,int[] offsets)

Donde xPts, yPts representan cada uno de los puntos que componen lo vértices del polígono a pintar, en nuestra figura serían {x0,x0,x1,x1},{y0,y0,y1,y1}, pointTypes especifica la forma de cada vértice, offsets el inicio de cada área en los puntos del polígono y colors corresponde a los colores de cada vértice. Es en este punto donde al jugar con esta combinación de colores le damos la forma al gradiente, para el rectángulo debemos definir el color para los 4 vértices por lo que el arreglo de colores sería {color1,color2,color3,color4}; si por ejemplo el color3 es diferente a los otros colores tendremos un gradiente con efecto hacia la esquina inferior derecha del rectángulo (x1,y1).
Con lo expuesto anteriormente el código del control queda por ahora de la siguiente manera:

public class LikeMsnLabel extends LabelField {

	private static final int BACKGROUND = Color.STEELBLUE;
	private static final int BACKGROUND2 = Color.WHITESMOKE;

	private int customHeight = 40;
	private final int[] colors = new int[] { BACKGROUND, BACKGROUND,BACKGROUND2, BACKGROUND };

	public LikeMsnLabel() {
		super("", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH | LabelField.FOCUSABLE);
	}

	protected void layout(int width, int height) {
		setExtent(width, customHeight);
	}

	protected void paint(Graphics graphics) {
		int y = graphics.getClippingRect().y;
		int y2 = graphics.getClippingRect().Y2();
		int x2 = graphics.getClippingRect().X2();
		int[] yIndexes = new int[] { y, y, y2, y2 };
		int[] xIndexes = new int[] { 0, x2, x2, 0 };
		graphics.drawShadedFilledPath(xIndexes, yIndexes, null, colors,	null);
	}
}

Ahora agregamos el control a la pantalla en la clase que definimos previamente hereando de MainScreen

public LikeMsnScreen(){
   LabelField label = new LikeMsnLabel();
   add(label);
 }

Y finalmente por ahora nuestro control se ve así:

Es todo por ahora en este post, en los siguientes agregaremos las imágenes, texto y otras características para darle el estilo deseado.

Saludos,

gish@c

Descargar proyecto Eclipse.

Iniciando con Blackberry Java Application Development

En medio de un mundo tecnológico tan voraz, donde el “estar conectado” se está volviendo una necesidad, (en lugar de ser un plus que nos pueda ayudar a nuestras labores diarias), las aplicaciones para dispositivos móviles deben estar a mano en la galería de cualquier proveedor de software, ya que ahora en lugar de preguntar “tienes un sistema contable???” ahora te dicen “y eso lo puedo ver en mi teléfono???”; y, siendo el Blackberry de Research in Motion (al menos en américa latina) el gadget preferido por la gente, empiezo a incursionar en el desarrollo de aplicaciones para esta plataforma 😛 (admito que me da una pereza mental revisar C de Symbian, Android no tiene peso aun en nuestra región, y el IPhone debido a su costo no tiene el nivel masivo de distribución del BB)

A las alturas de éste post ya hice mi primera aplicación con un par de features básicos; pero la idea de ésta entrada es describir las herramientas básicas necesarias para empezar con el desarrollo 😀

Las aplicaciones BB están hechas en Java Micro Edition (ME) más conocido como J2ME, así que efectivamente, no estás equivocado: Java Rules 😀 . Para alegría de todos nosotros RIM provee toda la tecnología necesaria para facilitar el desarrollo: Documentación en el sitio http://docs.blackberry.com/en/developers, el plug-in (estoy utilizando la version 4.5) para poder desarrollar en Eclipse IDE (estoy utilizando la versión 3.4 – Ganymede), simuladores para casi todos los modelos de BB, y el simulador del plan blackberry (Si, ese que hace que funcione el dichoso BB Pin :@ )

Los pasos iniciales no son nada del otro mundo, más que instalar el Eclipse, descomprimir el plugin e instalarlo en la carpeta del IDE, bajar un simulador con una versión de software compatible a la versión del plugin y bajar el simulador de la red de datos; si todo resulta bien en la instalación, en el IDE tendremos las nuevas opciones para crear Blackberry Projects y uno que otro menú adicional para configuración.

En mi caso tengo los simuladores del Pearl y el Curve 8520 que son los más populares, es recomendable también descargar el del Storm ya que hay ciertas características que debemos tener en cuenta para los bb touchscreen.

Sin mas ni mas puedes crear un proyecto nuevo con una clase simple para ejecutar como punto de entrada

import net.rim.device.api.ui.UiApplication;
public class ApplicationStart extends UiApplication {
        public static void main(String[] args) {
		ApplicationStart instanceApp = new ApplicationStart();
		instanceApp.enterEventDispatcher();
	}
}

Seleccionar en las opciones de Run/Debug Configurations la versión del emulador con el cual quieres probar

Iniciar el Simulador del Mobile Data Service (MDS, sí el del dichoso BB Pin) el cual por lo general se coloca como acceso directo en el menú inicio dentro de la carpeta Research in Motion-> Blackberry Email and MDS Services Simulators -> MDS

Ahora, en el proyecto de eclipse: Click derecho -> Run As-> Blackberry simulator.
Si no hay ningún problema se iniciará el emulador (si no tenías, ya tienes un blackberreado jaja xD) y en la ventana de comandos que tiene ejecutando el servicio de MDS puedes ver como se va generando un log de la comunicación; para probar que todo está en orden puedes entrar al BB Messenger, y luego abrir el browser y navegar en tu nuevo blackberry xD

Aquí la imagen navegando en mi blog desde el emulador

Bueno, por ahora es lo básico que debes tener para poder compilar y probar lo que haces sin entrar todavía en la labor de distribuir a aplicación a dispositivos reales, ya que hay que saber ciertos tips 😛

Si tienes ganas de seguir profundizando ya en la parte de desarrollo como tal, las lecturas recomendadas son:

Beginning Blackberry Development – Apress
Advanced Blackberry Development – Apress

Y por supuesto todo lo que el amigo google pueda proveer xD

Saludos,

gish@c