Recomendaciones y trucos para desarrollar con gvSIG 2.1 (1). Recorriendo datos

Hola a todos de nuevo,

De vez en cuando echo un vistazo al código de proyectos gvSIG realizados por otros desarrolladores y veo algunos fragmentos con pequeñas erratas que se repiten a lo largo del código. Algunas ni siquiera pueden considerarse erratas, sino que simplemente sería recomendable hacer las cosas de otra forma. He recogido aquí unas pocas de ellas e intentaré ir recogiendo algunas más para ir contándolas en otros artículos.

En este vamos a ver algunos trucos o buenas prácticas relacionadas con:

  • Liberación de recursos
  • Usar FeatureReference
  • Iterar sobre un conjunto de features

Liberación de recursos

Empecemos por la parte de liberación de recursos.

Cuando estamos creando código que accede a fuentes de datos a través de DAL, hay que tener en cuenta que dependiendo del proveedor de datos que estemos utilizando es importante que liberemos los recursos cuando terminemos de usarlos. Algunos proveedores, como los de acceso a base de datos, mantienen conexiones
abiertas con la base de datos que deberían ser liberadas al terminar de usarlas. En general cualquier “artefacto” que implemente el interface “Disposable” deberíamos cerciorarnos de que lo liberamos al terminar de usarlo.

Veamos un fragmento de código:

...
final List<FeatureReference> tmpFeatures = new ArrayList<FeatureReference>();
boolean showWarningDialog = false;
DisposableIterator it = null;
FeatureSet featureSet = null;

try {
  featureSet = featureStore.getFeatureSet();
  it = featureSet.fastIterator();
} catch (DataException ex) {
  String message = String.format(
    "Error getting feature set or fast iterator of %1",
    featureStore);
  LOG.info(message, ex);
  return;
}
while (it.hasNext()) {
Feature feature = (Feature) it.next();
  if (hasMoreThanOneGeometry(feature)
    || feature.getDefaultGeometry() == null) {
    showWarningDialog = true;
  } else {
    tmpFeatures.add(feature.getCopy().getReference());
  }

}

it.dispose();
featureSet.dispose();

if (showWarningDialog) {
  showWarningDialog();
}
...

En este fragmento de código podemos observar que se crea un “FeatureSet”, al que se le pide un iterador para recorrernos las features de un “FeatureStore”, y al final del proceso se invoca al método “dispose” de ambos para liberar los recursos que estos tengan asociados.

Si el proceso que se realiza va bien, es decir, no se producen errores, no pasará nada y los recursos se liberarán correctamente al ejecutarse las últimas dos líneas… pero ¿Qué sucede si se produce un error durante la ejecución de las líneas previas a invocar al método dispose?

Lo normal es que si el FeatureStore sobre el que se está trabajando es un tabla de una base de datos, deje “pillada” al menos una conexión con la base de datos. Y esta ya no se liberará hasta que se cierre gvSIG. Si el usuario insiste y repite el proceso y le sigue fallando, en un momento dado dejaremos al servidor de base de datos sin conexiones disponibles, y estas no se liberarán hasta que el usuario cierre gvSIG.

El escenario puede llegar a bloquear el acceso a un gestor de base de datos y no solo al usuario de gvSIG.

La recomendación es que cuando se esté trabajando con recursos “disposables” se utilice siempre una construcción “try…finally” tal como se muestra a continuación:

...
Disposable recurso = null;
try {
  ...
  recurso = ...
  ...
} finally {
  DisposeUtils.disposeQuietly(recurso);
}
...

El método “DisposeUtils.disposeQuietly” es un método de utilidad que comprueba si es null el recurso pasado, y solo intenta liberarlo en caso de que no lo sea. Además atrapa los errores y los ignora; bueno, se limita a enviarlos al registro de errores, en caso de que se produzcan al invocar al método “dispose” del recurso.

Si no queremos que se ignoren los errores usaremos “DisposeUtils.dispose”. En el código de ejemplo esto quedaría como:

...
final List<FeatureReference> tmpFeatures = new ArrayList<FeatureReference>();
boolean showWarningDialog = false;
DisposableIterator it = null;
FeatureSet featureSet = null;
try {

  try {
    featureSet = featureStore.getFeatureSet();
    it = featureSet.fastIterator();
  } catch (DataException ex) {
    String message = String.format(
      "Error getting feature set or fast iterator of %1",
      featureStore);
    LOG.info(message, ex);
    return;
  }
  while (it.hasNext()) {
    Feature feature = (Feature) it.next();
    if (hasMoreThanOneGeometry(feature)
      || feature.getDefaultGeometry() == null) {
        showWarningDialog = true;
    } else {
      tmpFeatures.add(feature.getCopy().getReference());
    }
  }

  if (showWarningDialog) {
    showWarningDialog();
  }
} finally {
  DisposeUtils.disposeQuietly(it);
  DisposeUtils.disposeQuietly(featureSet);
}
...

Usar FeatureReference

Cuando tenemos una Feature y queremos guardárnosla para acceder a sus datos más tarde normalmente tendríamos que guardarnos una copia de esta utilizando el método “getCopy”. Sin embargo, a veces no nos interesa guardarnos la feature completa, sino quedarnos con una referencia a ella. Para hacer esto utilizaremos el método “getReference” de la feature.

¿ En qué se diferencia la “feature” de su “referencia” ?
¿ Qué es una “referencia” a una “feature” ?

La “feature” es una estructura de datos que contiene todos los valores de los distintos atributos de esta, mientras que la referencia” es un estructura de datos que contiene la información mínima para poder recuperar la feature completa de su almacén de datos. En ocasiones sera un OID, en otras una clave primaria, siendo el proveedor de datos el encargado de decidir con cual quiere trabajar y de que tipo será ese OID.

Por ejemplo, una referencia a una feature de un shape usa un OID para referenciar a la feature dentro del shape, que normalmente será la posición de la feature dentro del shape, mientras que una feature de una tabla de base de datos tendrá los valores de los campos que constituyen la clave primaria de esa feature.

Tenemos que tener en cuenta que dependiendo de la fuente de datos subyacente, recuperar la información de una FeatureReference puede ser costoso. Las usaremos con cuidado y siendo conscientes de lo que se puede estar haciendo.

Por ejemplo, si tenemos una referencia a una feature de un shape, si invocamos a su método “getFeature”, esto provocará que se vaya a la posición asociada a la feature del shape y se cargue esta en memoria. El coste es asumible sin problemas. Sin embargo, si se trata de una referencia a una feature de una tabla de base de datos, provocará que se lance una consulta contra la base de datos para obtener el registro asociado a la feature y se pueda cargar en memoria. Hay que tener cuidado con esto, ya que si tenemos un List de referencias y las dereferenciamos, provocaremos que se lance una consulta contra la base de datos por cada referencia que tengamos, lo que puede no ser admisible.

Cuando usemos referencias, tendremos que tener en cuenta que recuperar sus features puede ser costoso, y tendremos que valorar si la aproximación que estamos realizando es la adecuada.

Con esto en mente… echemos un vistazo de nuevo al fragmento de código de antes:

...
while (it.hasNext()) {
  Feature feature = (Feature) it.next();
  if (hasMoreThanOneGeometry(feature)
    || feature.getDefaultGeometry() == null) {
      showWarningDialog = true;
    } else {
      tmpFeatures.add(feature.getCopy().getReference());
     }

    }
    ...

Observamos que está recorriéndose las features para guardarse las referencias a algunas features en un List. Sin embargo, para hacerlo hace “feature.getCopy().getReference()”, que provoca la creación de una copia de la feature original para después desecharla y quedarse con su referencia, que será la misma que si se la hubiésemos pedido a la feature original. Podríamos eliminar la obtención de la copia de la feature y pedir a la feature original su referencia y obtendríamos el mismo resultado.

...
while (it.hasNext()) {
  Feature feature = (Feature) it.next();
  if (hasMoreThanOneGeometry(feature)
     || feature.getDefaultGeometry() == null) {
      showWarningDialog = true;
    } else {
      tmpFeatures.add(feature.getReference());
    }

  }
  ...

Ahora observemos otro fragmento de código:

...
private List<FeatureReference> features;
...

int[] currIndexs = getSelectedIndexs();
// If selected is the first row, do nothing
if (currIndexs.length <= 0 || currIndexs[0] == 0) {
  return;
}
List<FeatureReference> selectedFeatures = new ArrayList<FeatureReference>();
for (int i = 0; i < currIndexs.length; i++) {
  FeatureReference selected = null;
  try {
    selected = features.get(currIndexs[i]).getFeature().getReference();
  } catch (DataException ex) {
    LOG.info("Error getting feature", ex);
    return;
  }
  selectedFeatures.add(selected);
}
if (!selectedFeatures.isEmpty()) {
...

El código llena un List de FeatureReference con parte de las referencias obtenidas de otra lista. Nos centraremos ahora mismo en el código:

...
selected = features.get(currIndexs[i]).getFeature().getReference();
...

Si pensamos que está haciendo esto, tendremos que:

  • Primero recuperamos una FeatureReference de la lista de “referencias a features”
  • Luego construimos la “feature” asociada a esa referencia, con lo que eso provoca que se acceda al almacén de fearures subyacente para recuperarla.
  • Y por último le pedimos a la nueva feature su referencia y descartamos la feature.

Todo ello además dentro de un bucle.

Si estamos trabajando con una fuente basada en base de datos, se lanzará una consulta para recuperar cada una de las features, lo que en si puede llevar bastante mas tiempo del deseable. De todos modos, hasta aquí aún tenemos suerte. Tenemos una referencia a la que le pedimos su feature para obtener su referencia. No es necesario recuperar la feature para obtener la referencia, ¡ya la tenemos!. Nos bastaría con guardarnos en “selected” la referencia original:

...
selected = features.get(currIndexs[i]);
...

En el código que estamos viendo se guarda las referencias en un List que luego usará para
mostrarlo en un JTable. Cada vez que el JTable tenga que acceder a los valores de las features a mostrar tendrá que dereferenciarlas para obtener así la feature. Si la tabla tiene muchas líneas, y las features están en base de datos se hará un consumo intensivo de base de datos, un acceso a la base de datos por cada línea de la tabla. Podría suceder que la herramienta no fuese a trabajar nunca con una fuente de datos “pesada” a la hora de recuperar features a partir de sus referencias, pero si no es así tendremos que plantearnos una implementación alternativa para este tipo de problema.

Por último, una cuestión más a tener en cuenta relacionada con las FeatureReference. No se puede garantizar que tras terminar la edición en un FeatureStore, las FeatureReference que te hayas guardado sean válidas. Hay fuentes de datos como shapes o dxfs, que usan como OID la posición de la feature dentro del fichero, y tras terminar la edición este orden puede cambiar. Sin embargo, en otras fuentes de datos como las de BBDD la referencia puede seguir siendo válida dependiendo de su clave primaria.

Iterar sobre un conjunto de features

Cuando queremos recorrer un conjunto de features, normalmente intentamos obtener un iterador y recorrerlo. Sin embargo la recomendación es que siempre que podamos para recorrer los datos usemos un visitor en lugar de un iterador.

Veámoslo con un ejemplo. Retomaremos el fragmento de código que vimos al principio:

...
final List<FeatureReference> tmpFeatures = new ArrayList<FeatureReference>();
boolean showWarningDialog = false;
DisposableIterator it = null;
FeatureSet featureSet = null;
try {

  try {
    featureSet = featureStore.getFeatureSet();
    it = featureSet.fastIterator();
  } catch (DataException ex) {
    String message = String.format(
      "Error getting feature set or fast iterator of %1",
      featureStore);
    LOG.info(message, ex);
    return;
  }
  while (it.hasNext()) {
    Feature feature = (Feature) it.next();
    if (hasMoreThanOneGeometry(feature)
      || feature.getDefaultGeometry() == null) {
        showWarningDialog = true;
    } else {
      tmpFeatures.add(feature.getCopy().getReference());
    }
  }

  if (showWarningDialog) {
    showWarningDialog();
  }
} finally {
  DisposeUtils.disposeQuietly(it);
  DisposeUtils.disposeQuietly(featureSet);
}
...

Veamos que hace:

  • Obtenemos un FeatureSet a partir del FeatureStore
  • Le pedimos un iterador
  • Nos recorremos todas las features y nos quedamos una referencia a ellas.
  • Al final mostramos un mensaje si se ha encontrado multigeometrias o no.

Usando “visitor” podría quedar algo como:

...
final MutableBoolean showWarningDialog = new MutableBoolean(false);
try {
  featureStore.accept(new Visitor() {
    public void visit(Object obj) throws VisitCanceledException, BaseException {
      Feature feature = (Feature) obj;
      if (hasMoreThanOneGeometry(feature)
        || feature.getDefaultGeometry() == null) {
        showWarningDialog.setValue(true);
      } else {
        tmpFeatures.add(feature.getReference());
      }
    }
  });
} catch (BaseException ex) {
  ... exception handling ...
}

if (showWarningDialog.isTrue() ) {
  showWarningDialog();
}
...

Como nos estamos recorriendo todas las features del store, podríamos visitar directamente el store. No pedimos un FeatureSet ni un iterador, así que no tendremos que preocuparnos de liberarlos. Tendremos que preocuparnos de atrapar las excepciones del método “visit”, pero también teníamos que hacerlo cuando creábamos el FeatureSet.

En este fragmento de código he utilizado algo que puede parecer extraño. Habia un flag que se modificaba dentro del bucle, “showWarningDialog”. Como hemos sustituido el cuerpo del bucle por una clase interna anónima, no podemos utilizar una variable “boolean” ya que esta no puede ser final. Así que en lugar de usar un “boolean”, he usado un “MutableBoolean” para almacenar el flag. Esta clase forma parte de la librería de “apache commons lang” que va de base con gvSIG.

Resumiendo

Recomendaciones en general:

  • Que cuando se este trabajando con recursos “disposables” se utilice siempre una construcción “try…finally“, usando “DisposeUtils.disposeQuietly” para liberar los recursos.
  • Una FeatureReference referencia a la feature, no precisas obtener una copia para pedirle la referencia.
  • Dereferenciar una FeatureReference llamando a “getFeature” puede ser un proceso “pesado”.
  • No asumiremos que tras terminar la edición en un FeatureStore las FeatureReference que tengamos guardadas sean válidas.
  • Siempre que podamos, para recorrer los datos usemos un visitor en lugar de un iterador.

Bueno, y por hoy asta aquí llego. Otro día ya contaré sobre algunas otras cosas…

Un saludo a todos!

 

Posted in development, gvSIG Desktop, spanish | Tagged | 1 Comment

gvSIG 2.1: Italian and Bulgarian translations updates

Italian and Bulgarian translations have been updated, and they are now available for the gvSIG 2.1 final version, thanks to Antonio Falciano (Italian), and Zahari Savov and Jordan Tsvetkov (Bulgarian).

If you installed the gvSIG 2.1 final version, you can update these languages now from the add-ons manager. From gvSIG, you have to go to Tools->Addons manager, then select the Installation from URL option and finally you have to select the “Official gvSIG repository for this version” option. When packages appears at the window you have to select the “Internationalization” option at the left side, and then the “Translations” package (1.0.0-27 version).

Translations

After installing it and restarting gvSIG the translation will be updated.

We encourage you to translate the gvSIG interface to new languages or update the existing ones. If you are interested you can contact us (mcarrera at gvsig.com).

Posted in community, english, gvSIG Desktop | 1 Comment

How to work with a form, browsing selected records using scripting in gvSIG 2.1

English translation of the article by Joaquin del Cerro.

Hi everyone…

Yesterday, someone in gvSIG users mailing list asked about how to make something with scripting.

Basically what was intended was to present a custom form using a script that showed the data of the selected record in a layer, and if they had multiple selected records, the buttons first / previous / next / last will be presenting the different selected records.

I´ve added one extra thing and here you can find, one possible implementation: even when there are not selected records, you will be able to move for all the records of the layer.

The idea is that when we load the form, we save the selected records in one list and we will have them easily handy. This action with the selected records could be more or less acceptable, but if we are scrolling all of the records in one table, it could be impossible, because the table can have a vast amount of records and we are not going to load in memory. Also, it could happen if we select thousands of records, but this is less usual.

I will comment only on the piece that is responsible for working with the entire table because it is the “darkest” part. As I said, to load all records from one table in a list may not be feasible for the table size, so in gvSIG, we have the possibility of using a paging mechanism on the table and we show it as an ongoing list, regardless of this “pager” take care of loading and unloading records required depending on the size of page indicated by us. The code would correspond to the LoadFullTable function.

def loadFullTable(table):
  # Obtain a pager based on data table, paging in groups of 200 records.
  pager = DALLocator.getDataManager().createFeaturePagingHelper(table,200)
  # Obtain one paged list from the pager.
  return pager.asList()

The code at the end is very easy. We call a function to which we pass the table and the maximum number of records which we want to be loaded simultaneously in the memory:

pager = DALLocator.getDataManager().createFeaturePagingHelper(table,200)

and then, we ask to the pager that we have obtained, to return us a list based on it.

return pager.asList()

This list can already be handled as if it were a normal list, but being the pager responsible for retrieving records from disk as it needs them.

I will not tell many more things, I think the rest of the code is clear enough if it has already been playing with python and scripting in gvSIG environment.

For the example, I´ve used the layer zonas_basicas_salud (It means health basic zones), available for downloading in:

http://downloads.gvsig.org/download/geodata/vector/SHP2D/zonas_basicas_salud.navarra.zip

I´ve assumed that is the selected layer when the script is launched.
The script result would be something similar to:

resultado

And the full script would be:


from gvsig import *

from org.gvsig.fmap.dal import DALLocator

table = None
data = None
currentRecord=0

def loadRecord():
 codzone = dialog.find("codzone")
 area = dialog.find("area")
 zone = dialog.find("zone")
 sector = dialog.find("sector")
 actual = dialog.find("current")

 record = data[currentRecord]

 dialog.setString(codzone,"text", "%s" % record.get("codzona"))
 dialog.setString(area,"text", record.get("Area"))
 dialog.setString(zone,"text", record.get("Zona"))
 dialog.setString(sector,"text", record.get("Sector"))

 dialog.setString(actual,"text", "%d / %d" %(currentRecord+1, len(data)) )

 #selectRecord(record)

def selectRecord(record):
 selection = table.getFeatureSelection()
 selection.deselectAll()
 selection.select(record)

def loadFullTable(table):
 # Obtain a pager based on data table, paging in groups of 200 records.
 pager = DALLocator.getDataManager().createFeaturePagingHelper(table,200)
 # Obtain one paged list from the pager.
 return pager.asList()

def loadSelection(table):
 data = list()
 selection = table.getFeatureSelection()
 for record in selection.iterator():
 data.append(record)
 return data

def onload(*args):
 global data
 global table
 # For being easier, assume that current layer is zonas_basicas_salud
 layer = currentLayer()
 # Obtain the table related to the layer
 table = layer().getFeatureStore()

 selection = table.getFeatureSelection()
 if selection.isEmpty():
 data = loadFullTable(table)
 else:
 data = loadSelection(table)
 currentRecord=0
 loadRecord()

def next():
 global currentRecord

 if currentRecord<len(data)-1 :
 currentRecord +=1
 loadRecord()

def previous():
 global currentRecord

 if currentRecord>0 :
 currentRecord -=1
 loadRecord()

def first():
 global currentRecord

 currentRecord = 0
 loadRecord()

def last():
 global currentRecord

 currentRecord = len(data)-1
 loadRecord()

Here is the xml of the form, to let us know the references names that I´ve used in the code:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- generated by ThinG, the Thinlet GUI editor -->
<panel columns="1" gap="4">
    <panel columns="2" gap="2">
        <label text="Zone code"/>
        <textfield name="codzone" weightx="1"/>
        <label text="Area"/>
        <textfield name="area"/>
        <label text="Zone"/>
        <textfield name="zone"/>
        <label text="Sector"/>
        <textfield name="sector"/>
    </panel>
    <panel gap="2">
        <label text="  "/>
        <button action="first" name="first" text="First"/>
        <button action="previous" name="previous" text="Previous"/>
        <textfield editable="false" end="3" name="current" start="3" text="0/0"/>
        <button action="next" name="next" text="Next"/>
        <button action="last" name="last" text="Last"/>
        <label text="  "/>
    </panel>
</panel>

Greetings to all and I hope you find it useful

Posted in development, english, gvSIG Desktop, gvSIG development, scripting | 1 Comment

Mapeamento Colaborativo da História de São Paulo (1870-1940)

himacoO grupo Hímaco reúne professores, pesquisadores e estudantes da Universidade Federal de São Paulo e do Arquivo Público do Estado de São Paulo, e tem o objetivo de explorar as possibilidades das geotecnologias em investigações históricas. Desde o início, o grupo optou por trabalhar com o gvSIG, tanto por conta das suas funcionalidades técnicas, como dos valores que ajuda a praticar, voltados à livre circulação do conhecimento e ao trabalho colaborativo.

O trabalho desenvolvido pelo grupo até o momento pode ser conferido em seu site: www.unifesp.br/himaco. Ali, há uma página de download, onde estão disponíveis rasters e vetores referentes ao passado da cidade de São Paulo, bem como um tutorial de introdução ao gvSIG aplicado a estudos históricos. Como estudo piloto, o grupo investigou as principais enchentes na cidade no período que vai de 1870 a 1940, e um mapa temático referente à maior delas, a de 1929, também se encontra disponível.

No momento, o grupo prepara um novo projeto de pesquisa, que representa um desafio bastante grande, tanto em termos técnicos como metodológicos. Esse post tem o objetivo de apresentar a concepção desse projeto e desde já anunciar que vamos precisar de muita ajuda para dar conta dele. Em breve, o projeto estará pronto para ser enviado às agências financiadoras. Até lá, estamos abertos a críticas e sugestões. Depois disso, se conseguirmos o financiamento, mais ainda ;-)

Segue o título e o resumo do mesmo.

Mapeamento Colaborativo da História de São Paulo (1870-1940)

O projeto prevê o desenvolvimento e a disponibilização na rede mundial de computadores de uma base cartográfica digital histórica da cidade de São Paulo, referente ao período de sua modernização urbano-industrial (1870-1940), associada a uma interface que permita a interatividade de pesquisadores interessados, de forma a que estes possam alimentar a base disponibilizada com eventos espacializáveis de suas próprias investigações. Dessa forma, pesquisadores interessados poderão produzir mapas e visualizações de suas respectivas pesquisas, a partir da base fornecida, ao mesmo tempo em que acabarão por enriquecer a base disponibilizada com as informações que terão alimentado ao sistema. Pretende-se, assim, criar as condições para o enriquecimento das abordagens da história de São Paulo daquele período, fazendo-o em conformidade com os mais recentes e interessantes desdobramentos das chamadas humanidades digitais, voltados ao trabalho colaborativo e à livre circulação do conhecimento.

Luis Ferla. Hímaco.

Posted in community, portuguese | Tagged , , | 1 Comment

Control de acceso en gvSIG 2.1.0

Hola a todos!

De nuevo aquí para contaros sobre el sistema de permisos de gvSIG.
En versiones anteriores de gvSIG, cuando un desarrollador tenía que realizar una personalización en la que en función del usuario este tuviese que tener acceso a unas u otras herramientas, o tener acceso de solo lectura a determinadas fuentes de datos, capas o tablas, no había nada especifico en gvSIG que le ayudase a abordar ese desarrollo y tenía que hacerlo él todo. En muchas ocasiones teniendo que reescribir código de gvSIG para introducir sus comprobaciones de permisos.
Con gvSIG 2.1.0 se han introducido mecanismos para gestionar el acceso a las distintas herramientas, así como a fuentes de datos. Poco a poco iremos extendiendo el sistema de permisos a otras funcionalidades de gvSIG como pueden ser :

  • Geoprocesos
  • Páginas de preferencias
  • Documentos del proyecto
  • Acceso a snappers

En este artículo vamos a ver cómo podemos usar estos mecanismos.
Lo primero que hay que tener claro es que gvSIG no viene con una gestión de usuarios integrada.
Pero… ¿Qué quiere decir esto?
Pues simplemente que gvSIG no dispone de herramientas para dar de alta usuarios o asignarles permisos a estos. Normalmente cuando precisamos dotar a gvSIG de un sistema de permisos es porque queremos integrarlo en una organización que ya tiene su propio sistema de gestión de usuarios. Así que no vamos a tener que duplicarlo para usarlo en gvSIG.
En gvSIG se ha definido un API que permite autenticar a un usuario, y comprobar si tiene autorización para realizar determinadas acciones. Este API es muy simple. Consta de dos entidades:

  • SimpleIdentityManager, que nos permite autenticar a un usuario.
  • SimpleIdentity, que representa a un usuario autenticado, y nos permite verificar si este está autorizado a acceder a una acción determinada.

La distribución estándar de gvSIG viene con una implementación base de estos servicios en la que existe un usuario predeterminado, “guest“, que cuando se le pregunta, responde siempre que sí esta autorizado a acceder a un recurso; pero podemos reemplazar esta implementación por una nuestra, y ahí esta lo interesante.

En el proyecto org.gvsig.tools están definidos los interfaces de SimpleIdentityManager y SimpleIdentity, su implementación por defecto, así como alguna clase abstracta para facilitarnos la implementación de estos interfaces, además de disponer en el “locator” de tools de métodos para recuperar y registrar implementaciones de ellos.
Para ilustrar como podemos aportar nuestra implementación del SimpleIdentityManager vamos a preparar un plugin para gvSIG que:

  • Registre una implementación propia de SimpleIdentityManager, la cual valide los usuarios contra una BBDD simple en ficheros “property” en los que se encuentre la información de permisos asociada a cada usuario.
  • Y presente un diálogo de identificación de usuario al arrancar gvSIG y valide este contra el SimpleIdentityManager registrado en gvSIG.

Lo de menos es el soporte usado como BBDD, cada cual tendrá el suyo propio en su organización, lo importante es ver como se encajan las piezas para poder disponer de un control de acceso a acciones y recursos personalizado en gvSIG.

En el SVN del proyecto “templates” del redmine de gvSIG encontraremos los fuentes del proyecto org.gvsig.trivialidentitymanagement, con la implementación que voy a ir comentando. Podéis descargarlo desde :

http://devel.gvsig.org/svn/gvsig-plugintemplates/org.gvsig.trivialidentitymanagement/trunk/org.gvsig.trivialidentitymanagement

En él encontraremos básicamente tres proyectos:

  • org.gvsig.trivialidentitymanagement.app.mainplugin
  • org.gvsig.trivialidentitymanagement.lib.impl
  • org.gvsig.trivialidentitymanagement.lib.api

org.gvsig.trivialidentitymanagement.lib.api
En él tendremos la definición de nuestro API, que debe extender de el de gvSIG.
Tendremos los interfaces:

  • TrivialIdentityManager
  • TrivialIdentity

org.gvsig.trivialidentitymanagement.lib.impl
Aquí tendremos las clases que implementan nuestro API. Tendremos:

  • DefaultTrivialIdentityManager
  • DefaultTrivialIdentity

La implementación de estos es muy sencilla. Solamente hacer mención a unos pocos detalles:

  • El SimpleIdentityManager siempre tiene que devolver, en su método getCurrentIdentity, un SimpleIdentity, nunca null, aunque no haya un usuario registrado. Bien podemos devolver un usuario que responde siempre que está autorizado, o que responde siempre que no lo está. Dependerá de lo que nos interese en nuestra personalización.
  • El método “isAuthorized(actionName)”, recibe un nombre de acción y debe responder si el usuario está autorizado a ejecutar la acción asociada a ese nombre.
  • El método “isAuthorized(actionName, resource,resourceid)“, deberá devolver si el usuario está autorizado o no a realizar la acción indicada sobre el recurso indicado. Actualmente, con gvSIG 2.1.0, solo el acceso a fuentes de datos, DataStore de DAL, utiliza este mecanismos, pasando como recurso el DataParameter asociado a la acción que se intenta realizar. Las acciones que usa el DAL son:
    • create-store
    • open-store
    • read-store

Antes de ver la implementaron de ejemplo vamos a ver en que consistiría la BBDD de ficheros properties que usaremos. Dentro de la carpeta de la instalación del plugin tendremos una carpeta  “dbusers” y en ella un fichero property por usuario, que llamaremos con el nombre del usuario seguido de “.property”. Así, si tenemos un usuario “user01″ tendremos un fichero “user01.properties”.

En el ejemplo tendremos dado de alta un usuario “user01″ con la siguiente configuración:

attr.password=user01
attr.fullname=Test user 01
# El usuario no puede cargar recursos cuyo nombre de fichero termine en dxf
action.dal-read-store.parameter.name=file
action.dal-read-store.parameter.pattern=.*[.]dxf
action.dal-read-store.ifmatches=false
# Tampoco puede acceder a la herramienta de información de un punto.
action.layer-info-by-point=false

La implementación de nuestro ejemplo, básicamente contiene un Map para cada usuario, e indexando por el nombre de la acción obtenemos un booleano que nos indica está autorizado a ejecutar esa acción, diciendo que si están todas las que no conoce:

@Override
public boolean isAuthorized(String actionName) {
    try {
	String value = (String) this.properties.get(ACTION_PREFIX+actionName);
	if( StringUtils.isBlank(value) ) {
	    return true;
	}
	boolean  b = BooleanUtils.toBoolean(value);
	return b;

    } catch(Throwable th) {
	return true;
    }
}

El método que comprueba si tenemos acceso a un recurso, es algo mas complicado, pero también muy básico. Solo gestiona recursos de DAL, así que el recurso es siempre un DataParameter. Para cada acción tiene la siguiente información:

  • name, un nombre de parámetro del DataParameter que recibe.
  • pattern, una expresión regular que aplicar sobre el parámetro indicado.
  • ifmatches, un valor booleano que nos indica si debemos retornar true o false cuando el valor del parámetro concuerde con la expresión regular.

Con algo tan simple ya podemos restringir el acceso a los recursos en función del usuario. Vamos a ir viendo poco a poco el código de este método:

@Override
public boolean isAuthorized(String actionName, Object resource, String resourceName) {
    try {

	if( resource == null ) {
	    return this.isAuthorized(actionName);
	}
	if( !DataManager.CREATE_STORE_AUTHORIZATION.equalsIgnoreCase(actionName) &&
	    !DataManager.READ_STORE_AUTHORIZATION.equalsIgnoreCase(actionName) &&
	    !DataManager.WRITE_STORE_AUTHORIZATION.equalsIgnoreCase(actionName) ) {
	    // Si no es una accion conocida no la tratamos de forma especial
	    return this.isAuthorized(actionName);
	}
	if( !(resource instanceof DataParameters) ) {
	    return true;
	}
	...

Lo primero que hacemos es comprobar si hemos recibido un recurso. Si no lo hemos recibido delegamos en el método isAuthorized que solo recibe el nombre de acción.
Luego nos cercioramos que la acción que se quiere realizar es una de las que soporta nuestro sistema de autorización. En nuestro caso solo soportamos las peticiones de acciones de DAL, así que si no es ninguna de ellas también delegamos en el método isAuthorized que solo recibe el nombre de acción. Y por ultimo comprobamos que el recurso es del tipo DataParameters, que es con los que vamos a trabajar, y si no lo es, como no sabemos manejarlo, simplemente decimos que sí que esta autorizado.

Sea cual sea el tipo de validación que utilicemos estas primeras lineas serán siempre muy parecidas, mientras que el resto de lineas del método ya serán muy dependientes de contra qué y cómo realicemos la comprobación de si un usuario tiene o no autorización para realizar una acción sobre un recurso.

Vamos a ver muy rápido el código del ejemplo:

        ...
	String ifmatchesValue = (String) this.properties.get(ACTION_PREFIX+actionName+".ifmatches");
	if( StringUtils.isBlank(ifmatchesValue) ) {
	    return true;
	}     

	DataParameters params = (DataParameters) resource;
	String parameterValue = null;
	String parameterPattern = (String) this.properties.get(ACTION_PREFIX+actionName+".parameter.pattern");
	if( StringUtils.isBlank(parameterPattern) ) {
	    return true;
	}
	String parameterName = (String) this.properties.get(ACTION_PREFIX+actionName+".parameter.name");
	if( StringUtils.isBlank(parameterName) ) {
	    return true;
	}
	if( parameterName.equalsIgnoreCase("all") ) {
	    parameterValue = params.toString();
	} else {
	    if( resource instanceof FilesystemStoreParameters && parameterName.equalsIgnoreCase("file") ) {
		parameterValue = ((FilesystemStoreParameters) resource).getFile().getAbsolutePath();
	    } else {
		if( params.hasDynValue(parameterName) ) {
		    Object x = params.getDynValue(parameterName);
		    if( x == null ) {
			return true;
		    }
		    parameterValue = x.toString();
		}
	    }
	}
	...

Lo que hace en estas lineas de código es recuperar los valores de los tres datos, “name“, “pattern” y “ifmatches” asociados a la acción que se ha solicitado, y luego intenta recuperar de los DataParameter el valor indicado por “name“, usando como casos especiales los nombres de parámetro “all” y “file“.

Una vez ya hemos recopilado esta información, la cosa es ya mas simple:

        ...
	if( StringUtils.isBlank(parameterValue) ) {
	    return true;
	}
	if( !parameterValue.matches(parameterPattern) ) {
	    return true;
	}
	return BooleanUtils.toBoolean(ifmatchesValue);

    } catch(Throwable th) {
	return true;
    }
}

Comprobamos si concuerda el valor del parámetro con la expresión regular y devolvemos el valor de “ifmaches” en caso de que lo haga.

Hay algún detalle más como los métodos:

  • getAttributeNames, que devuelve una lista de nombres de atributos extra que tiene asociado ese usuario
  • O getAttribute, que nos permite recuperar el valor de un atributo extra del usuario.

Pero no son de mucha importancia, y se explican por si solos.

org.gvsig.trivialidentitymanagement.app.mainplugin
Bueno, pues una vez ya tenemos nuestras clases …. ¿cómo le decimos a gvSIG que las utilice?

Para eso tendremos que crear un plugin y dejar en el algo de configuración adicional.
Por un lado crearemos en el plugin una clase que implemente Runnable, en la que meteremos el código de inicialización de nuestro sistema de permisos. Y por otro deberemos crear en el raíz de nuestro plugin en la carpeta de la instalación el fichero “identity-management.ini”. Este fichero solo precisa de dos lineas, aunque puede contener mas información si la precisamos para nuestra implementación. Vemos esas dos lineas;

IdentityManager=org.gvsig.trivialidentitymanagement.impl.DefaultTrivialIdentityManager
IdentityManagementInitializer=org.gvsig.trivialidentitymanagement.Initializer

La entrada IdentityManager, contendrá el nombre de la clase a utilizar como la implementación del SimpleIdentityManager de gvSIG. Esta clase y todo lo que precise deberá estar en el class-path de nuestro plugin. gvSIG, al iniciarse, comprobara si algún plugin tiene este fichero “identity-management.ini”, y utilizara la clase indicada en la entrada IdentityManager para cargarla y registrarla en el locator de tools. De esta forma cualquier porción de gvSIG que acceda al locator de tools para recuperar el SimpleIdentityManager obtendrá el nuestro.

La otra entrada, IdentityManagementInitializer, contendrá el nombre de una clase de nuestro plugin que implemente el interface runable. Una vez registrado nuestro SimpleIdentityManagement, se cargara esa clase y se invocara a su método run para que esta se encargue de realizar las inicializaciones que precisemos para que nuestro sistema funciones correctamente. Veamos que tenemos en el código de nuestro ejemplo:

public void run() {
    TrivialIdentityManager identityManager = (TrivialIdentityManager) ToolsLocator.getIdentityManager();
    PluginsManager pluginsManager = PluginsLocator.getManager();
    PluginServices plugin = pluginsManager.getPlugin(this);

    // Initialize the folder database  for the TrivialIdentityManager
    File pluginFolder = plugin.getPluginDirectory();
    File dbfolder = new File(pluginFolder, "dbusers");
    identityManager.setdbFolder(dbfolder);

    // Show login dialog
    // Do not return if user cancel login.
    LoginDialog loginDialog = new LoginDialog(pluginFolder);
    loginDialog.showDialog();
    logger.info("User logged as '"+identityManager.getCurrentIdentity().getID()+"'.");
}

Como vemos, hace basicamente dos cosas:

  • Inicializar en nuestro TrivialIdentityManager la BBDD donde esta almacenada la información de permisos.
  • Presentar un cuadro de diálogo para autenticar al usuario.

A partir de aquí, y según sea nuestra aplicacion y organización podemos adaptar este proyecto de ejemplo para que valide contra una BBDD relacional, un servicio web o un servidor de LDAP. No debería ser complicado hacerlo. La única precaución a tener es que la información de los permisos que tiene un usuario debería cargarse al instanciar la clase SimpleIdentity y cuando se llame a los métodos isAuthorized trabajar contra esa información ya en memoria. Si tenemos que acceder a un servicio externo cada vez que se compruebe si el usuario tiene permiso para acceder a una acción puede relentizarse mucho la ejecución de gvSIG.

Vamos a ver el proyecto de ejemplo en acción.

Con la configuración que hemos visto el usuario  user01 no podrá cargar ficheros DXF ni podrá acceder a la herramienta de información.

Con el plugin org.gvsig.trivialidentitymanagement.app.mainplugin instalado, al arrancar gvSIG nos presentra la siguiente pantalla:

login

Nos identificaremos con usuario “user01″ y clave “user01″ y continuara la carga de gvSIG.
Si una vez arrancado gvSIG intentamos cargar un fichero DXF nos presentara un mensaje diciendo que no estamos autorizados a realizar esa acción:

no-autorizado-dxf

Y si cargamos una capa vectorial, observaremos que no tenemos la herramienta de información.

sin-herramienta-de-informacion
Bueno, espero que os sirva, y a ver cuando tengo otro ratito y cuento algún que otro truquito que me queda en la manga sobre el control de acceso en gvSIG 2.1.0.

Me gustaría agradecer los comentarios de Juan Carlos Gutiérrez que en su momento me ayudaron en el diseño al hacerme participe de sus necesidades relacionadas con el control de acceso a los datos en gvSIG.

Un saludo a todos

Posted in development, gvSIG Association, gvSIG Desktop, spanish | Tagged | 2 Comments

MOOC Cycle “GIS for Users” free of charge

índiceThe gvSIG-Training e-Learning platform opens its registration period for the MOOC cycle “gvSIG for Users” in English, offered by gvSIG Association, in collaboration with GISMAP.

This cycle is made up of three different Modules:
Module 1: “Introduction to GIS” (starting on the 4th of May 2015)
Module 2: “Layer Editing” (starting on the 25th of May 2015)
Module 3: “Raster Analysis” (starting on Autumn 2015)

It will lead participants to how to use and exploit the potentiality of the open source software gvSIG while performing the most common GIS activities. This Course is both addressed to beginners and skilled GIS users who want to learn how to use this software.

The Course is open in continuous form and each module requires a participants engagement of around thirty hours.

Participants can thus plan individually the time to allocate to the course and complete all the scheduled activities without interfering with their daily work.

This Course is completely free of charge except for those participants asking for the corresponding credits of the gvSIG user certification program from the gvSIG Association, that is submitted to the payment of 40 Euros for each module or 100 Euros for the whole cycle.

For further information about topics, goals…:
http://web.gvsig-training.com/index.php/es/quienes-somos-2/noticias-2/145-the-free-mooc-cycle-gvsig-for-users

For registration, you have to press “Enroll” at the corresponding Module, and then accept the “Site policy agreement”. Finally you will have to register at the web page, or login if you were registered already.

Posted in english, gvSIG Desktop, training | Tagged | 4 Comments

Nueva edición del curso gratuito “Introducción a Scripting en gvSIG 2.1″

linux-python-logoEl objetivo de este MOOC es el de dar a conocer el potencial de la programación geoespacial, la posibilidad de crear nuevas herramientas, nuevos geoprocesos o análisis de datos, que nos aumentarán la potencia de gvSIG adaptándose a nuestras necesidades. También la automatización de tareas, que nos podrían generar un ahorro de tiempo y de trabajo considerable.

El curso estará en modalidad de inscripción gratuita y abierta desde el 4 de Mayo de 2015, pudiendo inscribirse o completarse en el momento en que el alumno lo desee. De este modo el curso estará disponible de forma permanente.

Para realizar este curso no se necesitan conocimientos previos sobre programación, será un nivel básico, además de explicar cada línea de código. El curso está realizado con el lenguaje de programación Python, el favorito para comenzar a programar, muy intuitivo y rápido de aprender.

Realizar este curso es completamente gratuito. Aquellos que lo completen y quieran recibir un Certificado de Aprovechamiento, correspondiente a 30 créditos del programa de Certificación de gvSIG, solo tendrán que aportar una contribución de 40 Euros, además de realizar un proyecto personal sobre un Script en gvSIG que será subido al Repositorio y estará disponible para toda la comunidad.

Más información sobre el temario, inscripción, etc.: http://web.gvsig-training.com/index.php/es/quienes-somos-2/noticias-2/140-massive-online-open-course-de-introduccion-a-scripting-en-gvsig-2-1

Para poder matricularse al curso se debe entrar en “Matriculación” al final de la página, y aceptar después el “Acuerdo con las Condiciones del Sitio”. Finalmente se debe realizar el registro.

Posted in development, gvSIG Desktop, scripting, spanish | Tagged , | 1 Comment

Nueva edición del Plano de Toledo del Greco con gvSIG

00_greco

Con este post os queremos presentar el trabajo realizado por uno de los colaboradores más activos del proyecto gvSIG, el profesor Cesáreo Bas Vivancos, director del Máster Universitario en Valoración, Catastro y Sistemas de Información Territorial  y de la Cátedra gvSIG. Junto con Rafael del Cerro Malagón, ha elaborado la nueva edición del plano de Toledo del Greco como aporte significativo a la celebración del IV Centenario del Greco.

La metodología seguida mediante el uso de gvSIG puede consultarte en el siguiente texto.

00_Greco2Un excelente ejemplo del uso de gvSIG con cartografía histórica.

Posted in community, gvSIG Desktop, spanish | Tagged | 3 Comments

Manual gvSIG: llamamiento a traductores

Thank-you-post-it_XoombiTras el lanzamiento de gvSIG 2.1 nos quedaba una asignatura pendiente, la actualización del manual de gvSIG. Tarea que estamos aprovechando no sólo para revisar y actualizar sus contenidos, sino también para aplicar un estilo uniforme a toda la documentación de usuario.

Es una tarea laboriosa debido a que gvSIG es hoy día una aplicación con un elevado número de funcionalidad. El objetivo es que esté disponible para la salida de gvSIG 2.2.

Lo que pedimos es vuestra colaboración para ayudarnos a que esté disponible en otros idiomas, y al menos nos gustaría que estuviera en inglés.

Para ello hemos estructurado el manual de forma que cada voluntario pueda traducir pequeños módulos de forma rápida. Por poco que podáis traducir estaréis haciendo un gran aporte a esta tarea.

Os animamos a todos los que tengáis un nivel bueno de inglés a colaborar y ayudar a que la documentación de gvSIG sea accesible al mayor número de usuarios.

Todos los interesados contactar con: info@gvsig.com

Posted in community, gvSIG Desktop, spanish | 2 Comments

Primer curso online de gvSIG para usuarios sobre la última versión, gvSIG 2.1

Tras la publicación de la última versión de gvSIG, está ya disponible el primer curso online en la plataforma gvsig-training.com sobre dicha versión, gvSIG 2.1.

En este curso se incluyen tanto las funcionalidades de las versiones anteriores como las nuevas herramientas incluidas en gvSIG.

Entre las novedades más importantes de esta versión se incluye la creación de librerías de símbolos propias, a partir de símbolos creados con cualquier programa de dibujo. Una funcionalidad muy interesante también es que una vez creada la librería, se puede generar un paquete de instalación que puede ponerse a disposición de los distintos usuarios de una organización. También se han incluido una gran cantidad de librerías disponibles para descargar desde la propia aplicación, relativas a emergencias, medio ambiente, criminología, etc.

En este curso el usuario aprenderá a crear vistas con cartografía, tanto local como remota, tanto ráster como vectorial, a crear capas nuevas, a realizar análisis básico (áreas de influencia…), y a crear sus propios mapas con su leyenda, escala, norte…, entre otras cosas.

El curso comenzará a partir del día 7 de abril, y las inscripciones siguen abiertas en el portal gvsig-training.com. Para realizar la inscripción se debe acceder a http://web.gvsig-training.com/index.php/es/cursos/online/actuales/product/42-curso-de-gvsig-para-usuarios-idioma-espanol-internacional-10a-edicion

Completando el curso se obtiene el Certificado Usuario Básico de la Asociación gvSIG, y permite acceder al Certificado Experto, a través de los otros cursos disponibles.

¡Os esperamos!

 

Posted in community, gvSIG Desktop, spanish, training | 2 Comments