6. Qué es OSGi
OSGi (Open Services Gateway initiative) es la especificación de modularidad para Java que iDempiere adoptó al pasar de ADempiere a iDempiere en 2012 (ver Tutorial 1). En vez de un único classpath gigante con todo el código del ERP, el servidor es un conjunto de bundles — jars (o directorios) con su propio manifiesto de dependencias — que un contenedor OSGi (iDempiere usa Equinox, la implementación de Eclipse) resuelve, arranca y conecta entre sí en tiempo de ejecución. Todo plugin de FINORA LVE es, técnicamente, un bundle OSGi más.
El manifiesto: la identidad y las dependencias de un bundle
Sección titulada «El manifiesto: la identidad y las dependencias de un bundle»Cada bundle declara en META-INF/MANIFEST.MF quién es y qué necesita. Este es el manifiesto real de
com.finora.lve.base:
Manifest-Version: 1.0Bundle-ManifestVersion: 2Bundle-Name: FINORA LVE BaseBundle-SymbolicName: com.finora.lve.base;singleton:=trueBundle-Version: 1.0.0.qualifierBundle-Vendor: DC GEEKS, C.A.Bundle-License: GPL-2.0-onlyBundle-RequiredExecutionEnvironment: JavaSE-17Bundle-Activator: com.finora.lve.base.ActivatorBundle-ClassPath: .Require-Bundle: org.adempiere.base;bundle-version="[13.0.0,14.0.0)", org.adempiere.plugin.utils;bundle-version="[13.0.0,14.0.0)"Import-Package: org.osgi.framework;version="1.9.0", org.osgi.service.event;version="1.4.0"Export-Package: com.finora.lve.base;version="1.0.0", com.finora.lve.base.model;version="1.0.0", com.finora.lve.base.process.setup;version="1.0.0"Service-Component: OSGI-INF/*.xml(finora-lve/plugins/com.finora.lve.base/META-INF/MANIFEST.MF)
Punto por punto:
Bundle-SymbolicNamees el identificador único del bundle en todo el servidor —com.finora.lve.base, no un nombre de clase.singleton:=truesignifica que solo puede haber una versión de este bundle activa a la vez (el patrón normal para bundles de aplicación, a diferencia de librerías que pueden convivir en varias versiones).Require-Bundledeclara una dependencia de otro bundle completo, con un rango de versión:[13.0.0,14.0.0)significa “cualquier 13.x, nunca un 14”. Esto es lo que impide que FINORA LVE 1.x arranque por accidente sobre un iDempiere 14 sin haber sido probado (finora-lve/docs/ARCHITECTURE.md, §2.4).Import-Packagedeclara una dependencia de paquetes específicos, más fino que un bundle entero — útil para depender solo de las interfaces del framework OSGi mismo (org.osgi.framework), sin acoplarse a todo un bundle.Export-Packagees lo contrario: qué paquetes de este bundle pueden importar otros bundles. Un paquete que no se exporta es privado, aunque esté en un jar público.Bundle-Activatores la clase que el contenedor llama al arrancar y detener el bundle (más abajo).Service-Component: OSGI-INF/*.xmlle dice a OSGi dónde buscar los componentes de Declarative Services (siguiente sección) — el comodín permite que cada paquete de trabajo agregue su propio XML sin tener que editar el manifiesto compartido cada vez (finora-lve/docs/ARCHITECTURE.md, §4.1).
El ciclo de vida de un bundle
Sección titulada «El ciclo de vida de un bundle»stateDiagram-v2
[*] --> INSTALLED: se copia a plugins/ y se agrega a bundles.info
INSTALLED --> RESOLVED: Require-Bundle e Import-Package satisfechos
RESOLVED --> STARTING: el contenedor lo arranca
STARTING --> ACTIVE: Activator.start() termina sin error
ACTIVE --> STOPPING: se detiene o se actualiza
STOPPING --> RESOLVED: Activator.stop() termina
RESOLVED --> UNINSTALLED: se retira
Un bundle que declara una dependencia que no existe en el servidor se queda en INSTALLED
(nunca resuelve) — es el error más común al desplegar un plugin nuevo, y casi siempre significa una
versión de Require-Bundle que no coincide con lo instalado.
Activator: código que corre al arrancar el bundle
Sección titulada «Activator: código que corre al arrancar el bundle»public class Activator extends LvePackActivator { @Override protected String[] requiredBundles() { return new String[0]; // base no depende de otro bundle LVE }}LvePackActivator extiende a su vez Incremental2PackActivator del core, que en su método
start() busca los 2Pack empaquetados dentro del propio bundle y los aplica contra la base de datos
(ver Tutorial 8). Este es el patrón que usa FINORA para “instalar
diccionario” sin ningún paso manual: el simple hecho de que el bundle llegue a ACTIVE dispara la
actualización.
Declarative Services: registrar un componente sin tocar el Activator
Sección titulada «Declarative Services: registrar un componente sin tocar el Activator»Escribir toda la lógica en el Activator no escala — cada nuevo event handler, proceso o modelo
tendría que registrarse ahí a mano. Declarative Services (DS) resuelve esto: un archivo XML
describe un componente y sus dependencias de servicios OSGi, y el contenedor lo instancia y conecta
solo, sin que el Activator sepa nada de él.
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" immediate="true" name="com.finora.lve.base.MappedFactories" activate="activate"> <implementation class="com.finora.lve.base.MappedFactories"/> <reference cardinality="1..1" field="mappedModelFactory" interface="org.idempiere.model.IMappedModelFactory" name="mappedModelFactory"/> <reference cardinality="1..1" field="mappedProcessFactory" interface="org.idempiere.process.IMappedProcessFactory" name="mappedProcessFactory"/></scr:component>public class MappedFactories { static final String[] MODEL_PACKAGES = { "com.finora.lve.base.model" }; static final String[] PROCESS_PACKAGES = { "com.finora.lve.base.process.fx", "com.finora.lve.base.process.setup" };
private IMappedModelFactory mappedModelFactory; private IMappedProcessFactory mappedProcessFactory;
public void activate(BundleContext context) { mappedModelFactory.scan(context, MODEL_PACKAGES); mappedProcessFactory.scan(context, PROCESS_PACKAGES); }}El XML dice “cuando existan los servicios IMappedModelFactory e IMappedProcessFactory en el
contenedor, inyéctalos en estos campos y llama a activate”. Ese componente, a su vez, le pide a esas
fábricas del core que escaneen los paquetes de este bundle en busca de clases anotadas
(@Model, @Process, @Callout) y las registren — el mecanismo exacto que usa FINORA LVE para dar
de alta su lógica de negocio (ver Tutorial 9).
Fuentes
Sección titulada «Fuentes»finora-lve/plugins/com.finora.lve.base/META-INF/MANIFEST.MFfinora-lve/plugins/com.finora.lve.base/src/com/finora/lve/base/Activator.javafinora-lve/plugins/com.finora.lve.base/OSGI-INF/com.finora.lve.base.MappedFactories.xmlfinora-lve/docs/ARCHITECTURE.md, §2.4, §4.1 y §8 (versionado lockstep, event handlers OSGi, ADR-LVE-006)- OSGi Alliance — especificaciones
- Documentación de Equinox — Eclipse Foundation