sábado, 17 de octubre de 2015

Implementación de org.vaadin.addons.lazyquerycontainer 7.4.0.1


Otra vez contenedores vagos en Grails y Vaadin! Yeah!

Usando el plugin que ahora forma parte del Framework Vaadin (Vaadin Plugin Lazy Query Container) ahora podemos crear esas tablas con cientos de miles de filas que necesitan ser paginadas de una forma relativamente fácil y práctica.

Debido a que ese plugin usa el estándar JAVA para poder trabajar, se nos dificulta un poco usar en Grails debido a que no vemos de frente la DB, sino que lo hacemos mediante GORM y nos pone a pensar un poco en como hacemos la implementación.

Para mi caso particular anexo el source directo, pero pueden verlo igual en el repositorio del TestLazyContainerVaadinGrails con un ejemplo de Demo2 escrito en la parte Groovy del proyecto. Cabe destacar que la implementación esta escrita puramente en JAVA porque la compilación dinámica presenta problemas para este tipo de abstracción. Empecemos!

Lo primero es entender como funciona el nuevo plugin y si ya han usado el anterior más o menos tendrán idea, pero explico.

Se tiene el contenedor (Container) que es el recipiente de la data y este a su vez esta unido a un creador de consultas (QueryFactory) y así mismo tenemos la consulta en sí (Query) que es creada por el creador de consultas.

Expliquemos de forma simple como funcionan estos 3 componentes:

  • El contenedor (Container): hace lo que dice su nombre y es el que crea el enlace de la tabla, la data y el servidor. Este a su vez maneja las columnas que se mostrarán.
  • El creador de consultas (QueryFactory): es el encargado de manejar como se va a machear la data de forma que coincida con las columna del contenedor (Container)
  • La consulta (Query): es la implementación misma de donde se buscan los datos es el conector real entre servidor y cliente.

Una vez claros con los conceptos entonces procedemos a crear nuestras implementaciones, pero por motivos de practicidad y de mantener un código más simple y rápido en el desarrollo de las UI con Grails y Vaadin yo mismo creé una abstracción más compleja, pero simple de implementar con tal de facilitar la creación (que conste que aún se puede usar Reflection para crear de forma dinámica las columnas y quizás mejorar la forma en que la data en memoria no se pierda para reducir aún más las consultas a la DB, pero ese es su problema, puesto no les puedo hacer la vida tán fácil sin que paguen :P jijiji) y ahora verán un ejemplo de ella.

Que conste que son libres de implementar directamente las interfaces sin la abstracción independientemente a cada clase en particular, pero notarán que ahorro muchas lineas de código.

Primero vamos a crear una interfaz que nos permita tener las consultas separadas del código de los contenedores, puesto ciertos elementos cambiantes en la UI modifican la consulta y sería tedioso llevarlos a clases independientes que en sí no deben saber cómo les llega la DATA, sino que le llegue.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.List;
/**
 *  Created by aluis on 10/8/15.
 */
public interface LazyQuery<BEANTYPE> {
    /**
     * Get Size by a Query
     */
    int getSize();
    /**
     * Get items to view on the page using the same model of the container.
     */
    List<BEANTYPE> getItemsIds(int startIndex, int numberOfIds);
    /**
     * Get Size for manual filter.
     */
    int getFilteredSize();
    /**
     * Get items to view on the page for manual filter using the same model of the container.
     */
    List<BEANTYPE> getFilteredItemsIds(int startIndex, int numberOfIds);
}
import java.util.List;

/**
 *  Created by aluis on 10/8/15.
 */
public interface LazyQuery<BEANTYPE> {

    /**
     * Get Size by a Query
     */
    int getSize();

    /**
     * Get items to view on the page using the same model of the container.
     */
    List<BEANTYPE> getItemsIds(int startIndex, int numberOfIds);

    /**
     * Get Size for manual filter.
     */
    int getFilteredSize();

    /**
     * Get items to view on the page for manual filter using the same model of the container.
     */
    List<BEANTYPE> getFilteredItemsIds(int startIndex, int numberOfIds);
}

Una vez creada la interfaz procedemos a crear nuestra consulta (Query)

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import com.vaadin.data.Item;
import org.vaadin.addons.lazyquerycontainer.Query;
import java.util.ArrayList;
import java.util.List;
/**
 *  Created by aluis on 10/8/15.
 */
public abstract class BaseQuery<BEANTYPE> implements Query {
    protected BaseQueryFactory<BEANTYPE> baseQueryFactory;
    public boolean filtered = false;
    protected BaseQuery(BaseQueryFactory<BEANTYPE> baseQueryFactory) {
        this.baseQueryFactory = baseQueryFactory;
    }
    @Override
    public int size() {
        if (filtered) {
            return baseQueryFactory.getLazyQuery().getFilteredSize();
        }
        return baseQueryFactory.getLazyQuery().getSize();
    }
    @SuppressWarnings("Convert2streamapi")
    @Override
    public List<Item> loadItems(int startIndex, int numberOfIds) {
        List<Item> items = new ArrayList<>();
        List<BEANTYPE> allData;
        if (filtered) {
            allData = baseQueryFactory.getLazyQuery().getFilteredItemsIds(startIndex, numberOfIds);
        } else {
            allData = baseQueryFactory.getLazyQuery().getItemsIds(startIndex, numberOfIds);
        }
        for (BEANTYPE data : allData) {
            items.add(baseQueryFactory.constructItem(data));
        }
        return items;
    }
    @Override
    public void saveItems(List<Item> list, List<Item> list1, List<Item> list2) {
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean deleteAllItems() {
        throw new UnsupportedOperationException();
    }
    @Override
    public Item constructItem() {
        return baseQueryFactory.constructItem();
    }
}
import com.vaadin.data.Item;
import org.vaadin.addons.lazyquerycontainer.Query;

import java.util.ArrayList;
import java.util.List;

/**
 *  Created by aluis on 10/8/15.
 */
public abstract class BaseQuery<BEANTYPE> implements Query {

    protected BaseQueryFactory<BEANTYPE> baseQueryFactory;
    public boolean filtered = false;

    protected BaseQuery(BaseQueryFactory<BEANTYPE> baseQueryFactory) {
        this.baseQueryFactory = baseQueryFactory;
    }

    @Override
    public int size() {
        if (filtered) {
            return baseQueryFactory.getLazyQuery().getFilteredSize();
        }
        return baseQueryFactory.getLazyQuery().getSize();
    }

    @SuppressWarnings("Convert2streamapi")
    @Override
    public List<Item> loadItems(int startIndex, int numberOfIds) {
        List<Item> items = new ArrayList<>();
        List<BEANTYPE> allData;
        if (filtered) {
            allData = baseQueryFactory.getLazyQuery().getFilteredItemsIds(startIndex, numberOfIds);
        } else {
            allData = baseQueryFactory.getLazyQuery().getItemsIds(startIndex, numberOfIds);
        }
        for (BEANTYPE data : allData) {
            items.add(baseQueryFactory.constructItem(data));
        }
        return items;
    }

    @Override
    public void saveItems(List<Item> list, List<Item> list1, List<Item> list2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean deleteAllItems() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item constructItem() {
        return baseQueryFactory.constructItem();
    }
}

Ya con nuestra consulta (Query) hecha podemos crear nuestro creador de consultas (QueryFactory). En este tenemos 2 atributos fundamentales para el rendimiento de nuestro sistema, puesto elegí mantener el objeto en sí como parte de la DATA que viaja entre el server y el cliente y a su vez como identificador único de los elementos y también la cantidad de elementos a traer para mantener en memoria.

Estos elementos son:

  • public static final String OBJ = "OBJ"; que es la forma en que uso para mantener la UI simple en cuanto al objeto en sí.
  • public static final int BATCH_GRID_SIZE = 20; y este es el indicador de cuantos elementos va a buscar por solicitud.


?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import com.vaadin.data.Item;
import com.vaadin.data.util.ObjectProperty;
import com.vaadin.data.util.PropertysetItem;
import org.vaadin.addons.lazyquerycontainer.QueryDefinition;
import org.vaadin.addons.lazyquerycontainer.QueryFactory;
/**
 *  Created by aluis on 10/8/15.
 */
public abstract class BaseQueryFactory<BEANTYPE> implements QueryFactory {
    public static final String OBJ = "OBJ";
    public static final int BATCH_GRID_SIZE = 20;
    protected QueryDefinition queryDefinition;
    protected LazyQuery<BEANTYPE> lazyQuery;
    protected BaseQueryFactory(LazyQuery<BEANTYPE> lazyQuery) {
        this.lazyQuery = lazyQuery;
    }
    public abstract Item constructItem();
    @SuppressWarnings("unchecked")
    public Item constructItem(BEANTYPE beantype) {
        PropertysetItem item = new PropertysetItem();
        for (Object propertyId : this.queryDefinition.getPropertyIds()) {
            Object value = createProperty(propertyId, beantype);
            item.addItemProperty(propertyId, new ObjectProperty(value,
                    queryDefinition.getPropertyType(propertyId),
                    queryDefinition.isPropertyReadOnly(propertyId)
            ));
        }
        return item;
    }
    public abstract Object createProperty(Object propertyID, BEANTYPE dataObject);
    protected Object getDefaultProperty(Object propertyID) {
        return queryDefinition.getPropertyDefaultValue(propertyID);
    }
    public QueryDefinition getQueryDefinition() {
        return queryDefinition;
    }
    public void setQueryDefinition(QueryDefinition queryDefinition) {
        this.queryDefinition = queryDefinition;
    }
    public LazyQuery<BEANTYPE> getLazyQuery() {
        return lazyQuery;
    }
    public void setLazyQuery(LazyQuery<BEANTYPE> lazyQuery) {
        this.lazyQuery = lazyQuery;
    }
}
import com.vaadin.data.Item;
import com.vaadin.data.util.ObjectProperty;
import com.vaadin.data.util.PropertysetItem;
import org.vaadin.addons.lazyquerycontainer.QueryDefinition;
import org.vaadin.addons.lazyquerycontainer.QueryFactory;

/**
 *  Created by aluis on 10/8/15.
 */
public abstract class BaseQueryFactory<BEANTYPE> implements QueryFactory {

    public static final String OBJ = "OBJ";
    public static final int BATCH_GRID_SIZE = 20;
    protected QueryDefinition queryDefinition;
    protected LazyQuery<BEANTYPE> lazyQuery;

    protected BaseQueryFactory(LazyQuery<BEANTYPE> lazyQuery) {
        this.lazyQuery = lazyQuery;
    }

    public abstract Item constructItem();

    @SuppressWarnings("unchecked")
    public Item constructItem(BEANTYPE beantype) {
        PropertysetItem item = new PropertysetItem();
        for (Object propertyId : this.queryDefinition.getPropertyIds()) {
            Object value = createProperty(propertyId, beantype);
            item.addItemProperty(propertyId, new ObjectProperty(value,
                    queryDefinition.getPropertyType(propertyId),
                    queryDefinition.isPropertyReadOnly(propertyId)
            ));
        }
        return item;
    }

    public abstract Object createProperty(Object propertyID, BEANTYPE dataObject);

    protected Object getDefaultProperty(Object propertyID) {
        return queryDefinition.getPropertyDefaultValue(propertyID);
    }

    public QueryDefinition getQueryDefinition() {
        return queryDefinition;
    }

    public void setQueryDefinition(QueryDefinition queryDefinition) {
        this.queryDefinition = queryDefinition;
    }

    public LazyQuery<BEANTYPE> getLazyQuery() {
        return lazyQuery;
    }

    public void setLazyQuery(LazyQuery<BEANTYPE> lazyQuery) {
        this.lazyQuery = lazyQuery;
    }
}

y por último nuestro contenedor (Container)

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition;
import org.vaadin.addons.lazyquerycontainer.LazyQueryView;
import org.vaadin.addons.lazyquerycontainer.QueryFactory;
/**
 *  Created by aluis on 10/8/15.
 */
public abstract class BaseContainer extends LazyQueryContainer {
    public BaseContainer(QueryFactory queryFactory) {
        super(new LazyQueryDefinition(true, BaseQueryFactory.BATCH_GRID_SIZE, BaseQueryFactory.OBJ), queryFactory);
        addContainerProperty(BaseQueryFactory.OBJ, Object.class, null, false, false);
    }
    @SuppressWarnings("unused")
    public void addDebug() {
        addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_QUERY_INDEX, Integer.class, 0, true, false);
        addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_BATCH_INDEX, Integer.class, 0, true, false);
        addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_BATCH_QUERY_TIME, Long.class, 0, true, false);
        addContainerProperty(LazyQueryView.PROPERTY_ID_ITEM_STATUS, Enum.class, 0, true, false);
    }
    public abstract void addColumns();
}
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition;
import org.vaadin.addons.lazyquerycontainer.LazyQueryView;
import org.vaadin.addons.lazyquerycontainer.QueryFactory;

/**
 *  Created by aluis on 10/8/15.
 */
public abstract class BaseContainer extends LazyQueryContainer {

    public BaseContainer(QueryFactory queryFactory) {
        super(new LazyQueryDefinition(true, BaseQueryFactory.BATCH_GRID_SIZE, BaseQueryFactory.OBJ), queryFactory);
        addContainerProperty(BaseQueryFactory.OBJ, Object.class, null, false, false);
    }

    @SuppressWarnings("unused")
    public void addDebug() {
        addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_QUERY_INDEX, Integer.class, 0, true, false);
        addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_BATCH_INDEX, Integer.class, 0, true, false);
        addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_BATCH_QUERY_TIME, Long.class, 0, true, false);
        addContainerProperty(LazyQueryView.PROPERTY_ID_ITEM_STATUS, Enum.class, 0, true, false);
    }

    public abstract void addColumns();
}

Una vez construidos todos estos elementos proceso a crear una implementación usando un DOMAIN (Student).

Creamos la consulta (Query) en un inicio:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
import com.alsnightsoft.vaadin.testcontainer.BaseQuery
import com.alsnightsoft.vaadin.testcontainer.BaseQueryFactory
import com.alsnightsoft.vaadin.testcontainer.domains.Student
/**
 *  Created by aluis on 10/16/15.
 */
class StudentQuery extends BaseQuery<Student> {
    public StudentQuery(BaseQueryFactory baseQueryFactory) {
        super(baseQueryFactory)
    }
}
import com.alsnightsoft.vaadin.testcontainer.BaseQuery
import com.alsnightsoft.vaadin.testcontainer.BaseQueryFactory
import com.alsnightsoft.vaadin.testcontainer.domains.Student

/**
 *  Created by aluis on 10/16/15.
 */
class StudentQuery extends BaseQuery<Student> {

    public StudentQuery(BaseQueryFactory baseQueryFactory) {
        super(baseQueryFactory)
    }
}

Luego creamos el creador de consultas (QueryFactory)

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import com.alsnightsoft.vaadin.testcontainer.BaseQueryFactory
import com.alsnightsoft.vaadin.testcontainer.LazyQuery
import com.alsnightsoft.vaadin.testcontainer.domains.Student
import com.vaadin.data.Item
import org.vaadin.addons.lazyquerycontainer.Query
import org.vaadin.addons.lazyquerycontainer.QueryDefinition
/**
 *  Created by aluis on 10/16/15.
 */
class StudentFactory extends BaseQueryFactory<Student> {
    public StudentFactory(LazyQuery<Student> lazyQuery) {
        super(lazyQuery)
    }
    @Override
    Item constructItem() {
        return constructItem(new Student())
    }
    @Override
    Object createProperty(Object propertyID, Student dataObject) {
        switch (propertyID.toString()) {
            case OBJ:
                return dataObject
            case "id":
                return dataObject.getId()
            case "name":
                return dataObject.getName()
            case "description":
                return dataObject.getDescription()
            case "enabled":
                return dataObject.getEnabled()
        }
        return getDefaultProperty(propertyID)
    }
    @Override
    Query constructQuery(QueryDefinition queryDefinition) {
        this.queryDefinition = queryDefinition
        return new StudentQuery(this)
    }
}
import com.alsnightsoft.vaadin.testcontainer.BaseQueryFactory
import com.alsnightsoft.vaadin.testcontainer.LazyQuery
import com.alsnightsoft.vaadin.testcontainer.domains.Student
import com.vaadin.data.Item
import org.vaadin.addons.lazyquerycontainer.Query
import org.vaadin.addons.lazyquerycontainer.QueryDefinition

/**
 *  Created by aluis on 10/16/15.
 */
class StudentFactory extends BaseQueryFactory<Student> {

    public StudentFactory(LazyQuery<Student> lazyQuery) {
        super(lazyQuery)
    }

    @Override
    Item constructItem() {
        return constructItem(new Student())
    }

    @Override
    Object createProperty(Object propertyID, Student dataObject) {
        switch (propertyID.toString()) {
            case OBJ:
                return dataObject
            case "id":
                return dataObject.getId()
            case "name":
                return dataObject.getName()
            case "description":
                return dataObject.getDescription()
            case "enabled":
                return dataObject.getEnabled()
        }
        return getDefaultProperty(propertyID)
    }

    @Override
    Query constructQuery(QueryDefinition queryDefinition) {
        this.queryDefinition = queryDefinition
        return new StudentQuery(this)
    }
}

Y por último creamos nuestro contenedor simbólico.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.alsnightsoft.vaadin.testcontainer.BaseContainer
import com.alsnightsoft.vaadin.testcontainer.LazyQuery
import com.alsnightsoft.vaadin.testcontainer.domains.Student
/**
 *  Created by aluis on 10/16/15.
 */
class StudentContainer2 extends BaseContainer {
    public StudentContainer2(LazyQuery<Student> lazyQuery) {
        super(new StudentFactory(lazyQuery))
        addColumns()
    }
    @Override
    public void addColumns() {
        addContainerProperty("id", Long.class, 0l, false, false)
        addContainerProperty("name", String.class, "", false, false);
        addContainerProperty("description", String.class, "", false, false);
        addContainerProperty("enabled", Boolean.class, true, false, false);
    }
}
import com.alsnightsoft.vaadin.testcontainer.BaseContainer
import com.alsnightsoft.vaadin.testcontainer.LazyQuery
import com.alsnightsoft.vaadin.testcontainer.domains.Student

/**
 *  Created by aluis on 10/16/15.
 */
class StudentContainer2 extends BaseContainer {

    public StudentContainer2(LazyQuery<Student> lazyQuery) {
        super(new StudentFactory(lazyQuery))

        addColumns()
    }

    @Override
    public void addColumns() {
        addContainerProperty("id", Long.class, 0l, false, false)
        addContainerProperty("name", String.class, "", false, false);
        addContainerProperty("description", String.class, "", false, false);
        addContainerProperty("enabled", Boolean.class, true, false, false);
    }
}

Entonces una vez terminado todo esto solo nos queda implementar!

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import com.alsnightsoft.vaadin.testcontainer.containers.StudentContainer2
import com.alsnightsoft.vaadin.testcontainer.domains.Student
import com.alsnightsoft.vaadin.testcontainer.services.StudentService
import com.vaadin.event.SelectionEvent
import com.vaadin.grails.Grails
import com.vaadin.server.VaadinRequest
import com.vaadin.ui.Grid
import com.vaadin.ui.UI
import com.vaadin.ui.VerticalLayout
/**
 *  Created by Ing. Aluis Marte on 2/1/2015.
 */
class Demo2 extends UI {
    private VerticalLayout mainLayout
    private StudentContainer2 container
    private Grid tableDemo
    @Override
    protected void init(VaadinRequest vaadinRequest) {
        mainLayout = new VerticalLayout()
        container = new StudentContainer2(new LazyQuery<Student>() {
            @Override
            int getSize() {
                return Grails.get(StudentService).countStudents(true)
            }
            @Override
            List<Student> getItemsIds(int startIndex, int numberOfIds) {
                return Grails.get(StudentService).listStudents(true, startIndex, numberOfIds)
            }
            @Override
            int getFilteredSize() {
                return 0
            }
            @Override
            List<Student> getFilteredItemsIds(int startIndex, int numberOfIds) {
                return new ArrayList<Student>()
            }
        })
        tableDemo = new Grid()
        tableDemo.setSelectionMode(Grid.SelectionMode.SINGLE)
        tableDemo.setImmediate(true)
        tableDemo.setSizeFull()
        tableDemo.setFooterVisible(true)
        tableDemo.setContainerDataSource(container)
        tableDemo.setColumnOrder("id", "name", "description", "enabled")
        tableDemo.getColumn("id").setHeaderCaption("ID")
        tableDemo.getColumn("name").setHeaderCaption("NAME")
        tableDemo.getColumn("description").setHeaderCaption("DESC")
        tableDemo.getColumn("enabled").setHeaderCaption("ACTIVE")
        tableDemo.removeColumn("OBJ")
        tableDemo.addSelectionListener(new SelectionEvent.SelectionListener() {
            @Override
            void select(SelectionEvent selectionEvent) {
                println "Student ID: " + ((Student) tableDemo.getSelectedRow()).getId()
                println "Student Name: " + ((Student) tableDemo.getSelectedRow()).getName()
            }
        })
        tableDemo.clearSortOrder()
        mainLayout.addComponent(tableDemo)
        setContent(mainLayout)
    }
}
import com.alsnightsoft.vaadin.testcontainer.containers.StudentContainer2
import com.alsnightsoft.vaadin.testcontainer.domains.Student
import com.alsnightsoft.vaadin.testcontainer.services.StudentService
import com.vaadin.event.SelectionEvent
import com.vaadin.grails.Grails
import com.vaadin.server.VaadinRequest
import com.vaadin.ui.Grid
import com.vaadin.ui.UI
import com.vaadin.ui.VerticalLayout

/**
 *  Created by Ing. Aluis Marte on 2/1/2015.
 */
class Demo2 extends UI {

    private VerticalLayout mainLayout

    private StudentContainer2 container

    private Grid tableDemo

    @Override
    protected void init(VaadinRequest vaadinRequest) {
        mainLayout = new VerticalLayout()

        container = new StudentContainer2(new LazyQuery<Student>() {
            @Override
            int getSize() {
                return Grails.get(StudentService).countStudents(true)
            }

            @Override
            List<Student> getItemsIds(int startIndex, int numberOfIds) {
                return Grails.get(StudentService).listStudents(true, startIndex, numberOfIds)
            }

            @Override
            int getFilteredSize() {
                return 0
            }

            @Override
            List<Student> getFilteredItemsIds(int startIndex, int numberOfIds) {
                return new ArrayList<Student>()
            }
        })

        tableDemo = new Grid()
        tableDemo.setSelectionMode(Grid.SelectionMode.SINGLE)
        tableDemo.setImmediate(true)
        tableDemo.setSizeFull()
        tableDemo.setFooterVisible(true)
        tableDemo.setContainerDataSource(container)
        tableDemo.setColumnOrder("id", "name", "description", "enabled")
        tableDemo.getColumn("id").setHeaderCaption("ID")
        tableDemo.getColumn("name").setHeaderCaption("NAME")
        tableDemo.getColumn("description").setHeaderCaption("DESC")
        tableDemo.getColumn("enabled").setHeaderCaption("ACTIVE")
        tableDemo.removeColumn("OBJ")
        tableDemo.addSelectionListener(new SelectionEvent.SelectionListener() {
            @Override
            void select(SelectionEvent selectionEvent) {
                println "Student ID: " + ((Student) tableDemo.getSelectedRow()).getId()
                println "Student Name: " + ((Student) tableDemo.getSelectedRow()).getName()
            }
        })

        tableDemo.clearSortOrder()
        mainLayout.addComponent(tableDemo)

        setContent(mainLayout)
    }
}

Felicidades! ya tenemos TABLES o GRID que usan contenedores vagos!

Más información aquí
Source Implementación GrailsLazyContainer
Source GIT TestLazyContainerGrailsVaadin

domingo, 1 de febrero de 2015

LazyContainer de Vaadin en Grails

Tras largas jornada de lecturas y desarrollo, por fín un componente en vaadin para grails para hacer que las tablas de datos trabajen con paginación y filtros del lado del servidor.

El demo incluye un ejemplo con query filtrado y el normal, con gran debug y buenas prácticas.

Demo y proyecto implementado con las clases.
https://bitbucket.org/aluismarte/testlazycontainervaadingrails/

Debido a las versiones superiores al Vaadin 7.4.1 el plugin dejó de funcionar correctamente, por lo que existe otra nueva interfaz usando el nuevo plugin nativo de Vaadin que respeta el JPA, pero con los ajustes necesarios para que Grails los pueda usar. Esta probado en las versiones de Grails 2.x

lunes, 6 de enero de 2014

Compilar kernel linux MK908

Con el gran desarrollo de la tecnología ARM, ya muchos sistemas y opciones están abiertas, pero aun están un poco arduas para usuarios mas novatos. Algunos curiosos como yo buscan la forma de compilar un kernel o encontrar una IMG que se adapte a nuestras necesidades.

En mi caso en particular estaba buscando una IMG SD Boot de la MK908 V1, pero no encontré nada y busque de donde estaban obteniendo los SRC de los kernel y decidí ver si podía compilar uno. Tras unos 12 días de intensa búsqueda y ardua lectura, por fin les traigo un manual rápido y completo para compilar el ultimo kernel que he visto hasta la fecha para la MK908.

Nota: Puede que quiera hacer una boot IMG normal que utilice lo que se llama parameter y eso será explicado según estemos en los pasos requeridos.

Que necesito?
Luego de descargar lo necesario empezamos lo bueno:

Primero dentro de la carpeta del kernel abrimos una consola o terminal y procedemos a hacer un arreglo en el source, pues en los driver mali existen unos archivos que no esta incluidos en la carpeta que deben estar, por lo que pasaremos a copiarlos a su debida carpeta.

cp drivers/gpu/mali/include/* drivers/gpu/mali/ump/common/

Luego de hacer esto lo que debemos hacer es copiar nuestro archivo de configuración a el lugar correcto sobrescribiendo (o moviendo para dejarlo tranquilo y tener un backup rápido) el archivo que esta o simplemente modificando la linea que nos interesa.

mv arch/arm/configs/rk3188_mk908_defconfig arch/arm/configs/rk3188_mk908_defconfig.old
cp rk3188_mk908_defconfig arch/arm/configs/

Si lo que les interesa es modificar la linea, puesto quieren hacer un kernel que haga boot directo desde la nand o que use un archivo de parameter puede editar en el archivo de configuración la siguiente linea:

CONFIG_CMDLINE="console=ttyFIQ0 androidboot.console=ttyFIQ0 init=/init"

En las comillas debe reemplazar según su necesidad, pues que en SD Boot quiero que sea autónomo lo hago así, pero en caso de usar un archivo de parameter debemos alterar estas lineas:

#CONFIG_CMDLINE_FORCE  is not set
CONFIG_CMDLINE_FROM_BOOTLOADER=y

Ya con esto tenemos un archivo de configuración normal que necesita parameter y el archivo debe tener el siguiente contenido:

FIRMWARE_VER:4.0.5
MACHINE_MODEL:rk30sdk
MACHINE_ID:007
MANUFACTURER:RK30SDK
MAGIC: 0x5041524B
ATAG: 0x60000800
MACHINE: 3066
CHECK_MASK: 0x80
KERNEL_IMG: 0x60408000
#RECOVER_KEY: 1,1,0,20,0
CMDLINE:initrd=0x62000000,0x00800000 root=LABEL=linuxroot init=/sbin/init mtdparts=rk29xxnand:0x00008000@0x00002000(boot),0x00008000@0x0000A000(kernel),0x00200000@0x00012000(swap),-@0x00212000(linux)

La linea CMDLine es la que alteramos para obtener los resultados que deseamos. Asi no tenemos que volver a compilar el kernel.

Nota: Al hacer esto damos por hecho que linux esta instalado en la memoria NAND (interna) y el es quien administra la maquina dejando de lado a ANDROID.

Ya hecha la configuración de todo lo necesario solo debemos empezar a compilar con el siguiente comando:

./build_omegamoon_mk908 2

El 2 escrito lo que dice que haga 2 trabajos de compilación a la vez, por lo que si tenemos un procesador de 4 o mas núcleos le recomiendo que pongan el numero según tantos CPU tengan.

Mas adelante mostraré como flashear la maquina con un buen nivel de detalle (fotos). Al igual que preparar el boot de nuestro Linux necesitaremos una SD CARD que pueda usar nuestra MK908 y procederemos a instalar el sistema en ARMHF que nos guste (Anteriormente subí un manual, pero para este caso deberemos tener una sola consideración adicional).

Al final de este proceso de compilación obtendremos un archivo llamado kernel.img que para nuestro manejo debemos entender que si usan el archivo de configuración que subí obtendrán un kernel recovery para boot SD CARD (este inclusive debería funcionar para sistemas instalados en la NAND, puesto que el root que busca al prender es una partición con nombre linuxroot).

Lectura y guia basada en

http://linux-rockchip.info/mw/index.php?title=Linux_on_NAND
https://github.com/omegamoon/Rockchip-GPL-Kernel



domingo, 1 de septiembre de 2013

Instalar Debian en SD Card en mk908

Hoy en día tenemos la oportunidad de tener mini-pc android con suficiente recursos como para correr un sistema linux con su entorno gráfico Gnome u otro que nos interese, en mi caso compre una Tronsmart MK908.



Este equipo tiene los siguientes recursos:

  • 2 GB de RAM DDR3
  • Rk3188 Quad Core 1.6ghz, max 1.8Ghz (Cortex-A9)
  • Quad Core Mali 400
  • On board NAND Flash 8G
  • Micro SD Card/ T-Flash(Maximum support 32GB)
  • WIFI support 802.11 b/g/n
  • Bluetooth v4.0
  • MINI HDMI Port
  • Mini USB,Support OTG/HOST ( USB port RJ45)

Enlace de compra en amazon: http://www.amazon.com/Tronsmart-MK908-Google-Android-RK3188/dp/B00CGYAGL6/ref=sr_1_1?ie=UTF8&qid=1378047748&sr=8-1&keywords=mk908

Ahora vamos a proceder a instalar debian en la Sd Card. Empecemos por hacer una partición al final de la memoria SD en formato EXT-4 y con nombre linuxroot, luego procedemos a montar la partición y a abrir una consola en ella para empezar a instalar. Esto lo podemos hacer con el particionador que deseemos.

Para hacer práctico el ejemplo la montaremos en una carpeta que crearemos en /mnt. (Esto lo corremos desde la terminal en super usuario SU)

mkdir /mnt/debianSD

El dispositivo cambia según la cantidad de discos por lo que deben averiguar cual es el dispositivo.

mount /dev/sdb1 /mnt/debianSD

En la terminal en modo super usuario (su) corremos el siguiente comando. (Recordemos que en --foreign podemos cambiar la distribución que nos plasca)

debootstrap --verbose --arch=armhf --foreign wheezy /mnt/debianSD http://ftp.de.debian.org/debian

Después que termine el proceso desmontamos la o las particiones para poder remover la memoria SD y la insertamos en el equipo. En el siguiente paso pueden tanto hacerlo desde el ordenador como directo desde el sistema Android con el comando adb shell que lo corremos usando el "Android SDK Tools" y el "Android SDK Platform-tools" (lo podemos descargar aqui http://developer.android.com/sdk/index.html).

Yo preferí correrlo desde el dispositivo, pero conectándole un teclado a mi MK908. Sea por cual sea el modo que decidieron proceder a terminar la instalación se tendrán que ejecutar los siguientes comandos para poder hacer lo que queremos.

Para poder ejecutar lo siguiente en el equipo necesitamos instalar una terminal en android (https://play.google.com/store/apps/details?id=jackpal.androidterm&hl=es-419).

Primero iniciamos en modo root.

su

Y ahora procedemos a preparar el entorno

export mnt=/system/debianSD
export PATH=/usr/bin:/usr/sbin:/bin:$PATH
export TERM=linux
export HOME=/root
export SHELL=/bin/bash

Procedemos a montar la partición para terminar de instalar. En caso de que mkdir no funcione pues solo montamos la partición de /system en modo escritura (mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system) esto puede que haga que Android reinicie, así que no se preocupen solo esperen que inicie de nuevo.

mkdir /system/debianSD
mount -t ext4 /dev/block/vold/179:2 /system/debianSD
busybox chroot $mnt

Con la ejecución del ultimo comando nos situamos dentro del sistema Debian en la memoria, por lo que la terminal ya no es Android sino Debian.

Muchos manuales que podrás encontrar en la red no te dirán lo siguiente, pues dentro de Debian debes correr el siguiente comando para poder activar los comandos básicos y terminar la instalación. Este comando debe ser corrido cada vez que entremos a Debian, pero automatizaremos esto más adelante.

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Una vez hecho la exportación del PATH procedemos a terminar de instalar.

/debootstrap/debootstrap --second-stage

Una vez completa vamos a agregar un repositorio y instalar lo que queremos. En /etc/apt/sources.list agrego

echo 'deb http://http.us.debian.org/debian/ wheezy main contrib non-free' > /etc/apt/sources.list
echo 'deb-src http://http.us.debian.org/debian/ wheezy main contrib non-free' > /etc/apt/sources.list

Adicionamos lo siguiente para entregar conexión a Internet

echo 'nameserver 8.8.8.8' > /etc/resolv.conf
echo '127.0.0.1 localhost' > /etc/resolv.conf

Ya en esta etapa hacemos exit para regresar a Android y automatizaremos la entrada a Debian con algunos scripts.

Este script lo ubicaremos en /system/bin/ con el nombre que queramos, en mi caso lo llame "debian". El contenido es el siguiente:

#!/system/bin/sh

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH
export TERM=linux
export HOME=/root
export SHELL=/bin/bash

mount -t ext4 /dev/block/vold/179:2 /system/debianSD
mount -t devpts /devpts /system/debianSD/dev/pts
mount -t proc /proc /system/debianSD/proc
mount -t sysfs /sysfs /system/debianSD/sys
rm -f /system/debianSD/etc/mtab
rm -rf /system/debianSD/tmp/*
rm -rf /system/debianSD/tmp/.X*
ln -s /proc/mounts /system/debianSD/etc/mtab
busybox chroot /system/debianSD /bin/bash

Este paso recomiendo hacerlo en una pc y copiarlo a la memoria para luego copiarlo a el sitio correcto. En caso de tener el script listo en la memoria lo copian de la siguiente manera

cp /mnt/sdcard/debian /system/bin/

Con esto cuando queremos entrar a Debian solo debemos abrir la terminar en android y escribir debian y con esto entramos.

Con esto termina la instalación, pero solo estamos en modo consola, por lo que vamos a instalar algunas cosas mas en nuestro Debian para poder verlo de forma gráfica.

Dentro de Debian ejecutamos lo siguiente para instalar un pequeño servidor de VNC.

apt-get update
apt-get install tightvncserver icewm xterm

Una vez instalador tenemos que iniciar el servidor y lo hacemos con el siguiente comando y la geometría la cambiamos a la que tenemos en el android.

vncserver -geometry 1280x720

Si queremos matarlo solo ejecutamos

vncserver -kill :1

Instalamos un escritorio ligero para mejor rendimiento, pero es decisión nuestra elegir el escritorio.

apt-get install lxde

Con esto solo queda tener un cliente en Android para poder visualizar nuestro sistema.

https://play.google.com/store/apps/details?id=com.iiordanov.freebVNC&hl=es-419

La configuración para el cliente es la siguiente:

Nombre conexión: Debian
Contraseña: La que ponemos cuando configuramos el vnc
Dirección: localhost
Puerto: 5901
Color formato: 24-bit color (4bpp)

Ahora queda la automatización de nuestro PATH que siempre se pierde y esto lo hacemos con lo siguiente (esto se hace dentro de Debian):

cd
echo 'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' > .bashrc

Algunas fotos de Debian corriendo.



Para aquellos que no les gusta hacer particiones en su SD Card, tan solo deben usar un disco virtual (son esos archivos img que sirven como discos) e instalar el Debian hay y en vez de montar la partición solo montan esa img y después el proceso es igual.

Si quieren crear su propio disco solo deben usar dd if=/dev/zero of=miDebian.img bs=1024M count=10 o si prefieren pueden hacerlo con un programa para ello. En caso que no quieran un archivo que tenga tamaño fijo, pueden usar offset para crear esas imágenes que crecen según el sistema lo necesita.


miércoles, 15 de mayo de 2013

Implementación de controles de juegos multiplataforma con código centrado.

En el desarrollo de juegos multiplataforma siempre tenemos el inconveniente de que los controles cambian de un entorno a otro y más cuando tenemos tecnologías que usan teclado, mouse y touch.

Pues en la búsqueda de hacer un control unisono para múltiples plataformas y que el código base del juego este centrado y este no tenga problemas a la hora de identificar los controles, pues usé el paradigma orientado a objetos y cree una clase que me abstrae los controles y los coloca en una especie de caja, por lo que solo se tiene que implementar la forma de captura de cada botón en las distintas plataformas y el juego solo leerá la clase con los botones y la lógica solo se ejecutará 1 vez y no múltiples veces por las diferentes plataformas.

.Pero que librerías o APIS tenemos que nos permiten crear juegos en múltiples plataformas?, pues LibGDX es una de estas y al estar escrita en JAVA permite que esta corra en diversas tecnologías sin problemas y  es hay en donde LibGDX demuestra su potencial, pues usando un proyecto base (Este contiene toda la lógica del juego y es el juego en sí) y los proyectos respectivos para lanzar el juego en las diferentes plataformas, es decir, que si queremos que corra en pc tenemos un proyecto que lanza el código del proyecto base y la librería se encarga de convertirlo en un software de pc y así sucede lo mismo con ANDROID.

Ahora que hablamos un poco de la tecnología a usar y la librería que nos dará soporte, pues queda definir el esquema de organización de un juego multiplataforma y mas o menos se ve de la siguiente manera:

Pues como verán los proyectos solo indican que son lanzadores, pero para los curiosos así se ven las clases main de los proyectos:

PC:

package com.alssoftrd.games.pruebared;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "PruebaRed";
cfg.useGL20 = false;
cfg.width = 800;
cfg.height = 480;

new LwjglApplication(new Prueba(1), cfg);
}
}


ANDROID:

package com.alssoftrd.games.pruebared;

import android.os.Bundle;

import com.alssoftrd.games.pruebared.PruebaRed;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class MainActivity extends AndroidApplication {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
     
        AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
        cfg.useGL20 = false;
        cfg.useAccelerometer = false;
        cfg.useCompass = false;
     
        initialize(new Prueba(0), cfg);
    }
}


Como ven solo existe un poco de código básico en cada proyecto, claro esta que hay que adicionar las librerías de cada plataforma y anexar como parte del código el proyecto base.

Ahora que vemos el esquema de trabajo de forma más gráfica, pues procedemos a hacer la clase de los controles que permitirán que los controles trabajan de forma normal en múltiples plataformas.

Primero definimos los botones y como es para explicar pues tomaré un control simple como los siguientes; que son un PAD (flechas de movimiento) y 2 botones básicos que son A y B.

Ahora que sabemos que botones usaremos pues procedemos a crear la clase con las siguientes caracteristicas: El PAD serán 4 variables booleanos que darán la dirección en sus respectivas axis y los botones A y B serán booleanos que indicarán cuando se presionan y cuando no.

En el PAD como uso la librería LibGDX para facilitarme la creación de el aspecto visual del mismo uso una clase existente y esta solo se usará en el proyecto de android y la clase se vera más o menos así:


import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad.TouchpadStyle;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;

public class AControl {

private boolean a;
private boolean b;
// private boolean start;
private boolean left;
private boolean right;
private boolean up;
private boolean down;
private Touchpad pad = null;
private TouchpadStyle estiloPad;
private Skin skinPad;

public AControl() {
this.a = false;
this.b = false;
this.left = false;
this.right = false;
this.up = false;
this.down = false;
}

private void cargarPad() {
                // Esto es para darle un aspecto visual, lo cual ignoro.
skinPad = new Skin();
TextureAtlas atlas = Manager.get().get("data/Pad/Pad.pack",
TextureAtlas.class);
estiloPad = new TouchpadStyle();
skinPad.addRegions(atlas);
estiloPad.background = skinPad.get("Pad", Drawable.class);
estiloPad.knob = skinPad.get("ButtonPad", Drawable.class);
pad = new Touchpad(20, estiloPad);
pad.setBounds(15, 15, 150, 150);
}

public boolean isA() {
return a;
}

public boolean getA() {
return a;
}

public void setA(boolean a) {
this.a = a;
}

public boolean isB() {
return b;
}

public boolean getB() {
return b;
}

public void setB(boolean b) {
this.b = b;
}

public boolean isLeft() {
return this.left;
}

public boolean getLeft() {
return this.left;
}

public void setLeft(boolean left) {
this.left = left;
}

public boolean isRight() {
return this.right;
}

public boolean getRight() {
return this.right;
}

public void setRight(boolean right) {
this.right = right;
}

public boolean isUp() {
return this.up;
}

public boolean getUp() {
return this.up;
}

public void setUp(boolean up) {
this.up = up;
}

public boolean isDown() {
return this.down;
}

public boolean getDown() {
return this.down;
}

public void setDown(boolean down) {
this.down = down;
}

public Touchpad getPad() {
if(pad == null) {
cargarPad();
}
return pad;
}
}


Como verán existen métodos que se pueden pensar que están repetidos, pero solo es por comodidad. Como ven todos los botones están abstraídos en una serie de booleanos y así solo tengo que saber en que plataforma estoy corriendo y asignar los botones a sus respectivas variables.

Muchos siguen pensando ¿que hago con esta clase?, es esta es la clase base de donde el juego leerá, pero todavia falta leer los inputs y asignarles y esto se encarga esta clase que usando singleton manejamos los controles en las diversas plataformas.


import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class Control {

private AControl controles;
        // 0 es ANDROID y 1 es PC
private int plataforma;

public Control(int plataforma) {
this.controles = new AControl();
this.plataforma = plataforma;
}

public void captura() {
if (plataforma == 0) {
capturaAndroid();
return;
}
capturaPc();
}

private void capturaAndroid() {

if (get().getPad().getKnobPercentX() > 0) {
controles.setRight(true);
} else {
controles.setRight(false);
}
if (get().getPad().getKnobPercentX() < 0) {
controles.setLeft(true);
} else {
controles.setLeft(false);
}
                // Faltan los botones de el axis Y
                // Faltan los botones A y B
}

private void capturaPc() {
// Movimiento del personaje
controles.setLeft(Gdx.input.isKeyPressed(Keys.A));
controles.setRight(Gdx.input.isKeyPressed(Keys.D));
controles.setUp(Gdx.input.isKeyPressed(Keys.W));
controles.setDown(Gdx.input.isKeyPressed(Keys.S));
// Manejo de los botones de acción
controles.setA(Gdx.input.isButtonPressed(Input.Buttons.LEFT));
controles.setB(Gdx.input.isButtonPressed(Input.Buttons.RIGHT));
}

public AControl get() {
return controles;
}

private int getPlataforma() {
return plataforma;
}

public void setTouchPad(Stage stage) {
if (getPlataforma() == 0) {
stage.addActor(get().getPad());
Gdx.input.setInputProcessor(stage);
}
}
}


Como verán el touchpad que esta en la clase de Acontrol se usa de forma independiente y este solo hay que activarle y gráficar  pues de forma indirecta puedo saber si el usuario esta pulsando el touchpad y saber en que dirección.

Entonces ya tenemos los controles abstraídos y que son manejables en diferentes entornos, pues lo único que hay que hacer es usar el método captura de la clase Control en el ciclo, para que este refresque en cada frame del juego para saber si se pulsan o no los botones y usando la clase get de la clase Control obtenemos los controles abstraídos y podemos usarle para crear la lógica de nuestro juego.

Mas adelante subiré ejemplos de código y proyectos usando LibGDX mostrando como se usan los códigos presentados.