Usando PersistenceFactory para persistir muchas clases con un interface comun

Hola a todos.

El otro día un compañero me planteo un problema relacionado con la persistencia del proyecto. Tenia un montón de clases que implementaban un interfaz Filtro, con un montón quiero decir mas de 100, y todas ellas debían persistir los mismos atributos en el proyecto. Además estas implementaciones de Filtro, vivían en librerías, jars, distintos que se desplegaban en la aplicación en varios plugins.

Para su primera aproximación, lo que había echo es persistir los atributos comunes a todas junto con el nombre de la clase, y luego al levantarlos usaba Class.forName(filterClassName). El problema es que si la clase vivía en otro plugin de gvSIG el Class.forName fallaba.

En principio, todo el mecanismo de persistencia diseñado alrededor de la persistencia del proyecto en gvSIG esta pensado para que estas cosas no ocurran, y nunca deberíamos guardar nombres de clase en los atributos de nuestros objetos al persistirlos en el proyecto.

Para que algo sea persistente podemos:

Para nuestro caso, lo que necesitamos es una factoría de persistencia. En principio están pensadas para:

  • Dotar de persistencia a objetos que no podemos tocar, por ejemplo no podemos hacer que la clase Color de java implemente Persistent. Por ejemplo, en el proyecto org.gvsig.fmap.mapcontext se implementa la factoria ColorPersistenceFactory para gestionar la persistencia de java.awt.Color.
  • También podemos utilizarlas para persistir objetos que implementan un interfaz común, como por ejemplo IProjection. En gvSIG existe una sola factoria de persistencia para todas las implementaciones de proyecciones, ProjectionPersistenceFactory.
  • O para dotar de persistencia a un conjunto de clases cuales sean. No tienen por que implementar un interfaz común, ni siquiera tener un conjunto de atributos común Por ejemplo en los test de org.gvsig.tools.lib podemos encontrar la clase AwtFactory.

En nuestro caso deberíamos tener una factoría de persistencia por librería, jar, que aporte un filtro, y en su Library, registrarla en el manager de persistencia. Siguiendo con lo del Color, en el doPostInitialize de MapContextLibrary se registra el ColorPersistenceFactory.

Para implementar factorías de persistencia podemos:

  • Implementar directamente el interfaz PersistenceFactory, Tiene métodos como:
    • public boolean manages(Object obj)
    • public boolean manages(PersistentState state)

    Que deberían responder true si manejan la persistencia del objeto. Y otros como:

    • public Class getManagedClass(PersistentState state)
    • public Class getManagedClass(Object object)
    • public String getManagedClassName(Object object)

    Que nos dicen que clase java representa a un objeto dado.

    Con esto tendríamos el control para que cada librería indique que clases es capaz de levantar y no tendríamos problemas con los class-paths ya que maneja Class y no nombres de clase.

  • Extender de AbstractSinglePersistenceFactory que nos da ya casi todo hecho, solo tendríamos que implementar un par de métodos; pero no creo que nos valga ya que esta pensado para que la factoría trabaje con una clase o interfaz, y aunque pueda parecer que en nuestro caso tenemos un interfaz, como necesitaríamos tener varias factorías para ese interfaz, no nos valdría ya que cada factoría debería responder, no al interfaz común, si no solo a las implementaciones individuales que es capaz de levantar.
  • Extender de AbstractMultiPersistenceFactory. Este caso seria una buena aproximacion a nuestro problema. Actualmente no conozco ningún uso en gvSIG de esta clase abstracta, aunque en org.gvsig.tools.lib tenemos un test que la usa, AwtFactory , y no la usa exactamente para lo que precisaríamos nosotros ahora.

Me voy a extender un poco mas sobre esta ultima opcion ya que parece ser la mas adecuada para lo que necesitamos.

En nuestro caso precisaríamos extender de el e implementar tres métodos:

  • public Object createFromState(PersistentState state)
  • public void saveToState(PersistentState state, Object obj)
  • protected void makeDefinitions()

En el makeDefinitions se crearía la definición de persistencia invocando a addDefinition y creando nuestra definición de forma similar a como lo haríamos para cualquier objeto que implementase Persistent ; pero además deberemos llenar la variable de instancia “classes” con la lista de objetos Class que la factoría vaya a gestionar, y esto ya no es tan simple ya que hay que mantener la coherencia con un Map auxiliar que se usa internamente.

A la clase AbstractMultiPersistenceFactory le faltaría disponer de un método para ir añadiendo clases a una definición (por cierto, se lo añado ya para próximos usos 🙂 ), pero podemos implementarlo en nuestra factoría. Seria algo como:

protected void addManagedClass(Class managedClass, DynClass definition ) {
    if( !this.classes.contains(managedClass) ) {
        this.classes.add(managedClass);
    }
    this.nameToClass.put(managedClass.getName(), managedClass);
    this.nameToClass.put(definition.getFullName(), managedClass);
    if( definition.getNamespace().equals(PersistenceManager.PERSISTENCE_NAMESPACE) ) {
        this.nameToClass.put(definition.getName(), managedClass);
    }
}

Esto nos permitiría añadir varias clases con la misma definición de persistencia. Por ejemplo, si tuviésemos una factoría que fuese a gestionar tres implementaciones del interfaz Filter, FilterA, FilterB y FilterC podríamosacer en el método makeDefinitions() algo como:

protected void makeDefinitions() {
  // Creamos la definicion de persistencia asociandola a la primera clase FilterA
  DynStruct definition = this.addDefinition(FilterA.class, 
      "FilterNoseque", "Filtros de noseque") 

  definition.addDynField....
  ...

  // Ahora asociamos las demas clases a la misma definicion de persistencia.
  this.addManagedClass(FilterB.class,definition);
  this.addManagedClass(FilterC.class,definition);

}

Ojo con el “FilterNoseque”, ya que ese “Noseque” deberá ser distinto en cada factoría que creemos para cada librería. De alguna manera debería identificar el grupo de filtros que gestiona esa factoría de persistencia.

Ahora echemos un vistazo a los otros dos métodos, createFromState y saveToState. Nuestra factoría, en el createFromState lo primero que debería hacer es identificar de que clase filtro se trata, y en función de ello levantarla y rellenar sus propiedades. Pero esto no es tan simple, ya que solo hay una definición de persistencia para todos los filtros que soporta nuestra factoría. Para identificarla tendremos que persistir, además de los atributos del filtro, un identificativo de la clase. En este punto podríamos decidir persistir el nombre de la clase y usar un Class.forName para levantarla, ya que la factoría solo debe manejar lo que este implementado en su librería, así que no tendría problemas con el class-path. Sin embargo no soy partidario de usarlo ya que dificulta la identificación de las dependencias entre las clases. Personalmente prefiero mantener un Map que me devuelva la Class a partir del nombre de esta, lo me dejara constancia de las dependencias dentro de la librería. Añadiremos a la factoría una variable de instancia myfilters que inicializaremos en el makeDefinitions() que podría quedar algo como:

protected void makeDefinitions() {
  // Creamos la definicion de persistencia asociandola a la primera clase FilterA
  DynStruct definition = this.addDefinition(FilterA.class, 
      "FilterNoseque", "Filtros de noseque"); 

  definition.addDynFieldString("filterName");
  definition.addDynField....
  ... ;

  // Ahora asociamos las demas clases a la misma definicion de persistencia
  // y creamos nuestro map.

  this.myfilters = new HashMap();
  Class[] myclasses = {
      FilterA.class,
      FilterB.class,
      FilterC.class
  };
  for( int i=0; i<myclasses.length ; i++ ) {
      this.addManagedClass(myclasses[i],definition);
      this.myfilters.put(myclasses[i].getName(), myclasses[i]);

  }
}

Una vez tenemos inicializado myfilters, nuestros métodos createFromState y saveToState podrían quedar algo como:

public void saveToState(PersistentState state, Object obj)
            throws PersistenceException {
    Filter filter = (Filter) obj;
    state.set("filterName", filter.getClass().getSimpleName());
    // Seguimos persistiendo el resto de propiedades de nuestro filtro
    state.set(...  
    ...
}

public Object createFromState(PersistentState state)
            throws PersistenceException {
    String filterName = state.getString("filterName");
    Class filterClass = (Class) this.myfilters.get(filterName);
    if( filterClass==null ) {
            throw new PersistenceException("Can't manage filter '"+filterName+"'.");
    }
    // Ahora creamos e inicializamos ya nuestro filtro.
    Filter filter = (Filter) filterClass.newInstance();
    filter.setXXX( state.get("XXX"));

    return filter;
}

Si lo hacemos así, cada factoría solo maneja las clases para las que ha sido creada y nunca se levanta una instancia de clase por nombre, con lo que solo se crean objetos con los que nuestro código ha sido compilado. La responsabilidad de levantar otros filtros recaerá en otra factoría que sabe hacerlo, y no esta.

Las implementaciones que he puesto son solo ideas, cada cual que las adapte según lo que necesite.

Bueno, supongo que con esto, ya es ponerse a hacer pruebas.

Un saludo

Joaquin

About Joaquin del Cerro

Development and software arquitecture manager at gvSIG Team. gvSIG Association
This entry was posted in development, gvSIG Desktop, spanish. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s