Data Binding en JDAL Swing


Introducción

El término Data Binding se refiere al concepto de procesar las entradas de datos desde la interfaz de usuario a los modelos del dominio de la aplicación de forma dinámica y más o menos automática. JDAL Swing soporta el binding de los controles Swing mediante la interfaz Binder desde la versión 1.0 y la interfaz ControlAccessor desde la versión 1.1.3.

La versión estable actual de jdal-swing 1.2.0 permite escribir aplicaciones swing sin necesidad de escribir ningún codigo de binding de datos. No importa lo complejas que sean la interfaces gráficas de usuario, Ud. sólo tendrá que preocuparse de crear el formulario y llamar en algún momento al método autobind().

Puede ver algunos ejemplos de código de formularios que utilizan jdal-swing en el paquete info.joseluismartin.gefa.ui de la aplicación Gefa (Gestión y Facturación) que he iniciado recientemente y se distribuye bajo licencia GPL.

Práticamente todos los formularios siguen el mismo patrón:

Si el modelo de respaldo del formulario se persiste en base de datos, jdal-core también puede hacer ese trabajo por Ud. Anote el modelo con Hibernate o JPA y declare el servicio jdal para ese modelo en el contexto de la aplicación.

Sigue una pequeña descripción del funcionamiento interno del sistema de binding que utiliza jdal-swing. Aunque realmente no es necesario conocerlo para utilizar la librería, si le será útil en el caso de que necesite utilizar un componente swing que no esté soportado. En ese caso únicamente necesita crear un ControlAccesor para el componente (tal y como se ha hecho en JCalendarControlAccessor) y registrarlo en el contexto de la aplicación.



Binder

La interfaz Binder añade dos métodos a ModelHolder que permiten mover los datos entre el control de la interfaz de usuario y el modelo del dominio:

JDAL delega la obtención del Binder apropiado a la fábrica BinderFactory que contiene un FactoryMethod que asocia un Binder con la clase Java del componente.

La interfaz PropertyBinder es una especialización de Binder que permite asociar un control a una propiedad de un modelo.

Finalmente la clase CompositeBinder representa un agregado de PropertyBinders que comparten el mismo modelo.

Salvo que quiera Ud. extender la librería o bien, utilizar en su aplicación directamente el sistema de Data Binding proporcionado por JDAL, no necesita utilizar directamente estas clases. El Template AbstractView le proporciona el soporte necesario para crear interfaces de usuario Swing con binding automático entre controles y modelos del dominio.


ControlAccessor

La interfaz ControlAccessor define un contrato para acceder a los controles de la interfaz de usuario de forma genérica, es decir, sin el conocimiento específico del tipo de control o componenente. ControlAccessor define cuatro métodos:

De forma similar a los Binders, JDAL delega la obtención del ControlAccessor apropiado para cada componente a una fábrica, ControlAccessorFactory cuya implementación por defecto, ConfigurableControlAccessorFactory utiliza un Map<Class,ControlAccessor> para asociar cada ControlAccessor a una clase Java.

Dada la similitud entre Binders y ControlAccessors, JDAL contiene una fábrica de Binders a partir de la fábrica de ControlAccessors ControlAccessorBinderFactory de modo que basta con implementar la interfaz ControlAccessor para un componente para disponer también del Binder asociado. No obstante, JDAL provee implementaciones de ControlAccessors y Binders tanto para los componentes Swing como para las implementaciones de la interfazView. El motivo es que la introducción de la interfaz ControlAccessor es más reciente y las implementaciones de los Binders se mantienen en la librería por motivos de compatibilidad. La recomendación actual es implementar únicamente los ControlAccessors para sus componentes y obtener los Binders mediante ControlAccessorBinderFactory.


AbtractView

AbstractView es la clase plantilla (Template, GoF) principal para la creación de interfaces de usuario Java que proporciona JDAL. AbstractView soporta actualmente dos formas de realizar el binding de datos entre los controles de la interfaz de usuario y los modelos del dominio:

Además del binding, AbstracView proporciona soporte para las siguientes funcionalidades:

En la mayoría de los casos, sólo necesitamos preocuparnos de crear el componente que contendrá los controles de la interfaz gráfica mediante la implementación del método abstracto JComponent buildPanel() y configurar un validador para disponer de un formulario Swing con todas las funcionalidades anteriores.


AutoBinder


La clase AutoBinder le permite utilizar el binding automático que proporciona JDAL sobre cualquier formulario sin imponer la necesidad de que el formulario extienda de AbstractView.

AutoBinder binder = new AutoBinder(view, model);
binder.update();
...
binder.refresh()


JSR-303

Desde la versión 1.2.0 jdal-swing soporta validación mediante las anotaciones de código JSR-303. Debido a que jdal-swing soporta validación mediante la interfaz Validator de springframework y que desde la versión 3 spring soporta validación mediante JSR-303, utilizar JSR-303 con jdal es inmediato y basta con seguir los siguientes pasos:



Sample

El ejemplo consiste en un formulario que permite editar connexiones a una base de datos. Utilizaremos la clase DBConnection como modelo y la clase DbConnectionForm como formulario. El único código de binding de datos que necesitamos la llamanda a autobind() en el método init() de DBConnectionForm.

AbstractView realizará todo el trabajo de binding por nosotros!.

DbConnection

/**
 * Model for database conections.
 * 
 * @author Jose Luis Martin
 */
public class DbConnection {
 
	private static Log log = LogFactory.getLog(DbConnection.class);
 
	/** database driver info */
	private Database database;
	/** host name */
	private String host;
	/** database port */
	private int port;
	/** database name */
	private String dbName;
	/** user name */
	private String user;
	/** database user password */
	private String password
 
	/** 
	 * Test the database connection 
	 */
	public boolean test() {
		boolean success = false;
		// Try to connect
		try {
	      Class.forName(database.getDriver());
	      Connection conn = DriverManager.getConnection(dbName, user, password); 
	      conn.close(); 
	      success = true;
 
	    } 
	    catch (Exception e) {
	    	log.error(e);
	    }
 
		return success;
	}
 
	/**
	 * @return the database
	 */
 
	public Database getDatabase() {
		return database;
	}
	/**
	 * @param database the database to set
	 */
 
	public void setDatabase(Database database) {
		this.database = database;
	}
 
	/**
	 * @return the host
	 */
	public String getHost() {
		return host;
	}
 
	/**
	 * @param host the host to set
	 */
	public void setHost(String host) {
		this.host = host;
	}
 
	/**
	 * @return the port
	 */
	public int getPort() {
		return port;
	}
 
	/**
	 * @param port the port to set
	 */
	public void setPort(int port) {
		this.port = port;
	}
 
	/**
	 * @return the name
	 */
	public String getDbName() {
		return dbName;
	}
 
	/**
	 * @param name the name to set
	 */
	public void setDbName(String dbName) {
		this.dbName = dbName;
	}
	/**
	 * @return the user
	 */
	public String getUser() {
		return user;
	}
 
	/**
	 * @param user the user to set
	 */
	public void setUser(String user) {
		this.user = user;
	}
 
	/**
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}
 
	/**
	 * @param password the password to set
	 */
	public void setPassord(String password) {
		this.password = password;
	}
}
 

DbConnectionForm

/**
 * Database Connection Form
 * 
 * @author Jose Luis Martin
 */
public class DbConnectionForm  extends AbstractView<DbConnection> 
	implements ActionListener {
 
	// Swing controls. We are using autobinding abstract view feature, so
	// we need to use the model properties as control names.
	private JComboBox database = new JComboBox();
	private JTextField port = new JTextField();
	private JTextField host = new JTextField();
	private JTextField dbName = new JTextField();
	private JTextField user = new JTextField();
	private JPasswordField password = new JPasswordField();
	private JButton test;
	private JLabel testResult = new JLabel(" ");
	private List<Database> databases = new ArrayList<Database>();
 
	/**
	 * @param dbConnection
	 */
	public DbConnectionForm(DbConnection dbConnection) {
		super(dbConnection);
		test = new JButton(getMessage("DbConnectionForm.test"));
		test.addActionListener(this);
 
 
	}
 
	/**
	 * Init method, called by container after property sets.
	 */
	public void init() {
		// Call autobind to create control bindings. 
		// This is the only bindign code that we need !
		autobind();
	}
 
	/**
	 * Override build panel and create the JComponent that contain the view controls.
	 */
	@Override
	protected JComponent buildPanel() {
		BoxFormBuilder fb = new BoxFormBuilder(FormUtils.createTitledBorder(getMessage("DbConnectionForm.title")));
		fb.setDebug(true);
		fb.row();
		fb.startBox();
		fb.setHeightFixed(true);
		fb.row()
		fb.add(getMessage("DbConnectionForm.database"),database);
		fb.row();
		fb.add(getMessage("DbConnectionForm.host"),host);
		fb.row();
		fb.add(getMessage("DbConnectionForm.port"), port);
		fb.row();
		fb.add(getMessage("DbConnectionForm.dbName"), dbName);
		fb.row();
		fb.add(getMessage("DbConnectionForm.user"), user);
		fb.row();
		fb.add(getMessage("DbConnectionForm.password"), user);
		fb.endBox();
		fb.row();
		fb.startBox();
		fb.row();
		box.add(c);
		fb.row();
		box.add(test);
		fb.row();
		box.add(testResult);
		fb.endBox();
 
		return fb.getForm();
	}
 
	/**
	 * Test database connection and show success/failed message
	 */
	public void actionPerformed(ActionEvent e) {
		if (getModel().test()) {
			testResult.setText(getMessage("DbConnectionForm.success"));
		}
		else {
			testResult.setText(getMessage("DbConnectionForm.failed"));
		}
	}
 
	/**
	 * @return the databases
	 */
	public List<Database> getDatabases() {
		return databases;
	}
 
	/**
	 * @param databases the databases to set
	 */
	public void setDatabases(List<Database> databases) {
		this.databases = databases;
	}
 
	/**
	 * Main test method to show the form
	 * @param args none
	 */
	public static void main(String[] args) {
		ViewDialog<DbConnection> d = new ViewDialog<DbConnection>();
		DbConnectionForm dbf = new DbConnectionForm(new DbConnection());
		dbf.setControlAccessorFactory(new ConfigurableControlAccessorFactory());
		dbf.init();
		// wrap the view in a ViewDialog to show it. ViewDialog will call view.update() on
		// on accept and dispose on cancel.
		d.setView(dbf);
		d.init();
		d.setVisible(true);
	}
 
}
 


Más Información


Puede obtener más información sobre JDAL en http://www.jdal.org (inglés) o consultar la documentación de JDAL en esta misma web que describe la aplicación de ejemplo.

Si lo desea, puede realizar sus preguntas o comentarios en foro de soporte de JDAL, trataré de responderle tan pronto como me sea posible.