GeoNetwork from Scratch II : Attack of the IDEs

We have already seen how to compile and run a basic GeoNetwork instance. Although we know that real developers will probably skip this step too, for new developers in GeoNetwork, it will be relief to have an IDE to work with. I know that many GeoNetwork developers use NetBeans o Intellij but as I am used to work with Eclipse, that’s what we are going to explore on this post.

First of all: Eclipse has better support for Maven projects on each version. So, to avoid headaches, just download the latest eclipse available.Eclipse has many installer tutorials, so I won’t stop here explaining how to run eclipse. I will just assume you know how to do it.

To run GeoNetwork from eclipse is very very easy. Just right click on the Package Explorer view to import -> As Maven Project over the folder you already had cloned on the last post:

Import As Maven Project
Import As Maven Project
Import As Maven Project
Import As Maven Project

There is still something Eclipse does not support right about GeoNetwork: we have a classes folder that Eclipse tends to misconfigure. So, go to that folder, right click and remove as source folder. To do this, go to the “web-app” project and right click on src/main/webapp/WEB-INF/classes. Select Build Path > Remove From BuildPath.

Then, completely remove the folder from the source code. Don’t worry, it’s git, you can recover it later. You can also do this by right-clicking on the folder and selecting Delete. Yes, you are sure you want to delete folder “classes“.

Now, update as maven project right clicking on the project “web-app” and selecting Maven > Update Project …

update project

Once this finishes, you can restore the folder we previously removed. Go to the “web-app” project, right click on src/main/webapp/WEB-INF and select Replace With >HEAD Revision. Yes, you are sure.

Congratulations! You are ready to use Eclipse to modify GeoNetwork.

But wait, how do we run GeoNetwork inside Eclipse to be able to debug?

We have several approaches here. Remember the jetty command to run GeoNetwork from the console? It is available also inside Eclipse (right click on web-app and Run As > Maven Build) and you can add some maven variables to be able to run in parallel a debug watch to debug your code. You can also set up a Tomcat server inside Eclipse and run GeoNetwork from it. This second option is more easy for beginners, so that’s what we are going to do now.

First, you have to create a Tomcat server inside Eclipse. So, search for the “Servers” tab and right click on it. Select New > Server. You will see a windows offering different types of servers. We will select the Tomcat v.7.0 Server one. You will probably won’t have any server runtime environment configured for it, but you can “Add…” a new one. There are many tutorials[1][2] for this, so we won’t stop here.

On the following window, you can select which applications to run. Obviously, you choose the one called “web-app” and Finish.

Now, you will have a new Server on the Servers tab. select it (left-click) and click on the green arrow just on the top of that tab. You will see on the “Console” tab all the output of GeoNetwork starting up. Once it is started, you can enter GeoNetwork the same way as before, using http://localhost:8080/geonetwork

Have fun customizing GeoNetwork!

GeoNetwork From Scratch I : The Phantom Catalog

GeoNetwork never has been an easy software to work with. But specially after the 3.0 version release, many things have changed. On this series of posts we will try to help new developers start with GeoNetwork.

The source code of GeoNetwork is available on a public repository on Github. This means that you can clone, fork and propose pushes of your custom changes. If you are not familiar with repositories of code or git, you should check this quick manual.

Maven and Java

GeoNetwork is built using maven version 3+. It is written on Java and requires version 7 or more. It works both with OpenJDK or the Oracle version.You will need git and maven installed on your local machine to work. There are several ways to install this on your local machine; for example if you have a Debian based OS (like Ubuntu), you can install them with just this command:

sudo apt-get install maven git

Make sure you installed maven version 3!!

$ mvn –version
Apache Maven 3.2.1 (ea8b2b07643dbb1b84b6d16e1f08391b666bc1e9; 2014-02-14T18:37:52+01:00)
Maven home: ….

Remember that this will also install java on your system. You can check that the version is the right one with the following command:

java -version

So, the very first step once you have your environment set up is clone the GeoNetwork repository on your local machine. That can be done on the command line using the following command inside an empty folder where the source code will be populated:

cd yourEmptyFolder
git clone https://github.com/geonetwork/core-geonetwork.git
git submodule init
git submodule update

As you can see, all the source code shown on github is also available on your local machine now.

Internal Structure of GeoNetwork

The source code of GeoNetwork is split on several smaller maven projects. To run GeoNetwork, you have to build all of them and run the project named “web“.

If you are familiar to maven, you will probably have guessed that you have to run a package install command on the root folder of GeoNetwork source code. But if you try that, maven will warn you that for building GeoNetwork you need more memory than the default memory provided to maven.

This means, you will have to export the maven options to increase the memory like this:

export MAVEN_OPTS=”-Xmx512M -XX:MaxPermSize=256M”

At this point we are not interested in running the tests, so you can skip them using the parameter “-DskipTests”:

mvn package install -DskipTests

At the end of this build (which can take long, depending on your network connection, as it has many third party libraries), you will see something like this:

[INFO] ————————————————————————
[INFO] Reactor Summary:
[INFO]
[INFO] GeoNetwork opensource ……………………….. SUCCESS [ 3.111 s]
[INFO] common utils ……………………………….. SUCCESS [ 13.678 s]
[INFO] Caching xslt module …………………………. SUCCESS [ 7.607 s]
[INFO] ArcSDE module (dummy-api) ……………………. SUCCESS [ 7.860 s]
[INFO] GeoNetwork domain …………………………… SUCCESS [ 33.785 s]
[INFO] Oaipmh modules ……………………………… SUCCESS [ 0.833 s]
[INFO] GeoNetwork Events …………………………… SUCCESS [ 0.654 s]
[INFO] GeoNetwork schema plugins ……………………. SUCCESS [ 4.646 s]
[INFO] GeoNetwork schema plugins core ……………….. SUCCESS [ 5.338 s]
[INFO] GeoNetwork schema plugin for ISO19139/119 standards SUCCESS [ 8.432 s]
[INFO] GeoNetwork core …………………………….. SUCCESS [ 16.304 s]
[INFO] GeoNetwork schema plugin for Dublin Core records retrieved by CSW SUCCESS [ 5.031 s]
[INFO] GeoNetwork schema plugin for Dublin Core standard . SUCCESS [ 8.419 s]
[INFO] GeoNetwork schema plugin for ISO19110 standard …. SUCCESS [ 3.627 s]
[INFO] GeoNetwork CSW server ……………………….. SUCCESS [ 5.546 s]
[INFO] GeoNetwork harvesters ……………………….. SUCCESS [ 3.888 s]
[INFO] GeoNetwork health monitor ……………………. SUCCESS [ 2.489 s]
[INFO] GeoNetwork services …………………………. SUCCESS [ 8.597 s]
[INFO] Geonetwork Web Resources 4 Java ………………. SUCCESS [ 5.261 s]
[INFO] Cobweb Customizations ……………………….. SUCCESS [ 4.226 s]
[INFO] GeoNetwork INSPIRE Atom ……………………… SUCCESS [ 3.990 s]
[INFO] Tests for schema plugins …………………….. SUCCESS [ 2.334 s]
[INFO] GeoNetwork user interface module ……………… SUCCESS [ 35.356 s]
[INFO] JS API and Service documentation ……………… SUCCESS [ 21.203 s]
[INFO] GeoNetwork web client module …………………. SUCCESS [ 47.484 s]
[INFO] GeoNetwork Web module ……………………….. SUCCESS [ 48.490 s]
[INFO] GeoNetwork E2E Javascript Tests ………………. SUCCESS [ 1.645 s]
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 02:19 min (Wall Clock)
[INFO] Finished at: 2015-07-17T10:36:43+01:00
[INFO] Final Memory: 232M/441M
[INFO] ————————————————————————

This will generate a war file you can use in any Java Application Container (server) like Tomcat on web/target/geonetwork.war

Running GeoNetwork

Congratulations! You are ready to run GeoNetwork. To do this, just go to the web folder and run jetty in there:

cd web; mvn jetty:run

After jetty starts, you can see your running GeoNetwork by opening a browser and enter to http://localhost:8080/geonetwork

Continue in Attack of the IDEs

¿Qué es GeoNetwork?

GeoNetwork es una aplicación web que permite mantener un catálogo de datos referenciados geográficamente. Esto es, un portal con buscador que permite visualizar metadatos combinándolos con mapas. Sigue estrictamente los diferentes estándares para datos, desde Inspire hasta OGC. Esto ha propiciado su gran expansión en muchas organizaciones, como el geoportal suizo  o el brasileño,pasando por el neozelandés.

Se despliega sobre un contenedor de aplicaciones java (como tomcat o jetty), funcionando sobre el framework Jeeves . Jeeves se basa en transformaciones XSLT que permiten un desarrollo rápido y sencillo (a la vez que potente) de interfaces tanto para un usuario como para máquinas (XML).  Esto hace que los datos en GeoNetwork sean fácilmente accesibles por diferentes plataformas.

Anotaciones y Decoradores en Java

Las anotaciones o decoradores sobre el código se han vuelto muy comunes en los últimos tiempos. Permiten al programador añadir información útil extra ya sea para comentar mejor el código o para modificar la forma de compilar/ejecutar una clase concreta. Son una extensión a Java para permitir la programación orientada a aspectos.

Las anotaciones pueden ser de tres tipos, según el momento en el que son visibles:

Información para el Compilador

Estas anotaciones permiten al compilador indicar si debe o no omitir errores y warnings o qué hacer con ellos. A nada que se haya trabajado con un IDE Java (como eclipse) se habrán utilizado este tipo de anotaciones de forma natural, por ejemplo usando @Override sobre una función para indicar que está sobreescribiendo una declaración definida en una clase padre. Esta anotación es completamente opcional,  pero permite tanto al compilador como al desarrollador comprobar que efectivamente se está sobreescribiendo una funcionalidad existente por herencia.

Por ejemplo:

public class Parent {     
    public void do(){
        System.out.println("Parent");
     }
}

public class Son extends Parent{     
    usando @Override
    public void do(){
        System.out.println("Son");
     }
}
Anotaciones en Tiempo de Compilación y Despliegue

Estas anotaciones permiten añadir información extra al compilador para que modifique la forma en la que se genera el código de los ficheros .class. Puede servir para modificar clases (añadiendo o modificando funcionalidad respecto a lo descrito en el código fuente), generar clases nuevas (por ejemplo en base a un fichero descriptor), etc...

Estas anotaciones sólo serán visibles en este punto, es decir, no se escribirán sobre los .class y por tanto no se podrán consultar en tiempo de ejecución.

Anotaciones en Tiempo de Ejecución

Estas anotaciones pueden ser consultadas en tiempo de ejecución y funcionan de forma muy parecida a como se utilizaría una interfaz.

Veamos directamente un ejemplo de cómo crear una anotación Runtime y cómo se puede utilizar. La anotación MyAnnotation se podrá aplicar a elementos de tipo field, es decir, a atributos de una clase:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
     public @interface MyAnnotation {
}

Ahora podemos crear una clase que esté anotada con esta anotación:

public class myObject
 {
 @MyAnnotation
 public String field;
 }

De esta forma, en cualquier otra parte del código, podemos comprobar mediante reflexión si un objeto tiene un campo marcado con la anotación:

Class res = objeto.getClass();
for (Field f : res.getFields()) {
     if (f.isAnnotationPresent(MyAnnotation.class)) {
          System.out.println("OK");
      }
}

Más información:

Apuntes del taller de GvSIG 2.0

Estos son los apuntes que tomé sobre gvSIG 2.0 en las últimas Jornadas GvSIG.

Pre-requisitos:

  • Java
  • Eclipse
  • Ant (opcional)
  • Maven (opcional)
  • gvSIG (esto es recursivo :))

La principal ventaja de gvSIG 2.0 es que puedes crear un plugin sin saber cómo funciona gvSIG ni tener que compilarlo. Tenemos una instalación de gvSIG que despliega unos binarios que genera el workspace.. Pero no tenemos que tocar los fuentes de gvSIG, salvo que algo no funcione (bugs) o haya que añadir alguna funcionalidad al núcleo. Preferentemente no lo toques, mejor consulta con los desarrolladores del núcleo de gvSIG y ellos intentarán encargarse.

Con el binario de gvSIG viene un asistente para ejecutarlo y que cree un workspace de eclipse con una plantilla prefijada que deja todo configurado para compilar nuestra extensión. También incorpora un asistente para generar instalables fácilmente. Estos asistentes son accesibles mediante la aplicación gvSIG, en el menú de la aplicación.

Org.gvsig.tools is the basic infrastructure library to develop plugins.The main functionality is focused on the registration of extension points, utilities to separate API, implementations, SPI (service provider), and monitoring tasks (which in version 1.0 used to freeze the application).This library also supports events, persistence, etc …

Una librería es un jar. Cuando nuestra aplicación levante el jar, org.gvsig.tools prepara dicha librería y la inicializa dentro del core de gvSIG. Las clases dentro de la librería implementan la interfaz Library (AbstractLibrary).

Los managers (PluginsManager) son el punto de entrada a las funcionalidades. Son como factorías (singletons) (al menos uno por librería) que levanta instancias de las funcionalidades incluídas dentro de la librería. También guarda la configuración del módulo.

Los locators (PluginsLocator) permiten registrar implementaciones de managers. Nos permiten recuperar la implementación de manager de un API en concreto. "Dame el manager de esta librería."

Un plugin es una pieza que aporta una funcionalidad: botones y barras de herramientas, opciones de menús, proveedores de datos y tipos de documentos. Andami no ha evolucionado demasiado desde la versión gvSIG 1.x. Andami es el framework de los plugins.

El plugin siempre tendrá al menos dos ficheros:

  • config.xml que indica las clases que implementan el plugin, las dependencias y los menús
  • package.info que indica la versión, el nombre, el build,... del plugin.

Una extensión (IExtension) es un conjunto de herramientas asociadas a un plugin metidas en una barra de herramientas o menú y funcionan de forma conjunta. Un grupo de plugins, vaya. La extensión que implemente ExclusiveUIExtension especifica qué herramientas están o no visibles en cada momento, sin tener que tocar código en el core de gvSIG.

Para crear un nuevo plugin, hacemos uso de la herramienta del menú de generación de plugins disponible en la versión de desarrollo de gvSIG. Esto genera el workspace automáticamente e instala dicho plugin en el gvSIG desde el que hemos generado el plugin. Si no tienes versión de desarrollo, tendrás que compilar gvSIG de los fuentes..

Conviene ir haciéndolo mientras se leen estos apuntes o puedes perderte.

El plugin constará de dos proyectos maven: org.gvsig.plugin y org.gvsig.plugin.app. org.gvsig.plugin aportará la funcionalidad de la librería de forma independiente de gvSIG (la lógica de negocio). Puede tener dependencias de librerías de gvSIG, pero debería poder funcionar sin tener que abrir la aplicación. Es decir, no requiere nada de Andami, por ejemplo. En org.gvsig.plugin.app.mainplugin (dentro de org.gvsig.plugin.app) tendrá la implementación de dicha funcionalidad dentro de gvSIG.

The “api” packages should contain interfaces and the “impl” packages should contain implementations.

En principio el workspace está preparado para trabajar con eclipse e importar los proyectos con el plugin de maven. Si hemos cogido una plantilla adecuada al generar los fuentes del plugin, tendremos prácticamente todo el trabajo hecho (salvo la lógica de negocio exacta de nuestro plugin).

Es importante hacerse un proyecto java para probar nuestro plugin, con su propio main, sin tener que arrancar gvSIG. Así mismo se recomienda que en la parte de librería haya tests unitarios. Es decir, podemos hacer una aplicación con toda la potencia de gvSIG, pero sin utilizar gvSIG en sí, es decir, como si gvSIG fuera una potente librería gis. Conclusión: si lo hacemos bien, podríamos incluso utilizar nuestro plugin de gvSIG en otra aplicación... como GoFleet.

Cada plugin tiene su propio instalador, que también se genera con un asistente dentro de la aplicación gvSIG.

Alta Concurrencia

Cuando nos enfrentamos a aplicaciones con una alta concurrencia, solemos encontrarnos con una serie de problemas tipo. En este artículo me centraré en los problemas de recursos (cpu y memoria), dejando los problemas de sincronización para otro futuro artículo. De momento me centraré en los problemas más típicos y sus soluciones más directas.

Cuando descubrimos los hilos y las ventajas del procesamiento en paralelo puede ocurrir que acabemos abusando de su uso. Si el problema es que tenemos una gran cantidad de hilos (¿100? ¿1000?) simultáneos, el procesador estará saltando de uno a otro sin parar, sin dejar que realmente terminen, por muy corta que sea su ejecución real. Y con el tiempo se irán encolando más y más hilos que sólo ralentizarán el proceso. A eso hay que sumarle que el coste de crear y destruir hilos puede llegar a ser significativo cuando hablamos de tantos hilos a la vez.

High Concurrency with the Thread Pool Pattern
High Concurrency with the Thread Pool Pattern

En este caso, al primer método al que debemos recurrir es al Patrón Thread Pool . Este patrón consiste en limitar el número de hilos que hay ejecutándose en un momento dado.
En vez de crear hilos nuevos, creamos tareas, que esperan apiladas. Así mismo, tendremos un pool de hilos que irán cogiendo esas tareas y ejecutándolas lo más pronto posible. Un ejemplo clásico de este patrón se encuentra en la clase SwingWorker. Si queremos implementar "a mano" este patrón, conviene que le echemos un vistazo a la interfaz ExecutorService.

Si tenemos un hilo en background que está haciendo un uso muy intensivo del procesador, pero no nos importa ralentizar su ejecución, podemos hacer uso del comando sleep ( Thread.sleep (...)) para que el hilo libere periódicamente el procesador, permitiendo que el resto de hilos puedan ejecutarse más rápidamente. Esto es útil para hilos que se ejecutan en modo mantenimiento, que deben estar siempre ejecutándose pero que no tienen por qué responder en tiempo real. Otra forma de parar temporalmente un hilo mientras otro se ejecuta, es mediante el método join ( Thread.Join () ), que hace que un hilo espere hasta que el otro hilo termine. Aunque más útil en caso de que tengamos un hilo claramente más prioritario que otro, no es viable si no podemos tener una referencia al hilo más prioritario desde el menos prioritario para indicarle a qué hilo tiene que esperar.

Pero la alta concurrencia no viene dada sólo por el uso del procesador. Puede ocurrir que varios hilos necesiten acceder a grandes cantidades de información de forma casi simultánea. Estos hilos no sólo estarán repitiendo la información en memoria sino que muchas veces estarán repitiendo todo el proceso de extraer dicha información.

Este problema suele estar resuelto en la mayoría de librerías de acceso a datos (base de datos mayormente). Por ejemplo, podemos encontrarnos con el caso de ehcache , que utiliza hilos para almacenar información ( Thread-Specific Storage Pattern ). De esta forma, el acceso y almacenamiento de esta información es compartido, por lo que se disminuye tanto el uso de la memoria necesaria como el tiempo de procesador requerido para extraer y darle forma a la información. Los diferentes hilos que procesarán esta información, irán pidiéndole los datos a ehcache, que se encargará de optimizar estos accesos.

Para mejorar esta solución tenemos las colecciones concurrentes, que permiten a diferentes hilos utilizar los mismos objetos sin presentar problemas de concurrencia.

Existen más soluciones para mejorar la alta concurrencia (sin entrar en optimizaciones del propio código), pero las aquí descritas suelen ser buenas ideas con las que comenzar.

Referencias Útiles:

Easy map on Java

Muchas veces uno no sabe por dónde empezar cuando quiere adentrarse en el mundo de la programación GIS. Demasiadas librerías, demasiados IDE, pero a la hora de la verdad todo el mundo presupone que ya tienes una base y todo es un caos.

Para los principiantes yo recomendaría que le echaran un vistazo a un proyecto bastante nuevo que pretende extender Swing (la librería gráfica por excelencia de Java) con widgets geográficos. De esta forma, añadir un mapa a una aplicación de escritorio Java sería una tarea tan sencilla como añadir un botón o un campo de texto.

Por supuesto, para aplicaciones GIS de cierta complejidad, un simple visualizador como este no sería suficiente. Pero es un buen punto de partida para familiarizarnos con lo que es un mapa y las posibilidades que suele ofrecer al desarrollador.

Con un proyecto Java que añada SwingX-WS a sus dependencias, el siguiente código nos mostraría una ventana con un mapa simple:

es.emergya.gis.examples package;

import java.awt.BorderLayout;

public class SwingWS {

public static void main (String [] args) {
Form = new JFrame JFrame ("Map");

JXMapKit JXMapKit jXMapKit1 = new ();
jXMapKit1.setDefaultProvider (org.jdesktop.swingx.JXMapKit.DefaultProviders.OpenStreetMaps);
jXMapKit1.setDataProviderCreditShown (true);
jXMapKit1.setName ("jXMapKit1") / / NOI18N
jXMapKit1.setAddressLocation(new GeoPosition(41.881944, 39.627778));

form.getContentPane().add(jXMapKit1, BorderLayout.CENTER);

form.pack();
form.setVisible(true);
}
}

Las tiles de los mapas los extrae de OpenStreetMap, pero es perfectamente configurable para cualquier servidor WMS.

Event Listeners Personalizados en Java

Aunque el patrón Observer está implementado nativamente en Java, a veces necesitamos realizar un manejo de eventos que se adapte mejor a nuestras necesidades.

El problema del manejo de eventos es muy sencillo: Tenemos un objeto que va a ir cambiando de estado. Sin tocar su código, debemos ser capaces de "engancharlo" a otros objetos para que estén pendientes de estos cambios de estado y actúen en consecuencia. Este "enganche" debe poder activarse y desactivarse dinámicamente durante la ejecución.

To implement it we will use a static object, a class and an interface: The static object will be responsible for ensuring the relationships between observers and observables, and notify relevant changes to the objects concerned. The class is used to pass information during an event. The interfaces will make the distinction between objects observed and observable objects and any other objects on the application.

Pero la mejor forma de ver cómo funciona es mediante un ejemplo:

Las instancias de la clase MyCustomEvent serán las que porten la información de un observado a un observable cuando se produzca un evento. Esta clase deberá contener toda la información necesaria sobre ese evento. Por ejemplo, si el evento fuera un click del ratón, esta clase debería llevar datos tales como: coordenadas en la pantalla o el número de clicks. En concreto, nuestra clase de evento sólo contiene el objeto original que lanzó el evento.

import java.util.EventObject;
import java.util.LinkedList;
import java.util.List;

public class MyCustomEvent extends EventObject {

  private static final long serialVersionUID = 7383182229898306240L;

  private final MyCustomListener source;

  public MyCustomEvent (MyCustomListener OriginalSource) {
    super (OriginalSource);
    this.source = OriginalSource;
  }

 MyCustomListener public getSource () {
    this.source return;
  }
}

Al objeto estático que guardará las relaciones entre observables y observados lo llamaremos MyCustomEventHandler. Es muy importante la sincronización de los métodos para evitar concurrencias en aplicaciones multihilo:

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class MyCustomEventHandler {
  private static final Log log = LogFactory.getLog(MyCustomEventHandler.class);

  private static Map <MyCustomListener, Set > = new LinkedHashMap observable <MyCustomListener, Set > ();

  public static void fire (MyCustomEvent event) {
    log.trace ("fire (" + event + ")");
    try {
      Observers = getRemarks September (source);
      if (Observers! = null) {
        for (final MyCustomListener pl: Observers) {
          try {
            pl.fire (event);
          } catch (Throwable t) {
            log.error (t, t);
          }
        }
      }
    } catch (Throwable t) {
      log.error (t, t);
    }
  } 

  private static synchronized September getRemarks (MyCustomListener source) {
    observables.get return (source);
  }

/ **
* Register to watch to alert you when observable change
*
* @ Param observer
* @ Param observable
* /
  public static synchronized void register (MyCustomListener observer, MyCustomListener observable) {
    Observers getRemarks September = (observable);
    if (null == observers) {
      observers = new HashSet();
    }
    observers.add(observer);
    observables.put(observable, Observers);
  }

/ **
* Deregister not become observers to alert you when observable
* Modify
*
* @ Param observer
* @ Param observable
* /
  public static synchronized void deregister (MyCustomListener observer, MyCustomListener observable) {
    Observers getRemarks September = (observable);
    if (null == Observers) {
      Observers = new HashSet ();
    }
    observers.remove (observer);
    observables.put (observable, Observers);
  }
}

Tanto los objetos observables como los objetos observadores tienen que implementar la interfaz MyCustomListener. Esta interfaz obliga a implementar el método fire(MyCustomEvent event) . En los observables, esta función será la encargada de llamar a MyCustomEventHandler. En los observadores, esta función será llamada cuando se lance un evento de su objeto observado.

En realidad sería conveniente separar esta interfaz en dos: una para observadores y otra para observados, ya que si un objeto quiere ser a la vez observador y observado podría tener problemas con este sistema. Pero por simplificación para el ejemplo hemos optado por esta implementación.

import java.util.EventListener;

public interface extends EventListener {MyCustomListener
  public void fire (MyCustomEvent event);
}

Si quisiéramos tener diferentes tipos de eventos (parecido a como funciona el MouseListener), lo único que necesitaríamos es ampliar la interfaz MyCustomListener con todas las funcionalidades que queremos que tenga:

import java.util.EventListener;

public interface MyCustomListener extends EventListener {
  public void fireObjectRecycled (MyCustomEvent event);
  public void fireObjectRefreshed (MyCustomEvent event);
  public void fireObjectUpdated (MyCustomEvent event);
}

Así mismo, podríamos usar diferentes clases derivadas de MyCustomEvent para cada una de estas funcionalidades.

El objeto estático MyCustomEventHandler también necesitaría ser modificado para lanzar los diferentes eventos.

Referencias: Hilo donde se discute este tipo de implementación de Event Handler

Cómo funciona la memoria en Java

Como he tenido que empaparme de su funcionamiento, aprovecho y pongo un post sobre conceptos básicos de la memoria en Java.

Nota: Este post es de hace unos años así que la estrategia por defecto de la JVM puede haber cambiado, aunque los conceptos son los mismos.

Heap y Non-Heap

Para empezar, Java divide la memoria en dos segmentos bien diferenciados:

  • Heap: Objetos del usuario, variables, ...
  • Non-Heap/Perm: Código, metadatos, ...

La que más nos interesa es la Heap, porque es la que podemos "controlar". A la memoria Non-Heap (Perm) se le puede configurar el tamaño con el comando MaxPermSize. Pero esto sólo es útil si la aplicación va a cargar o generar dinámicamente muchas clases diferentes.

Generaciones

La memoria heap se divide a su vez en dos generaciones según su tiempo de vida:

  • Young Generation
  • Old Generation

Normalmente la generación joven está compuesta de variables locales y objetos temporales, mientras que la generación vieja suele componerse de estructuras que son necesarias durante toda la ejecución: configuraciones, ventanas gráficas,...A su vez, la generación joven se divide en dos:

  • Eden: Aquí es donde se crean los objetos inicialmente
  • Survivor: Es como el limbo a través del cual se pasa de la generación joven a la vieja. Suele estar compuesto a su vez en dos partes.

Se puede especificar el ratio entre el espacio de la generación joven y el espacio de la generación vieja con el comando de la máquina virtual -XX: NewRatio .

El Recolector de Basura (Garbage Collector)

El Recolector de Basura ( Garbage Collector ) realiza dos tipos de tareas periódicas:

  • Minor Collection: Que revisa de forma rápida la generación joven.
  • Major Collection: Que revisa de forma exhaustiva toda la memoria, fundamentalmente la generación vieja.

El Recolector de Basura se ejecuta a la vez que la ejecución normal del programa, por lo que cada ejecución suya implica una pequeña pausa (de milisegundos normalmente) en todos los threads que se estén ejecutando en ese momento. Mientras la memoria permanezca más o menos liberada, el Recolector de Basura se limitará a las minor collections, para no interferir con el flujo de la aplicación.

Para tener una idea más exacta de cómo trabaja, se puede añadir la opción -verbose:gc a la máquina virtual. Este comando irá imprimiendo cada vez que se ejecute el Recolector de Basura cuanto espacio de memoria ha conseguido liberar.

Estrategias

Existen varias implementaciones del Recolector de Basura, siendo la más común el Serial Collector. Esta implementación, además de ser la más sencilla, utiliza sólo un procesador. Si se está usando una máquina más potente, con varios procesadores y una buena cantidad de memoria física, se puede activar el Parallel Collector , que utiliza varias CPUs a la vez. Esto mejora la forma en la que funciona el Recolector de Basura, pero también puede paralizar a ratos el flujo de ejecución normal de la aplicación.

De todas formas, para el buen funcionamiento y limpieza de la memoria, conviene tener pequeños objetos temporales de vida corta antes que objetos largos y duraderos. Los pequeños objetos temporales se quedarán en el Eden, por lo que se recolectarán mucho antes y de forma mucho más rápida.

Así mismo, tener objetos en memoria sin usar, aunque no molesten a la ejecución del programa, ralentizan la ejecución del Recolector de Basura, porque tendrá que procesarlos una y otra vez en todas sus pasadas.

Forzando la recolección de basura

En algún momento puede parecer tentador forzar una ejecución del Recolector de Basura llamando a System.gc(). Sin embargo, esto lo único que hará será forzar una major collection de forma asíncrona, rompiendo completamente toda la heurística del Recolector de Basura. Es tan desaconsejable que hasta hay una opción en la máquina virtual para desactivar estas llamadas: -XX:+DisableExplicitGC .

Para ayudar a la tarea del Recolector de Basura, existen tres tipos de referencias :

  • Weak: No impide al recolector de basura llevárselo.
  • Soft: Como el weak, pero el recolector de basura lo respeta un poco más y sólo se lo lleva si le hace falta memoria. Útil para cachés, pero puede ser engañoso.
  • Phantom: Siempre devuelve null, no accede al objeto realmente, pero puede servir servir para limpiar antes de que se lleven al objeto que lo enlaza.

Una clase que explica bastante bien el funcionamiento de este tipo referencias es  WeakHashMap: Funciona como un HashMap, pero usando referencias Weak. De esta forma, si las claves que contiene sólo están referenciadas en dicho HashMap, considera que ya no son útiles y las borra.

Referencias útiles:

FastJTable

Las JTables Swing de Java por defecto suelen estar poco preparadas para manejar actualizaciones frecuentes y un gran número de columnas. Basándome en el código de Java Christmas Tree , he creado una extensión ligera de JTable que va bastante más rápida a la hora de manejar gran cantidad de filas.

/**
* Based on Sun's CTTable (Christmas Tree):
* https://java.sun.com/products/jfc/tsc/articles/ChristmasTree/
*
* @author marias
*/

import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;

import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.TableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Based on CTTable from Christmas Tree:
* https://java.sun.com/products/jfc/tsc/articles/ChristmasTree/
*
* @author marias marias@emergya.es
*/
public class FastJTable extends JTable {
private static final Log log = LogFactory.getLog(FastJTable.class);
private static final long serialVersionUID = -3218140266706898440L;

private JScrollPane scrollPane;

public FastJTable(TableModel model) {
super(model);
}

public void updateUI() {
super.updateUI();
setUI(new FastTableUI(this));
}

private static class FastTableUI extends BasicTableUI {

public FastTableUI(FastJTable table) {
super();
installUI(table);
}

@Override
public void installUI(JComponent c) {
// Overriden to install our own CellRendererPane
super.installUI(c);
c.remove(rendererPane);
rendererPane = new FastCellRendererPane();
c.add(rendererPane);
}
}

/**
* FastCellRendererPane overrides paintComponent to NOT clone the Graphics
* passed in and NOT validate the Component passed in. This will NOT work if
* the painting code of the Component clobbers the graphics (scales it,
* installs a Paint on it...) and will NOT work if the Component needs to be
* validated before being painted.
*/
private static class FastCellRendererPane extends CellRendererPane {
private static final long serialVersionUID = 4811773663334451913L;
private JViewport viewport;

public FastCellRendererPane() {
super();
}

// Can be ignored, we don't exist in the containment hierarchy.
public void repaint() {
}

@Override
public void paintComponent(Graphics g, Component c, Container p, int x,
int y, int w, int h, boolean shouldValidate) {
try {
if (c == null || !isVisible(new Rectangle(x, y, w, h))) {
log.trace("No lo pintamos (" + c + ")");
return;
}

// if (p != null) {
// Color oldColor = g.getColor();
// g.setColor(p.getBackground());
// g.fillRect(x, y, w, h);
// g.setColor(oldColor);
// }

if (c.getParent() != this) {
this.add(c);
}

c.setBounds(x, y, w, h);

// As we are only interested in using a JLabel as the renderer,
// which does nothing in validate we can override this to do
// nothing, if you need to support components that can do
// layout,
// this will need to be commented out, or conditionally
// validate.
if (!(c instanceof JLabel))
c.validate();

// JComponent jc = (c instanceof JComponent) ? (JComponent) c
// : null;
// jc.setDoubleBuffered(true);

// Don't create a new Graphics, reset the clip and translate
// the origin.
Rectangle clip = g.getClipBounds(c.getBounds());
g.clipRect(x, y, w, h);
g.translate(x, y);
c.paint(g);
g.translate(-x, -y);
g.setClip(clip.x, clip.y, clip.width, clip.height);
c.setBounds(-w, -h, 0, 0);
} catch (Throwable t) {
log.error("Error al pintar el componente de la tabla ", t);
}
}

/**
* We only paint the visible parts of the JTable.
* @param rectangle visible
* @return if it has to be painted on screen
*/
public boolean isVisible(Rectangle rectangle) {
if (viewport == null)
return true;

			Rectangle visRect = viewport.getViewRect();
			int xmin = ((Double) rectangle.getMinX()).intValue();
			int ymin = ((Double) rectangle.getMinY()).intValue();
			int xmax = ((Double) rectangle.getMaxX()).intValue();
			int ymax = ((Double) rectangle.getMaxY()).intValue();
			return (visRect.contains(new Point(xmin, ymin))
					|| visRect.contains(new Point(xmax, ymin))
					|| visRect.contains(new Point(xmin, ymax)) || visRect
					.contains(new Point(xmax, ymax)));
		}
	}
}