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 :
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>
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.
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.
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:
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:
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:
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>
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 = ""
}
}
}
}
}