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

Nuevo Portal Javahispano

Desde hoy se estrena nuevo portal en javahispano.org, se ve bastante bien; entre los proyectos que se buscan fomentar es la de las subcomunidades dentro del sitio; se estrenan nuevas seciones como las de Android y Certificación, y los grupos por países; así que si estas animado a participar únete y aporta al ecosistema tecnológico del que todos somos parte.

Db4o Series – Collections Basics

Despues del DVP 2010 y la gorra xD tenia que dedicarle un post a db4o jeje.
Db4o puede manejar la persistencia de las listas de una manera natural, incluso dándonos incluso colecciones especiales con implementación para manejar persistencia y activación automática (Transparent Persistence y Transparent Activation). En este post voy a tratar de explicar las bases de como trabajar con colecciones y las operaciones comunes de base de datos con estas estructuras, no es el objetivo mostrar como optimizar el uso de colecciones o utilizar persistencia/activacion automática, sino comprender lo básico con la configuración necesaria para que funcione, es decir sin tunning.
Teniendo el siguiente modelo:

Donde la clase Project es la que tiene una lista de recursos, pudiendo ser del tipo Developer o ProjectLeader, vamos a manejar las operaciones en la coleccion de manera normal y vamos a dejar que db4o se encargue del resto 🙂
Las consideraciones que debemos tener para estas estructuras son:

  • Si no estamos utilizando Transparent Persistence debemos configurar la actualizacion en cascada (cascade on update) si vamos a modificar las propiedades de los objetos dentro de la colección (para este ejemplo vamos a hacerlo y tendremos que activar este feature).
configuration.common().objectClass(Project.class).
     cascadeOnUpdate(true);
  • No necesitamos código adicional para grabar los objetos agregados a las colecciones
  • Cuando se elimina un objeto de la lista y se graban los cambios, el objeto no es eliminado de la base de datos, simplemente la referencia es eliminada de la lista.
  • Cuando se elimina la referencia de un objeto de la base de datos y este estaba referenciado por una lista, se va la referencia a NULL ya que la lista nunca se entero que el objeto fue eliminado de la base de datos, tenemos que tener mucho cuidado para tener integridad en los objetos, una manera de solventar este tema es utilizando los callbacks y teniendo un diseño en ambas direcciones (el objeto de la colección tiene una referencia al objeto padre y se puede eliminar la referencia cuando se detecta que va a ser eliminado de la base de datos)

Con el modelo presentado anteriormente, queremos crear un proyecto con varios desarrolladores:

Project db4o = new Project("Db4Objects");
// Agregamos los recursos a la colección
db4o.getResources().add(new ProjectLeader("John Smith", 95000));
db4o.getResources().add(new Developer("Anne Lu", 35000));
db4o.getResources().add(new Developer("Marie Perez", 32000));
db4o.getResources().add(new Developer("Carl", 33000));
db4o.getResources().add(new Developer("Rodrigo", 35000));
container.store(db4o);

Para cualquiera que haya programado en Java le son natural las 6 primera lineas, solo la ultima linea corresponde al store de db4o que se encarga de guardar en la base de datos los objetos creados, todo el árbol de objetos desde el proyecto hasta los recursos, debido a la configuración de almacenamiento en cascada.

Así mismo podemos actualizar las listas añadiendo o removiendo objetos, o actualizando propiedades de los objetos en la colección.
Esto es lo que tiene la base de datos antes de hacer algún cambio:

Project: Db4Objects
John Smith – 95000.0
Anne Lu – 35000.0
Marie Perez – 32000.0
Carl – 33000.0
Rodrigo – 35000.0

Hacemos las actualizaciones

Query query = container.query();
query.constrain(Project.class);
//Consultamos el proyecto db4o para obtener el objeto y modificarlo
query.descend("name").constrain("Db4Objects");
ObjectSet result = query.execute();
Project db4o = (Project) result.next();
// Removemos un recurso del proyecto
db4o.getResources().remove(3);
// Actualizamos el salario de un recurso
Resource developer = db4o.getResources().get(1);
developer.setSalary(developer.getSalary() + 1000);
// Agregamos un nuevo desarrollador al proyecto
db4o.getResources().add(new Developer("Adam Pereiro", 50000));
//Guardamos los cambios
container.store(db4o);

Y el resultado es el siguiente:

Project: Db4Objects
John Smith – 95000.0
Anne Lu – 36000.0
Marie Perez – 32000.0
Rodrigo – 35000.0
Adam Pereiro – 50000.0

Nada complicado, ahora si queremos obtener el objeto principal a partir de un elemento de la colección, es decir si quisiéramos saber el proyecto de un recurso en especifico podemos utilizar Native Queries por ejemplo. Para el caso si tenemos varios proyectos y queremos obtener solo los que tienen recursos con un salario menor o igual a 30000.
Agregamos 2 proyectos adicionales

Project eclipseIDE = new Project("EclipseIDE");
eclipseIDE.getResources().add(new ProjectLeader("Carlos Field", 85000));
eclipseIDE.getResources().add(new Developer("Gian Lopez", 40000));
eclipseIDE.getResources().add(new Developer("Tania Mellas", 28000));
eclipseIDE.getResources().add(new Developer("Sara Parker", 37000));
eclipseIDE.getResources().add(new Developer("Neil Smith", 35000));

Project netBeans = new Project("Netbeans");
netBeans.getResources().add(new ProjectLeader("Karen Bowler", 23000));
netBeans.getResources().add(new Developer("Juan Perez", 18000));
container.store(netBeans);
container.store(eclipseIDE);

El query seria algo asi:

final double maxSalary = 30000;
List<Project> projects = container.query(new Predicate<Project>() {
 public boolean match(Project project) {
  for (Resource resource : project.getResources()) {
   if (resource.getSalary() <= maxSalary)
    return true;
   }
   return false;
  }
});

Lo cual nos daría como resultado:
…Projects with salary <= 30000…
Project: EclipseIDE
Project: Netbeans

Como podemos ver no tenemos que preocuparnos por nada mas que lo que en este caso Java provee con el api de Collections y tan solo con un store lo hacemos persistir en la base de datos! En un post posterior mejorare el modelo para que tenga Activación y Persistencia Automática y el manejo del borrado de referencias.

Saludos,
gish@c

Descargas (Proyecto Eclipse para Db4o7.12)

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

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

Edición en JTable con Db4o

Una de las ventajas de una base de datos orientada a objetos como Db4o, es la simplicidad al momento de trabajar con modelos de objetos un poco mas complejos. Este post es para mostrar como usando la flexibilidad de Java junto con Db4o (v7.4) se puede hacer un mantenimiento en un grid, al final el resultado se verá más o menos asi:

Como base tome las clases Car y Pilot que vienen en el tutorial de Db4o, y como se puede ver tiene un combo de seleccion de pilotos ya que es lo minimo que se requiere cuando se tiene un modelo de objetos así.
Lo importante aqui es sacarle el provecho al sistema de referencias de la base de datos, ya que al estar editando los objetos en el grid solo basta con hacer un update a la referencia que es esta editando para que se guarden los cambios, Las clases del proyecto se pueden ver en el gráfico a la izquierda.

El proyecto está con Netbeans 6.5, y en el pkg com.sample.objects estan los objetos mas importantes, los del pkg com.sample son los que agrega por default el nb para proyectos java desktop.
Las clases DBManager, CarDB, y PilotDB son las que se encargan de las consultas y actualizaciones a la base de datos; las clases importantes en las que entraremos en mas detalle son las Model ya que son las clases que se encargan de todo detras del JTable y es donde pondremos lo necesario para persistir cualquier modificación que hagamos directo en la tabla.
Lo principal es comenzar con el modelo que vamos a proveer al JTable para que sepa con lo que está lidiando, asi que extendemos de AbstractTableModel, y se sobrecargan los métodos requeridos, la sobrecarga clave para el ejemplo es del método setValueAt ya que para éste caso luego de establecer el valor a la propiedad actualizamos el objeto que fue editado:

public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
super.setValueAt(aValue, rowIndex, columnIndex);
Car car = getDatasource().get(rowIndex); //Aqui obtenemos la instancia del objeto que está siendo editado
switch (columnIndex) {
case 0:
car.setModel(aValue.toString());
break;
case 1:
car.setColor(aValue.toString());
break;
case 2:
car.setPilot((Pilot) aValue);
break;
DBManager.getInstance().getCarDB().storeCar(car);
}

Básicamente, fuera del código en Java para hacer edición en grid es el único llamado a realizar para persistir las modificaciones. Para que esto sea posible las clases model tienen una lista que utlizan como datasource


public class CarTableModel extends AbstractTableModel {
private List datasource;
.
.

public class PilotComboBoxModel extends AbstractListModel implements ComboBoxModel {
private Pilot current;
private List datasource;
.
.
.

En la clase EditPane se encuentra la carga de las listas con lo que se obtiene de la base, los objetos se mantienen en el sistema de referencias de Db4o:

//Se carga la lista de pilotos en el combo para el grid</span>
PilotComboBoxModel cmbModel = new PilotComboBoxModel(DBManager.getInstance().getPilotDB().getPilots());
cmbPilots = new JComboBox(cmbModel);
SetTableModel(cmbPilots);

private void SetTableModel(JComboBox cmbPilots) {
//Se carga la lista de carros a mostrar en la tabla
carTableModel = new CarTableModel(DBManager.getInstance().getCarDB().getCars());
//Al jtable carTable le seteamos el model y el editor para la columna de pilotos
carTable.setModel(carTableModel);
carTable.getColumnModel().getColumn(2).setCellEditor(new DefaultCellEditor(cmbPilots));
}

El código del ejemplo se lo pueden descargar aquí y Db4o por aca.

Para usar el ejemplo luego de ejecutar por primera vez el proyecto hay que comentar el llamado a la linea que llena la base de datos en la clase EditInTableDb4oApp.

public static void main(String[] args) {

DBManager.PopulateDatabase();// Comentar despues de primera ejecucion
launch(EditInTableDb4oApp.class, args);
}

En un siguiente post le agrego un par de cosas al ejemplo para cumplir con el CRUD, bueno… el Read es una busqueda que no es para el ejemplo asi que en realidad sera un CUD 😛

Saludos,
gish@c