Índice

0. Classpath

Para que un proyecto pueda usar GDBMS ha de incluir en su classpath los jars de GDBMS y de DriverManager. Además de estos jars habrá que incluir los jars con los drivers si hay alguno. Existe un proyecto que trata de adaptar los drivers de gvSIG a los de GDBMS. En él se pueden encontrar un driver de shapefile de lectura/escritura y un driver de sólo lectura de postGIS. Se deberá incluir dicho jar también para poder usar tales drivers.

1. Acceder a una fuente de datos.

Cualquier operación con una fuente de datos se hace a través de la interfaz DataSource. Los pasos a seguir son los siguientes:

–Crear un DataSourceFactory.

–Establecer los drivers para manejar una fuente de datos.

–Registrar dicha fuente de datos.

–Obtener el DataSource que nos proporcione los métodos para manejar la fuente de datos.

1.1 Crear un DataSourceFactory.

Este paso es común a cualquier fuente de datos con la que queramos trabajar y solo hay que instanciar un objeto de la clase DataSourceFactory de la siguiente forma:

DataSourceFactory factory = new DataSourceFactory();

Si se quieren usar drivers del proyecto GDBMS gvSIG plugins se necesario obtener la instancia de la clase LayerFactory

DataSourceFactory factory = LayerFactory.getDataSourceFactory();

1.2 Establecer los drivers para manejar una fuente de datos

Este paso también es igual en cualquier fuente de datos. Existe la posibilidad de configurar un directorio en el que se ponen los drivers y el sistema los reconoce automáticamente, aunque es aconsejable el registrado manual:

DriverManager dm = new DriverManager();
dm.registerDriver(“hsqldb driver”, HSQLDBDriver.class);
dm.registerDriver(“csv”, CSVDriver.class);
factory.setDriverManager(dm);

1.3 Registrar una fuente de datos.

El siguiente paso es registrar la fuente de datos. Esta operación se hace mediante la instrucción addDataSource(DataSourceDefinition dsd). Dependiendo del tipo de nuestra fuente de datos, el DataSourceDefinition será distinto:

1.3.1 Registrar un fichero.

Si el fichero contiene datos alfanuméricos, el DataSourceDefinition será un FileSourceDefinition. Si contiene datos espaciales, será un SpatialFileSourceDefinition. Los constructores de ambas clases requieren tres parámetros. Estos son el nombre con el que nos referiremos a la fuente de datos, el driver que se usará para manejar la fuente de datos y la ruta donde se encuentra el fichero. Dos ejemplos:

El código que registra una base de datos alfanumérica almacenada en el fichero persona.csv es:

FileSourceDefinition def = new FileSourceDefinition("persona", "csv", “persona.csv");
factory.addDataSource(def);

El código que registra una base de datos espacial almacenada en el fichero puntos.shp es:

SpatialFileSourceDefinition spatialDef = new SpatialFileSourceDefinition("myPoints", "FMap ShapeFile Driver", “points.shp");
factory.addDataSource(spatialDef);

1.3.2 Registrar un objeto.

Si los objetos contienen datos alfanuméricos, el DataSourceDefinition será un ObjectSourceDefinition. Si contiene datos espaciales, será un SpatialObjectSourceDefinition. El constructor de ambas clases requiere dos parámetros. El primero de ellos es el nombre de la fuente de datos. El segundo parámetro es el driver con el que se manejará el objeto. En este caso el driver debe ser creado como una implementación de la interfaz AlphanumericObjectDriver o SpatialObjectDriver, según sea nuestro objeto. Veamos unos ejemplos.

El código que registra un objeto con información alfanumérica es:

FakeObjectDriver driver = new FakeObjectDriver();
ObjectSourceDefinition def = new ObjectSourceDefinition("myObject", driver);
factory.addDataSource(def);

El código que registra un objeto con información espacial es:

FakeSpatialObjectDriver driver = new FakeSpatialObjectDriver();
SpatialObjectSourceDefinition def = new SpatialObjectSourceDefinition("mySpatialObject", driver);
factory.addDataSource(def);

NOTA: Las clases FakeObjectDriver y FakeSpatialObjectDriver son drivers ya implementados que están en el paquete com.hardcode.gdbms.engine.data. Su utilidad se reduce a las pruebas automatizadas

1.3.3 Registrar una base de datos.

Si la base de datos contiene datos alfanuméricos, el DataSourceDefinition será un DBTableSourceDefinition. Si también contiene datos espaciales, será un SpatialDBTableSourceDefinition. En ambos casos, los parámetros del constructor son los mismos.

El código que registra una base de datos alfanumérica es:

DBSourceDefinition def = new DBSourceDefinition(null, 0, basedir + "/src/test/resources/testdb", "sa", "", "persona");
DBTableSourceDefinition tableDef = DBTableSourceDefinition("alfanumerica", def, "GDBMS HSQLDB driver"));
factory.addDataSource(def);

El código que registra una base de datos espacial es:

DBSpatialSourceDefinition def = new DBSpatialSourceDefinition(“127.0.0.1”, 5432, “testdb", "root", "", "polygons");
SpatialDBTableSourceDefinition tableDef = SpatialDBTableSourceDefinition("polygonsInPostGIS", def, "GDBMS PostGIS driver"));
factory.addDataSource(def);

1.4 Obtener el DataSource de una fuente de datos.

La instrucción que realiza esta operación es igual en todas las bases de datos:

DataSource source = factory.createRandomDataSource(nombre);

Donde ‘nombre’ es el nombre que le hayamos dado a la definición de la fuente de datos que hemos registrado.

Es posible eliminar el registro del DataSource llamando a su método remove. Esta llamada no tiene efecto en el DataSource pero su nombre ya no estará registrado en el DataSourceFactory y no será posible obtener más instancias de la fuente de datos.

DataSource source = factory.createRandomDataSource(name);
source.remove();

2 Lectura de fuentes de datos

Las operaciones de lectura posibles de una fuente de datos son:

¡¡IMPORTANTE!!: Para realizar cualquier operación de lectura, ES IMPRESCINDIBLE llamar al método start() de DataSource. En caso contrario se producirá un comportamiento indeterminado. Una vez acabadas las operaciones de lectura, hay que llamar al método stop() de DataSource.

2.1 Obtener el valor de un campo.

Para obtener un determinado valor de una fuente de datos hay que pasarle la fila y el índice del campo de dicho valor como parámetros al método getFieldValue() de DataSource.

source.start();
Value v = source.getFieldValue(0, 0);
source.stop();

Obtiene el valor correspondiente al campo 0 en la fila 0.

Value es una interfaz implementada por distintos wrappers sobre los tipos básicos. Véase el Javadoc para más información.

2.2 Obtener los nombres de todos los campos.

Para realizar esta operación se usa el método getFieldNames() de DataSource.

source.start();
String[] nombres = source.getFieldNames();
source.stop();

2.3 Obtener el índice de un campo.

El método getFieldIndexByName() de DataSource devuelve un int con el índice correspondiente al campo enviado como parámetro.

source.start();
int index = source.getFieldIndexByName(“campo”);
source.stop();

2.4 Obtener el numero de filas.

Se puede obtener el número de filas de una fuente de datos mediante el método getRowCount() de DataSource.

source.start();
long rows = source.getRowCount();
source.stop();

2.5 Obtener todos los valores de una fila.

El método getRow() de DataSource devuelve el Value[] correspondiente a la fila enviada como parámetro.

source.start();
Value[] valores = source.getRow(0);
source.stop();

Obtiene el conjunto de valores correspondiente a la fila 0.

2.6 Obtener el tipo de geometrías de la fuente de datos.

En el caso de fuentes de datos espaciales, se puede obtener el tipo de geometría mediante el método getGeometryType() de SpatialDataSource. Este método devuelve un int que puede ser comparado con las constantes de la interfaz SpatialDataSource.

SpatialDataSource spatial = (SpatialDataSource) source;
int type = spatial.getGeometryType();

2.7 Obtener el índice del campo espacial.

En el caso de fuentes de datos espaciales, se puede obtener el índice del campo espacial mediante el método getSpatialFieldIndex().

SpatialDataSource spatial = (SpatialDataSource) source;
int spatialIndex = spatial.getSpatialFieldIndex();

3 Escritura en fuentes de datos

Las operaciones de escritura posibles en una fuente de datos son:

¡¡IMPORTANTE!!: Para realizar cualquier operación de escritura, ES IMPRESCINDIBLE llamar con anterioridad al método beginTrans() de DataSource. Una vez acabadas las operaciones de escritura, éstas se pueden cancelar mediante el método rollBackTrans() o enviarlas mediante el método commitTrans(). En cualquier caso hay que usar uno de los dos métodos al acabar de usar la fuente de datos.

Estas operaciones pueden ser observadas añadiendo un EditionListener al DataSource mediante los métodos DataSource.addEditionListener() and DataSource.removeEditionListener(). Cuando un listener es añadido a un DataSource recibe notificaciones de las operaciones de edición hasta que es eliminado. Si se tiene que realizar una gran cantidad de operaciones puede ser lento o incorrecto notificar a los listeners después de cada simple operación. Para evitar esto se han implementado tres modos de notificación: DISPATCH, STORE e IGNORE. El primero notifica a los listeners después de cada operación simple, el segundo almacena las notificaciones y las envía cuando cambia el modo y el tercero no notifica en ningún caso. Estos modos pueden ser cambiados y obtenidos mediante los métodos DataSource.getDispatchingMode() y DataSource.setDispatchingMode().

3.1 Inserción de un valor de la fuente de datos

En cualquier tipo de fuente de datos se puede insertar un nuevo dato usando el método setFieldValue(). Para crear el valor se debe usar el método estático ValueFactory.createValue() que devuelve el Value correspondiente al dato enviado como parámetro.

source.setFieldValue(0, 0, ValueFactory.createValue("valor"));

3.2 Inserción de una fila vacía.

En todas las fuentes de datos se puede insertar una fila vacía al final de la tabla mediante el método insertEmptyRow() de DataSource.

source.insertEmptyRow();

Pero para insertar una fila vacía en una posición específica de la tabla hay que llamar al método insertEmptyRowAt() haciendo un casting a AlphanumericDataSource o SpatialDataSource y enviarle como parámetro la posición donde se ha de insertar la fila.

AlphanumericDataSource alpha = (AlphanumericDataSource) source;
alpha.insertEmptyRowAt(0);

3.3 Inserción de una fila con valores.

La inserción de filas con valores al final de la tabla se realiza con el método insertFilledRow() de DataSource.

Value[] valores = new Value[] {ValueFactory.createValue("valor1"), ValueFactory.createValue(1) };
source.insertFilledRow(valores);

Al igual que para insertar filas vacías, para insertar filas con valores hay que hacer un casting de la siguiente manera:

SpatialDataSource spatial = (SpatialDataSource) source;
Value[] valores = new Value[] {ValueFactory.createValue("valor1"), ValueFactory.createValue(1)};
spatial.insertFilledRowAt(0, valores);

3.4 Eliminación de una fila.

Para eliminar una fila de una fuente de datos hay que hacer un casting (como en el caso de inserciones de filas en una determinada posición) a AlphanumericDataSource o SpatialDataSource y llamar al método deleteRow() enviándole como parámetro el número de la fila a eliminar:

SpatialDataSource spatial = (SpatialDataSource) source;
spatial.deleteRow(0);

3.5 Deshacer y rehacer.

En cualquier fuente de datos existe la posibilidad de deshacer una acción realizada y posteriormente rehacerla. Para ello, primeramente hay que obtener el DataSource enviando como parámetro del método createRandomDataSource() la constante DataSourceFactory.UNDOABLE. Estas dos operaciones se realizan mediante los métodos undo() y redo() de DataSource. Para saber si en un momento determinado se puede deshacer o rehacer una acción sobre una fuente de datos se puede hacer uso de los métodos que proporciona DataSource: canUndo() y canRedo().

DataSource source = factory.createRandomDataSource(nombre, DataSourceFactory.UNDOABLE);

4 Ejecución de instrucciones SQL.

GDBMS permite la ejecución de instrucciones SQL sobre las fuentes de datos registradas. La ejecución de estas instrucciones se realiza a través del método executeSQL() de DataSourceFactory.

El método executeSQL(), a parte de devolver el DataSource resultante de la instrucción SQL, registra dicho DataSource en el DataSourceFactory. De esta forma, podemos obtener el nombre del DataSource creado para usarlo en posteriores instrucciones SQL. Un ejemplo:

FileSourceDefinition def = new FileSourceDefinition("persona", "csv", "persona.csv");
factory.addDataSource(def);
DataSource source = factory.executeSQL("select apellido from persona;", DataSourceFactory.MANUAL_OPENING);
DataSource source2 = factory.executeSQL("select * from " + source.getName() + " order by apellido asc;", DataSourceFactory.MANUAL_OPENING);

También se puede crear funciones personalizadas para utilizarlas en instrucciones SQL. Hay que seguir dos pasos. En primer lugar, las funciones se han de crear implementando la interfaz Function. Los métodos a implementar son cuatro:

factory.executeSQL(“select max(age) from person; ”, DataSourceFactory.MANUAL_OPENING);
factory.executeSQL(“select sum(lenght(name)) from persona; ”, DataSourceFactory.MANUAL_OPENING);

En segundo lugar, hay qua añadir la clase creada al FunctionManager usando su método estático addFunction(). Un ejemplo:

FunctionManager.addFunction(new MyFunction());

donde MiFuncion es una implementación de la interfaz Function.

Una vez ejecutada esa instrucción, se pueden ejecutar instrucciones SQL como la siguiente:

factory.executeSQL(“select myFunction(name) from persona;”, DataSourceFactory.MANUAL_OPENING);

donde el valor devuelto por al función vendrá determinado por la implementación del método evaluate(Value[] args) de MyFunction.

Por último, cabe destacar que se pueden delegar la ejecución de las instrucciones SQL en el servidor en caso de que el origen de datos resida en un sistema de gestión de base de datos. Es también importante saber que se pueden crear instrucciones personalizadas (custom query) si se quiere realizar una operación distinta a las ya predefinidas "select" y "union"

5 Acceso a metadatos.

Para obtener información sobre los esquemas de un DataSource se puede usar el método DataSource.getDataSourceMetadata(). Este método devuelve una implementación de la interfaz Metadata la cual da acceso a los nombres y tipos de los campos, propiedades de sólo lectura, etc.

Metadata m = ds.getDataSourceMetadata();
for (int i = 0; i < m.getFieldCount(); i++) {
System.out.println(m.getFieldName(i) + ": " + m.getFieldType(i));
}

El valor que devuelve el método getFieldType es una de las constantes de la clase Value y representa el tipo del campo en el sistema de tipos de GDBMS. Para conocer el esquema en términos específicos del driver existe el método DataSource.getDriverMetadata(), que devuelve una implementación de la interfaz DriverMetadata que permite conocer el tipo específico del driver para cualquier campo.

También es posible la modificación del esquema de un DataSource ya creado mediante la eliminación o adición de campos. Para ello se dispone de los métodos DataSource.addField() y DataSource.removeField().

6 Creación de fuentes.

GDBMS da la posibilidad de crear fuentes de datos con aquellos drivers que lo soportan. El método DataSourceFactory.createDataSource() toma como parámetro una implementación de la interfaz DataSourceCreation y crea la fuente especificada por la implementación concreta. FileSourceCreation y DBSourceCreation son las implementaciones usadas para crear ficheros y tablas de base de datos respectivamente. El constructor de ambas implementaciones toma un argumento de tipo DriverMetadata y se puede usar un SpatialDriverMetadata ya que éste extiende a DriverMetadata. Hay dos implementaciones de estas interfaces que pueden ser usadas para crear una fuente de datos desde cero: DefaultDriverMetadata y DefaultSpatialDriverMetadata.

DefaultDriverMetadata ddm = new DefaultDriverMetadata();
ddm.addField("text", "VARCHAR", new String[]{"LENGTH"}, new String[]{"5"});
ddm.addField("int", "TINYINT");
ddm.addField("decimal", "DECIMAL", new String[]{"SCALE", "PRECISION"}, new String[]{"5", "3"});
DBSource dbsd = new DBSource("www.myhost.com", 1234, "testdb", "user", "password", "new_table");
SourceCreation sc = new DBSourceCreation("GDBMS HSQLDB driver", dbsd, ddm);
dsf.createDataSource(sc);
dsf.addDataSource(new DBTableSourceDefinition("newDataSource", dbsd, "GDBMS HSQLDB driver"));
DataSource d = dsf.createRandomDataSource("newDataSource");
d.start();
...
d.stop();

Para crear una fuente de datos con el mismo esquema que otra podemos usar el objeto DriverMetadata del primero para construir el objeto SourceCreation. Sin embargo, esto sólo se puede hacer si las dos fuentes de datos son accedidas por el mismo driver ya que la operación trata con información específica del driver.

DataSource d1 = dsf.createRandomDataSource("existingDataSource");
d1.start();
DefaultDriverMetadata ddm = d1.getDriverMetadata();
d1.stop();
DBSource dbsd = new DBSource("www.myhost.com", 1234, "testdb", "user", "password", "new_table");
SourceCreation sc = new DBSourceCreation("GDBMS HSQLDB driver", dbsd, ddm);
dsf.createDataSource(sc);
dsf.addDataSource(new DBTableSourceDefinition("newDataSource", dbsd, "GDBMS HSQLDB driver"));
DataSource d = dsf.createRandomDataSource("newDataSource");
d.start();
...
d.stop();

7 Fuentes de datos espaciales.

Para acceder información espacial se usa la interfaz SpatialDataSource. A través de ella es posible conocer cual es el índice del campo espacial, el tipo de geometrías que almacena, la extensión del conjunto de los datos, etc.

Es posible crear un índice espacial sobre la fuente de datos y consultarlo para acceder a los datos más rápidamente. Crear un índice es tan facil como llamar al método SpatialDataSource.buildIndex(). Tras la llamada, las filas del DataSource no pueden ser eliminadas y las llamadas a deleteRow() pondrán todos sus campos a nulo pero no borrarán ninguna fila. ¿Qué pasa si estamos editando un DataSource y queremos construir un índice? No hay problema. Las filas se ponen a null durante la edición pero cuando se llama a DataSource.commitTrans() lo primero que se hace es eliminar realmente las filas. Para consultar el índice espacial se debe usar el método SpatialDataSource.queryIndex(). El índice puede ser eliminado explícitamente llamando a SpatialDataSource.removeIndex() o implícitamente cerrando la fuente de datos con los métodos SpatialDataSource.commitTrans() o SpatialDataSource.rollBackTrans().