Translations of this page:

Creación de Plugins

Desarrollar Plugins de Osmius es realmente sencillo. Para ello hemos seleccionado Groovy como el lenguaje de programación idóneo, gracias a su facilidad de uso y aprendizaje.

Por otra parte, en el equipo de Osmius hemos desarrollado un miniSDK que sirve de guía a la hora de realizar el desarrollo de un nuevo plugin. Puede descargarse el SDK de la siguiente dirección:

Como podrá comprobar, realmente hay muy pocas clases y muy poquitos métodos dentro del miniSDK. Hemos intentado que prime la sencillez a la hora desarrollar un nuevo plugin que haya pocas cosas que aprender y que la creación de un nuevo plugin sea muy fácil y directa.

Al final, un plugin es un fichero zip que consta de :

  • un fichero plugin.xml que describe al plugin
  • un fichero .jar que contiene las clases que ejecutan las acciones a la hora de instalar el plugin.
  • ficheros adicionales a la instalación del plugin utilizados por las clases del fichero .jar según disponga el desarrollador.

Lo primero que le vamos a comentar es el fichero plugin.xml. Tiene la siguiente forma:

<plugin>
  <idnPlugin>TestPlugin</idnPlugin>
  <clsName>org.osmius.plugins.TestPlugin</clsName>
  <desPlugin>Plugin de ejmplo</desPlugin>
  <idnVersion>1.0</idnVersion>
  <jarPlugin>plugin.jar</jarPlugin>
  <dtiCreated>2010-04-12 15:50:32.350 CEST</dtiCreated>
  <txtAuthor>Develover</txtAuthor>
  <txtMail>developer@peopleware.es</txtMail>
  <txtUrl>www.osmius.com</txtUrl>
  <txtKey></txtKey>
  <idnFree>1</idnFree>
  <txtFiles>plugin.jar</txtFiles>
</plugin>
  • idnPlugin : nos indica el identificador único del plugin. No puede haber dos plugins con el mismo identificador
  • clsName : nos indica el nombre completo de la clase que la consola debe cargar para su ejecución. Los plugins deben empaquetarse dentro de org.osmius.plugins
  • desPlugin : una descripción de lo que hace el plugin (1024 caracteres máximo)
  • idnVersion : la versión del plugin
  • jarPlugin : el fichero jar que contiene las clases del plugin
  • dtiCreated : fecha de creación del plugin
  • txtAuthor : el Desarrollador
  • txtMail : correo electrónico del desarrollador
  • txtUrl : Página Web del desarrolador
  • txtKey : Developer key (no usado por el momento)
  • idnFree : Plugin gratuito o de pago
  • txtFiles : Ficheros que van dentro del zip a excepción de plugin.xml

Este fichero será usado por la consola para la gestión de los plugins cargados, así como por el plugin para las actualizaciones, control de versión, etc.

Entrando ya a nivel de código, empezaremos explicando dos clases muy básicas, Message y OsmiusResponse y que son la clave de la comunicación entre el plugin y la consola de Osmius.

  • Message es una clase que está compuesta por 3 propiedades: messageID, langcode y description.

Como veremos más adelante, el proceso de instalación de plugins está basado en el intercambio de mensajes. Un plugin instalado correctamente debe devolver siempre el código PLG-00000, de este modo la consola sabrá que se ha finalizado la instalación del plugin y terminará el proceso. El campo langcode indica en el lenguaje que debe devolverse la descripción del mensaje dentro del campo description.

  • La clase OsmiusResponse sólo tiene dos campos: error y message. error es un boolean que nos sirve para determinar si una operación ha sido errónea o no. Para el mensaje con código PLG-00000 el campo error del objeto OsmiusResponse será false.

Mediante estas dos clases es fácil establecer un protocolo de intercambio de mensajes entre la consola y el plugin. Cuando decidamos cargar un nuevo plugin en la consola, está entrará en un proceso de invocar el método de execute de forma indefinida mientras no se den dos condiciones: que dicho método retorne error = true en el objeto OsmiusResponse o que el messageID tenga valor PLG-00000.

A la hora de desarrollar un plugin, este debe cumplir obligatoriamente una interface, que hemos denominado OsmiusPlugin. Esta interface define los 2 siguientes métodos:

  • public OsmiusResponse execute(Connection conn, ClassLoader classLoader, Locale locale, String version, String workingDir,String messageID);
  • public int getMesageID(String messageID);

El método getMessageID lo único que hace es convertir nuestro mensaje PLG-00000 en un valor numérico, 0 en este ejemplo, que es mucho más fácil de ser tratado por ejemplo en una sentencia switch.

El método execute es realmente el importante. Es en este método dónde el desarrollador deberá implementar la lógica de su plugin. Como se puede observar, este método retorna un objeto OsmiusResponse, que indicará a la consola el resultado de las acciones ejecutadas por el plugin.

Por otra parte, el método execute recibe 6 parámetros:

  • Connection conn: es una conexión a la base de datos de osmius para poder ejecutar sentencias SQL, bajo la responsabilidad del desarrollador del plugin.
  • ClassLoader classloader: es un típico class loader de java que le pasa la consola al plugin para poder cargar ficheros de recursos de una manera fácil.
  • Locale locale: parámetro para determinar en que idioma se deben retornar los mensajes de ok/error.
  • String version: la versión de la consola, para poder determinar si el plugin puede o no ejecutarse en determinada versión de consola.
  • String workingdir: directorio de trabajo de plugin, donde puede efectuar las operaciones que desee, como descomprimir archivos, etc.
  • String messageID: inicialmente este objecto es null. Dependiendo de la ejecución del plugin, por ejemplo puede tratarse de una actualización, el plugin devuelve un objeto OsmiusResponde con error false y con un messageID PLG-XXXXX, la consola mostrará el texto de dicho mensaje, que será del tipo ¿quiere actuarlizar ….?. En caso de aceptar, la consola enviará de nuevo al plugin dicho messageID PLG-XXXXX, con lo que el plugin podrá reconducir la instalación del plugin desde ese punto en concreto.

Para facilitar el desarrollo, el equipo de Osmius proporciona la clase OsmiusPluginBase de la que deben extender todos los nuevos plugins y que proporciona métodos adicionales de ayuda al desarrollo del plugin, así como nuevos métodos abstractos que el desarrollador debe implementar y que sirven para guiar al desarrollo del mismo. Los métodos son:

  • public abstract boolean checkConsoleVersion(String version) : método para testear la version de la consola
  • public abstract State getStatePluginVersion(Connection conn,String pluginName, String pluginVersion) : método para testear el estado del plugin, no instalado, misma version, version anterior, nueva version
  • public String getDBPluginVersion(Connection conn, String pluginName): método para obtener de base de datos la versión actualmente instalada

También tenemos un enum, State, para determinar los posibles estados de un plugin: NOT_INSTALLED, SAME_VERSION, MINOR_VERSION, MAYOR_VERSION

Con todo esto, un posible ejemplo de implementación de un plugin sería:

class TestPlugin extends OsmiusPluginBase {
   OsmiusResponse execute(Connection conn, ClassLoader classLoader, Locale locale, String version, String workingDir, String messageID) {
      def messages = ResourceBundle.getBundle("org_osmius_plugins_Plugin", locale, classLoader) // Cargamos el fichero de mensajes con el locale correspondiente
      def pluginXML =  new XmlSlurper().parseText(new File(classLoader.getResource("plugin.xml").toString().toURI()).text) // Cargamos el fichero plugin.xml
      String pluginVersion = pluginXML.idnVersion  // Determinamos la versión del plugin
      OsmiusResponse response = new OsmiusResponse(false, new Message("PLG-00000", locale.getLanguage(), messages.getString("PLG-00000")));  // Creamos un objeto Osmius Response por defecto con error false y messageID PLG-00000
      try {
	 if (checkConsoleVersion(version)) { // Primero chequeamos la versión de la consola que nos llega como parámetro para determinar si podemos instalar el plugin en esa versión de consola
	    int msgID = getMesageID(messageID);  // Comprobamos el messageID, la primera vez que nos invocan desde la consola será nulo
	    switch (msgID) {
	       case -1: // Primera vez : messageID null -> -1
	          def state = getStatePluginVersion(conn, "org.osmius.plugins.Plugin", pluginVersion)
	          switch (state) {
	             case State.NOT_INSTALLED:
	                 // Instalar el plugin mediante las acciones necesarios
	                break
	             case State.SAME_VERSION: // ERROR: El plugin ya está instalado
	                response.setError(true);
	                response.setMessage(new Message("PLG-01001", locale.getLanguage(), messages.getString("PLG-01001")))
	                break
	             case State.MINOR_VERSION: // ERROR: Está intentando instalar una versión anterior
	                response.setError(true);
	                response.setMessage(new Message("PLG-01002", locale.getLanguage(), messages.getString("PLG-01002")))
	                break
	             case State.MAYOR_VERSION: // NO ERROR - Construimos un mensaje para informar al usuario de la consola si desea instalar esta nueva versión
	                response.setError(false);
	                response.setMessage(new Message("PLG-01003", locale.getLanguage(), messages.getString("PLG-01003")))
	                break
	          }
	          break
	       case 1003:
	          // Hacer la acción/es que deseemos
	          break
	       default:
	          break
	    }
	 } else { // ERROR : El plugin no puede ser instalado en esta versión de la consola
	    response.setError(true);
	    response.setMessage(new Message("PLG-01000", locale.getLanguage(), messages.getString("PLG-01000") + version))
	 }
      } catch (Exception e) {  // Error genérico
	 response.setError(true)
	 response.setMessage(new Message("PLG-00001", locale.getLanguage(), messages.getString("PLG-00001") + e.printStackTrace()))
      }
      return response;
   }
   boolean checkConsoleVersion(String version) {
      boolean value = true
      if (version < "v10.04") { //Sólo trabaja con consolas posteriores a la versión v10.04
	 value = false
      }
      return value
   }
   State getStatePluginVersion(Connection conn, String pluginName, String pluginVersion) {  // Método de ayuda para determinar la versión de plugin actualmente instalada
      State value = State.NOT_INSTALLED
      def version = getDBPluginVersion(conn, pluginName)  // Método de ayuda que se conecta a BB.DD. y detecta la versión de plugin actualmente instalada
      if (version == "") { // No plugin installed
	 value = State.NOT_INSTALLED
      } else if (version == pluginVersion) {
	 value = State.SAME_VERSION
      } else if (pluginVersion < version) {
	 value = State.MINOR_VERSION
      } else if (pluginVersion > version) {
	 value = State.MAYOR_VERSION
      }
      return value
   }
}

Para facilitar aún más las cosas, desde el equipo de desarrollo de Osmius hemos creado una clase para poder instalar plugins de Agentes de forma genérica. Lo único que debe realizar el desarrollador es primeramente desarrollar el agente siguiendo las instrucciones de desarrollo de agentes de Osmius y luego utilizando está clase (sin tener que reescribirla) modificar las propiedades de un nuevo fichero osmiusPluginAgent.xml para que el agente pudiera ser instalado de forma automática en la consola de Osmius vía plugin.

El fichero osmiusPluginAgent.xml tiene la siguiente estructura:

  <osmiusAgent>
    <typeAgent>JBOSS001</typeAgent>
    <typePlatforms>LINUX001</typePlatforms>
    <sqlNewFile>NEW_JBOSS001.sql</sqlNewFile>
    <sqlUpdateFile>UPDATE_JBOSS001.sql</sqlUpdateFile>
    <logoFile>logojboss001.gif</logoFile>
    <binaryFiles>JBOSS001_LINUX001.zip</binaryFiles>
  </osmiusAgent>
  • typeAgent : nos indica el nuevo tipo de Agente/Instancia que vamos a desplegar
  • typePlataforms : plataforma sobre la que se ejecuta el agente (Windows, linux,…)
  • sqlNewFile : fichero con las sentencias SQL necesarias para la correcta instalación del Agente en la plataforma de Osmius (creación de tipo de agente, tipo de instancia,…)
  • sqlUpdateFile : fichero con las sentencias SQL necesarias para una actualización del Agente.
  • logoFile : imagen que sigue el patrón de las definidas en Osmius para identificar visualmente el tipo de agente/instancia (29×20 pixels)
  • binaryFiles : es un zip que contiene los ficheros necesarios para la correcta ejecución del agente.

La clase OsmiusPluginAgent es muy similar a la clase TestPlugin que hemos puesto de ejemplo, pero se le han añadido métodos adicionales para la gestión automática de alta/modificadión de agentes en la consola.

class OsmiusPluginAgent extends OsmiusPluginBase {
   OsmiusResponse execute(Connection conn, ClassLoader classLoader, Locale locale, String version,
                          String workingDir, String messageID) {
      def messages = ResourceBundle.getBundle("org_osmius_plugins_OsmiusPluginAgent", locale, classLoader)
      def pluginXML =  new XmlSlurper().parseText(new File(classLoader.getResource("plugin.xml").toURI()).text)
      String pluginVersion = pluginXML.idnVersion
      String pluginName = pluginXML.idnPlugin
      OsmiusResponse response = new OsmiusResponse(false, new Message("PLG-00000", locale.getLanguage(), messages.getString("PLG-00000")));
      NodeChild xmlagent = new XmlSlurper().parseText(new File(classLoader.getResource("osmiusPluginAgent.xml").toURI()).text)
      try {
         if (checkConsoleVersion(version)) { // First: Check version

            int msgID = getMesageID(messageID);

            switch (msgID) {
               case -1: // First Time : messageID null -> -1
                  def state = getStatePluginVersion(conn, pluginName, pluginVersion)
                  switch (state) {
                     case State.SAME_VERSION: // ERROR: OsmiusPluginAgent is already installed
                        response.setError(true);
                        response.setMessage(new Message("PLG-01001", locale.getLanguage(), messages.getString("PLG-01001")))
                        break
                     case State.MINOR_VERSION: // ERROR: Trying to install a previous version
                        response.setError(true);
                        response.setMessage(new Message("PLG-01002", locale.getLanguage(), messages.getString("PLG-01002")))
                        break
                    case State.NOT_INSTALLED: // NO ERROR - INFO Message to install version
                       response.setError(false);
                       response.setMessage(new Message("PLG-02001", locale.getLanguage(),
                          xmlagent.typeAgent.toString() + " " + messages.getString("PLG-02001") + " " +
                                  xmlagent.typePlatforms.toString()))
                       break
                     case State.MAYOR_VERSION: // NO ERROR - INFO Message to install new version
                        response.setError(false);
                        response.setMessage(new Message("PLG-02002", locale.getLanguage(),
                          xmlagent.typeAgent.toString() + " " + messages.getString("PLG-02002") + " " +
                                  xmlagent.typePlatforms.toString()))
                        break
                  }
                  break
               case 2001: // Install New Agent
                  conn.autoCommit = false;

                  try  {
                    File sqlfile = new File(workingDir + "/" + xmlagent.sqlNewFile.toString())

                    executeSqlFile(conn, response, sqlfile)

                    insertBinariesAgent(conn, response, workingDir, xmlagent)

                    insertLogoAgent(conn, response, workingDir, xmlagent)

                    conn.commit()

                  } catch (Exception e) {
                    conn.rollback()
                    response.setError(true)
                    response.setMessage(new Message("PLG-00001", locale.getLanguage(), messages.getString("PLG-00001")
                              + e.toString()))

                    return response
                  }

                  response.setError(false);
                  response.setMessage(new Message("PLG-00000", locale.getLanguage(), messages.getString("PLG-00000")))

                  break

              case 2002: // Update Agent
                conn.autoCommit = false;

                try  {
                  File sqlfile = new File(workingDir + "/" + xmlagent.sqlUpdateFile.toString())

                  executeSqlFile(conn, response, sqlfile)

                  insertBinariesAgent(conn, response, workingDir, xmlagent)

                  insertLogoAgent(conn, response, workingDir, xmlagent)

                  conn.commit()

                } catch (Exception e) {
                  conn.rollback()
                  response.setError(true)
                  response.setMessage(new Message("PLG-00001", locale.getLanguage(), messages.getString("PLG-00001")
                          + e.toString()))

                  return response
                }

                response.setError(false);
                response.setMessage(new Message("PLG-00000", locale.getLanguage(), messages.getString("PLG-00000")))

                break

               default:
                break
            }
         } else { // ERROR : This plugin can not be installed on this Console version
            response.setError(true);
            response.setMessage(new Message("PLG-01000", locale.getLanguage(), messages.getString("PLG-01000")
                    + version))
            return response
         }
      } catch (Exception e) {
          response.setError(true)
          response.setMessage(new Message("PLG-00001", locale.getLanguage(), messages.getString("PLG-00001")
                 + e.toString()))
          return response
      }

      return response;
   }

   boolean checkConsoleVersion(String version) {
      boolean value = true
      if (version < "v10.04") { //Only works with v10.04
         value = false
      }
      return value
   }

   State getStatePluginVersion(Connection conn, String pluginName, String pluginVersion) {
      State value = State.NOT_INSTALLED

      def version = getDBPluginVersion(conn, pluginName)
      if (version == "") { // No plugin installed
         value = State.NOT_INSTALLED
      } else if (version == pluginVersion) {
         value = State.SAME_VERSION
      } else if (pluginVersion < version) {
         value = State.MINOR_VERSION
      } else if (pluginVersion > version) {
         value = State.MAYOR_VERSION
      }
      return value
   }

   void insertBinariesAgent(Connection conn, OsmiusResponse response, String workingDir, NodeChild xmlagent) {

      Sql sql = new Sql(conn)

      String [] binaries = xmlagent.binaryFiles.toString().split(",")

      byte[] bytesarr

      String typAgent = xmlagent.typeAgent.toString();

      binaries.each { binary ->
        String typPlatform = binary.substring(binary.indexOf("_") + 1,binary.indexOf("."));
        bytesarr = (new File(workingDir + "/" + binary)).readBytes();
        sql.executeUpdate("update osm_typplatform_typagent_d set BIN_AGENT = ? where typ_agent = ? and typ_platform = ?",
                [bytesarr,typAgent,typPlatform])
      }

    }

    void insertLogoAgent(Connection conn, OsmiusResponse response, String workingDir, NodeChild xmlagent) {
      File source = new File(workingDir + "/" + xmlagent.logoFile.toString())
      File destination = new File(workingDir + "/../../images/" + xmlagent.logoFile.toString())
      copyFile(source,destination)
    }

    void copyFile(File source, File destination) {
      def reader = source.newReader()
      destination.withWriter {writer ->
         writer << reader
      }
      reader.close()
    }

    void executeSqlFile(Connection conn, OsmiusResponse response, File sqlfile) {

      Sql sql = new Sql(conn)
      String sqlexec = ""
      String line = ""

      sqlfile.withReader { reader ->
        while (null != (line = reader.readLine())) {
          if (line.size() == 0 || line[0] == '-')
            continue;
          sqlexec = sqlexec + " " + line
          if (sqlexec[sqlexec.size()-1] == ';') {
            sqlexec = sqlexec.trim()
            String [] words = sqlexec.split(" ")
            if (0 == words[0].compareToIgnoreCase("update")) {
              sql.executeUpdate(sqlexec)
            }
            else
              sql.execute(sqlexec)

            sqlexec = ""
          }
        }
      }
   }
}
 
desarrollo/plugins.txt · Última modificación: 2010/12/22 13:22 (editor externo)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki