JDAL Vaadin

JDAL Vaadin es un módulo de JDAL que facilita el uso de JDAL con el framework Vaadin. El objetivo principal de JDAL es la reutilización del código común en aplicaciones de bases de datos mediante configuración y este objetivo se ha cumplido bastante bien en JDAL Vaadin hasta el punto que puede obtener las tablas páginables y los formularios de edición de objetos únicamente a partir de la configuración del contexto Spring. Mediante JDAL Vaadin puede crear una tabla con orden, filtrado, paginación y edición de modelos en menos de una hora simplemente configurando en el contexto de la aplicación.

Antes de ver el ejemplo, veámos algunas de las clases principales de JDAL Vaadin:


Aplicación de Ejemplo

Para la aplicación de ejemplo he reutilizado la capa de integarción del ejemplo de JDAL que nos proporciona los tres modelos persistentes: Book, Category y Author y el constructor de Criterias BookCriteriaBuilder que en este caso se ha implementado de nuevo utilizando el soporte de JPA que se ha añadido a JDAL Core en la versión 1.1.3.

JPA BookCriteriaBuilder:

/**
 * JPA Criteria Builder for Book Filter
 * 
 * @author Jose Luis Martin
 */
public class BookCriteriaBuilder implements  JpaCriteriaBuilder<Book> {
 
	/**
	 * {@inheritDoc}
	 */
	public CriteriaQuery<Book> build(CriteriaQuery<Book> criteria, CriteriaBuilder cb, Filter filter) {
		BookFilter f = (BookFilter) filter;
 
		Root<Book> root = criteria.from(Book.class);
		Path<Author> author = root.<Author>get("author");
		List<Predicate> predicates = new ArrayList<Predicate>();
 
		if (StringUtils.isNotEmpty(f.getName()))
			predicates.add(cb.like(root.<String>get("name"), f.getName()));
 
		if (f.getCategory() != null)
			predicates.add(cb.equal(root.<Category>get("category"), f.getCategory()));
 
		if (f.getBefore() != null)
			predicates.add(cb.lessThanOrEqualTo(root.<Date>get("publishedDate"), f.getBefore()));
 
		if (f.getAfter() != null)
			predicates.add(cb.greaterThanOrEqualTo(root.<Date>get("publishedDate"), f.getAfter()));
 
		if (StringUtils.isNotEmpty(f.getAuthorName()))
			 predicates.add(cb.like(author.<String>get("name"), f.getAuthorName()));
 
		if (StringUtils.isNotEmpty(f.getAuthorSurname()))
			predicates.add(cb.like(author.<String>get("surname"), f.getAuthorSurname()));
 
		if (StringUtils.isNotEmpty(f.getIsbn()))
			predicates.add(cb.like(root.<String>get("isbn"), f.getIsbn()));
 
 
		if (predicates.size() > 0)
			criteria.where(cb.and(predicates.toArray(new Predicate[]{})));
 
		return criteria;
	}
}
 

Sigue la configuración del contexto de Spring. Esta vez en lugar de utilizar HibernateDao para crear los DAOs utilizamos la implementación que utiliza JPA, JpaDao:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	default-init-method="init"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
	<context:annotation-config/>
 
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:jdbc.properties</value>
			</list>
		</property>
	</bean>
 
	<!-- DataSource -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driverClassName}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
 
		<property name="acquireIncrement" value="3" />
		<property name="minPoolSize" value="2" />
		<property name="maxPoolSize" value="15" />
		<property name="maxIdleTime" value="5" />
		<property name="numHelperThreads" value="5" />
 
		<property name="idleConnectionTestPeriod" value="10" />
		<property name="autoCommitOnClose" value="false" />
		<property name="preferredTestQuery" value="select 1;" />
		<property name="testConnectionOnCheckin" value="true" />
 
		<property name="checkoutTimeout" value="60000" />
	</bean>
 
     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource"/>
 
	  <property name="jpaDialect">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
     </property>
     <property name="jpaVendorAdapter">
       <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
         <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
       </bean>
     </property>
    </bean>
 
 
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
      <property name="dataSource" ref="dataSource"/>
    </bean>
 
  <!-- bean post-processor for JPA annotations -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
 
   <bean id="categoryDao" class="info.joseluismartin.dao.jpa.JpaDao">
   		<constructor-arg value="org.jdal.samples.model.Category"/>
   </bean>
 
   <!-- Daos -->
   <bean id="daoFactory" class="info.joseluismartin.dao.jpa.JpaDaoFactory" />
 
   <bean id="bookDao" class="info.joseluismartin.dao.jpa.JpaDao">
   		<constructor-arg value="org.jdal.samples.model.Book"/>
   		<property name="criteriaBuilderMap">
   			<map>
   				<entry key="bookFilter" value-ref="bookCriteriaBuilder"/>
   			</map>
   		</property>
   </bean>
 
   <bean id="bookCriteriaBuilder" class="org.jdal.samples.dao.filter.BookCriteriaBuilder"/>
 
   <!-- Simple Dao -->
   <bean id="simpleDao" class="info.joseluismartin.dao.jpa.JpaDao" />
 
   <!-- Persistent Services -->
   <bean id="persistentServiceFactory" class="info.joseluismartin.service.PersistentServiceFactory" />
 
   <bean id="bookService" class="info.joseluismartin.logic.PersistentManager">
   		<property name="dao" ref="bookDao"/>
   </bean>
 
	<!-- Simple PersistentService -->
	<bean id="persistentService" class="info.joseluismartin.logic.PersistentManager">
		<property name="dao" ref="simpleDao" />
	</bean>   
 
    <bean id="categoryService" class="info.joseluismartin.logic.PersistentManager">
   		<property name="dao" ref="categoryDao"/>
   </bean>
 
   <!-- UI Widgets -->
 
   <bean id="guiFactory" class="info.joseluismartin.vaadin.ui.ApplicationContextGuiFactory" />
 
   <bean id="comboBoxFieldBuilder" class="info.joseluismartin.vaadin.ui.form.ComboBoxFieldBuilder">
  		<property name="persistentServiceFactory" ref="persistentServiceFactory"/>
   </bean>
 
   <!-- Create and configure Fields by means of JPA annotations -->
   <bean id="formFieldFactory" class="info.joseluismartin.vaadin.ui.form.AnnotationFieldFactory" >
   		<property name="classBuilderMap">
					<map>
				    	<entry key="org.jdal.samples.model.Category" value-ref="comboBoxFieldBuilder" />
				    	<entry key="org.jdal.samples.model.Author" value-ref="comboBoxFieldBuilder" />
				    </map>
		</property>
		<property name="fieldProcessors">
			<list>
			<!-- size category combo to 150px width, is to large by default -->
				<bean class="info.joseluismartin.vaadin.ui.form.SizeFieldProcessor">
					<property name="widths">
						<map>
							<entry key="category" value="150px"/>
						</map>
					</property>
				</bean>
			</list>
		</property>
   </bean>
 
	<bean id="bookPageableTable" class="info.joseluismartin.vaadin.ui.table.PageableTable"
		scope="prototype">
		<property name="table" ref="table" />
		<property name="paginator" ref="paginator" />
		<property name="service" ref="bookService" />
		<property name="entityClass" value="org.jdal.samples.model.Book"/>
		<property name="formFieldFactory" ref="formFieldFactory" />
		<property name="editor" value="bookEditor" />
		<property name="filterEditor"  value="bookFilterEditor" />
		<property name="beanFilter">
			<bean class="org.jdal.samples.dao.filter.BookFilter" />
		</property>
		<property name="guiFactory" ref="guiFactory" />
		<property name="actions" ref="actionList" />
	</bean>
 
	<!--  Action List for table buttons -->
	<util:list id="actionList" scope="prototype">
		<bean class="info.joseluismartin.vaadin.ui.table.AddAction"  p:icon="images/table/filenew.png"
			p:description="Add new item" />
		<bean class="info.joseluismartin.vaadin.ui.table.RemoveAction" p:icon="images/table/edit-delete.png"
			p:description="Delete selected items" />
		<bean class="info.joseluismartin.vaadin.ui.table.FindAction" p:icon="images/table/edit-find.png"
			p:description="Apply filter" />
		<bean class="info.joseluismartin.vaadin.ui.table.RefreshAction" p:icon="images/table/reload.png"
			p:description="Refresh current page" />
 
	</util:list>
 
 
	<bean id="table" class="info.joseluismartin.vaadin.ui.table.ConfigurableTable"
		scope="prototype">
 
		<property name="selectable" value="true" />
		<property name="multiSelect" value="true" />
 
		<property name="columns">
			<list value-type="info.joseluismartin.vaadin.ui.table.Column">
				<bean class="info.joseluismartin.vaadin.ui.table.Column">
					<property name="name" value="id" />
					<property name="displayName" value="ID" />
					<property name="width" value="60" />
					<property name="align" value="c" />
				</bean>
				<bean class="info.joseluismartin.vaadin.ui.table.Column">
					<property name="name" value="name" />
					<property name="displayName" value="Title" />
					<property name="width" value="300" />
					<property name="align" value="b" />
				</bean>
				<bean class="info.joseluismartin.vaadin.ui.table.Column">
					<property name="name" value="author" />
					<property name="displayName" value="Author" />
					<!-- really don't needed, HibernateDao figures them -->
					<property name="sortPropertyName" value="author.name"/>
					<property name="width" value="150" />
					<property name="align" value="b" />
				</bean>
				<bean class="info.joseluismartin.vaadin.ui.table.Column">
					<property name="name" value="category" />
					<property name="displayName" value="Category" />
					<!-- really don't needed, HibernateDao figures them -->
					<property name="sortPropertyName" value="category.name"/>
					<property name="width" value="200" />
					<property name="align" value="b" />
				</bean>
				<bean class="info.joseluismartin.vaadin.ui.table.Column">
					<property name="name" value="isbn" />
					<property name="displayName" value="ISBN" />
					<property name="width" value="150" />
					<property name="align" value="b" />
				</bean>
				<bean class="info.joseluismartin.vaadin.ui.table.Column">
					<property name="name" value="publishedDate" />
					<property name="displayName" value="Published Date" />
					<property name="width" value="150" />
					<property name="align" value="b" />
					<property name="propertyEditor">
						<bean class="org.springframework.beans.propertyeditors.CustomDateEditor">
							<constructor-arg>
								<bean class="java.text.SimpleDateFormat">
									<constructor-arg value="MM/dd/yyyy" />
								</bean>
							</constructor-arg>
							<constructor-arg value="true" />
						</bean>
					</property>
				</bean>
			</list>
		</property>
	</bean>
 
	<bean id="paginator" class="info.joseluismartin.vaadin.ui.table.VaadinPaginator"
		scope="prototype">
		<property name="first" ref="paginatorFirstButton" />
		<property name="last" ref="paginatorLastButton" />
		<property name="next" ref="paginatorNextButton" />
		<property name="previous" ref="paginatorPreviousButton" />
		<property name="pageSizes" value="10,20,30,40,50,100,200,All" />
	</bean>
 
	<bean id="paginatorFirstButton" class="com.vaadin.ui.Button" scope="prototype">
		<property name="icon" value="images/table/go-first.png" />
	</bean>
 
	<bean id="paginatorLastButton" class="com.vaadin.ui.Button" scope="prototype">
		<property name="icon" value="images/table/go-last.png" />
	</bean>
 
	<bean id="paginatorNextButton" class="com.vaadin.ui.Button" scope="prototype">
		<property name="icon" value="images/table/go-next.png" />
	</bean>
 
	<bean id="paginatorPreviousButton" class="com.vaadin.ui.Button" scope="prototype">
		<property name="icon" value="images/table/go-previous.png" />
	</bean>
 
	<bean id="buttonPanel" class="com.vaadin.ui.HorizontalLayout"
		scope="prototype" />
 
 
	<!-- Book Form Editor -->
	<bean id="bookEditor" class="com.vaadin.ui.Form" scope="prototype">
		<property name="visibleItemProperties" value="#{ { 'name', 'author','category','isbn', 'publishedDate' } }" />
		<property name="width" value="500"  />
		<property name="formFieldFactory">
			<bean class="info.joseluismartin.vaadin.ui.form.AnnotationFieldFactory">
				<property name="fieldProcessors">
					<list>
						<bean class="info.joseluismartin.vaadin.ui.form.SizeFieldProcessor">
							<property name="defaultWidth" value="400px" />
						</bean>
					</list>
				</property>
			</bean>
		</property>
	</bean>
 
	<!-- The book Filter Editor -->
	<bean id="bookFilterEditor" class="com.vaadin.ui.Form" scope="prototype">
		<property name="visibleItemProperties" value="#{ {'name', 'authorName','authorSurname', 'category', 'isbn', 'before', 'after' } }" />
		<property name="layout">
			<bean class="com.vaadin.ui.HorizontalLayout" p:spacing="true"/>
		</property>	
		<property name="formFieldFactory" ref="formFieldFactory" />
	</bean>
 
	<bean id="dataSourceTable" class="info.joseluismartin.vaadin.ui.table.ConfigurableTable" parent="table"
		scope="prototype">
		<property name="containerDataSource" ref="bookContainerDataSource"/>
	</bean>
 
	<bean id="bookContainerDataSource" class="info.joseluismartin.vaadin.data.ContainerDataSource" scope="prototype">
		<constructor-arg value="org.jdal.samples.model.Book"/>
		<property name="service" ref="bookService"/>
	</bean>
 
	<bean id="bookFilterView" class="org.jdal.samples.vaadin.BookFilterView" scope="prototype">
		<property name="categoryService" ref="categoryService"/>
		<property name="visibleProperties" value="#{ { 'name', 'author','category','isbn', 'publishedDate' } }" />
	</bean>
 
 
	<!-- Property Editors -->
	<bean id="customEditorConfigurer"
		class="org.springframework.beans.factory.config.CustomEditorConfigurer">
		<property name="customEditors">
			<map>
				<entry key="com.vaadin.terminal.Resource">
					<bean class="info.joseluismartin.vaadin.beans.VaadinResourcePropertyEditor" />
				</entry>
			</map>
		</property>
	</bean>
 
 
	<!-- Tx Advice -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!-- the transactional semantics... -->
    <tx:attributes>
      <!-- all methods starting with 'get' and 'load' are read-only -->
      <tx:method name="get*" read-only="true"/>
      <tx:method name="load*" read-only="true"/>
      <!-- other methods use the default transaction settings -->
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>
 
   <aop:config>
    <!-- Make all methods on package service transactional  -->
     <aop:pointcut id="serviceOperation" 
     	expression="execution(* info.joseluismartin.service.PersistentService.*(..))"/>
     <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
  </aop:config>
 
</beans>
 
 

El único código java que necesitamos escribir es la clase Application de Vaadin que contendrá la tabla paginable:


/*
 * Copyright 2009-2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jdal.samples.vaadin;
 
import info.joseluismartin.beans.AppCtx;
import info.joseluismartin.vaadin.ui.table.PageableTable;
 
import org.jdal.samples.model.Book;
 
import com.vaadin.Application;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.Reindeer;
 
/**
 * Simple Test for PageableTable and ContainerDataSource.
 * 
 * @author Jose Luis Martin
 */
@SuppressWarnings("serial")
public class TestApp extends Application {
 
	@SuppressWarnings("unchecked")
	@Override
	public void init() {
		Window mainWindow = new Window("JDAL Vaadin Sample");
		Label title = new Label("JDAL Vaadin Sample Application");
		title.setStyleName(Reindeer.LABEL_H1);
		PageableTable pageableTable = (PageableTable<Book>) AppCtx.getInstance().getBean("bookPageableTable");
		Panel panel = new Panel("Table with external paginator and server side paging and sorting");
		panel.addComponent(pageableTable);
		VerticalLayout layout = new VerticalLayout();
		layout.setSpacing(true);
                layout.addComponent(title);
		layout.addComponent(panel);
		mainWindow.setContent(layout);
		setMainWindow(mainWindow);
	}
}
 

Si no se muestra a continuación puede ver el ejemplo aquí. Puede descargar el código fuente del ejemplo de jdal-vaadin-jpa-sample.tgz

Para editar un libro, haga 'doble click' sobre el, para borrar uno o varios libros primero seleccionelos y luego pulse en la papelera. La base de datos se restaura cada diez minutos, no se extrañe si los libros que acaba de eliminar reaparecen o desaparecen los que ha añadido.


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.

He utilizado jdal-vaadin para realizar la aplicación de administración de Tila (Tiles on Lan) un servidor de caché de tiles. La aplicación de administración se encuentra bajo el paquete info.joseluismartin.gtc.admin y puede servirle de ejemplo. Tila se distribuye bajo licencia Apache 2.

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