¿Cómo deserializar un objeto de bytes en Osgi?

En mi aplicación osgi tengo tres paquetes, travel.api , table.api y utils . travel.api depende de table.api que depende de utils . Tenga en cuenta que travel.api no depende directamente de utils . Utilizo aQute Bnd para generar los manifiestos y creo que está funcionando bien. Los manifiestos se muestran a continuación.

Hay una clase llamada PageData que tiene un campo de tipo TableData , que a su vez tiene un campo de tipo TestObject . PageData se encuentra en travel.api , TableData se encuentra en table.api y TestObject se encuentra en utils . Todo esto funciona bien cuando se cargan los paquetes. El problema viene cuando recibo una matriz de bytes que representa un objeto PageData . Tengo que deserializarlo en el paquete travel.api . Esto no debería ser un problema ya que es donde se define. Uso org.jboss.netty.handler.codec.serialization.ObjectDecoderInputStream y paso el cargador de clases del paquete travel.api . La excepción que se muestra a continuación es lanzada pero básicamente dice:

 Caused by: java.lang.ClassNotFoundException: com.openaf.utils.TestObject not found by travel.api [9]. 

Ahora esto tiene sentido porque si mira el Import-Package de travel.api para travel.api verá que com.openaf.utils (donde se encuentra TestObject ) no está en la lista. Si agrego este paquete entonces está correctamente deserializado. Sin embargo, esto no parece ser una buena solución general, ya que tendría que revisar todos los campos que utiliza PageData y garantizar que todos se importen en este módulo, y recursivamente en todos los campos que contienen esos campos, etc.

¿Estoy haciendo algo completamente mal aquí?

¿Cuál es la mejor manera de deserializar un objeto cuando se usa OSGi?

Si lo hago correctamente y tengo que especificar todas las importaciones “profundas”, ¿hay alguna forma de hacer que Bnd haga una generación “profunda”?

Cualquier ayuda sería muy apreciada!

Estoy usando felix v4 como mi librería osgi.

 Manifest-Version: 1 Bnd-LastModified: 1355404320862 Bundle-ManifestVersion: 2 Bundle-Name: travel.api Bundle-SymbolicName: travel.api Bundle-Version: 0 Created-By: 1.7.0_07 (Oracle Corporation) Export-Package: com.openaf.travel.api;uses:="scala.runtime,scala,scala.c ollection,com.openaf.pagemnager.api,scala.reflect,com.openaf.table.api ";version="0.0.0" Import-Package: com.openaf.pagemnager.api,com.openaf.table.api,scala,sc ala.collection,scala.reflect,scala.runtime Tool: Bnd-1.44.0 Manifest-Version: 1 Bnd-LastModified: 1355404158858 Bundle-ManifestVersion: 2 Bundle-Name: table.api Bundle-SymbolicName: table.api Bundle-Version: 0 Created-By: 1.7.0_07 (Oracle Corporation) Export-Package: com.openaf.table.api;uses:="scala.runtime,scala,scala.co llection,scala.reflect,scala.collection.immutable,scala.collection.gene ric,com.openaf.utils";version="0.0.0" Import-Package: com.openaf.utils,scala,scala.collection,scala.collection .generic,scala.collection.immutable,scala.reflect,scala.runtime Tool: Bnd-1.44.0 Manifest-Version: 1 Bnd-LastModified: 1355404158801 Bundle-ManifestVersion: 2 Bundle-Name: utils Bundle-SymbolicName: utils Bundle-Version: 0 Created-By: 1.7.0_07 (Oracle Corporation) Export-Package: com.openaf.utils;uses:="scala.runtime,scala,scala.collec tion,scala.reflect";version="0.0.0" Import-Package: scala,scala.collection,scala.reflect,scala.runtime Tool: Bnd-1.44.0 java.io.InvalidClassException: failed to read class descriptor at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1585) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1964) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1888) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1964) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1888) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) at org.jboss.netty.handler.codec.serialization.ObjectDecoderInputStream.readObject(ObjectDecoderInputStream.java:115) at com.openaf.rmi.common.DefaultObjectEncoder$.decode(RMICommon.scala:33) at com.openaf.rmi.client.ClientHandler.messageReceived(ClientPipelineFactory.scala:43) at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:296) at org.jboss.netty.handler.codec.frame.FrameDecoder.unfoldAndFireMessageReceived(FrameDecoder.java:363) at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:345) at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:211) at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268) at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255) at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:94) at org.jboss.netty.channel.socket.nio.AbstractNioWorker.processSelectedKeys(AbstractNioWorker.java:372) at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:246) at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:38) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) at java.lang.Thread.run(Thread.java:722) Caused by: java.lang.ClassNotFoundException: com.openaf.utils.TestObject not found by travel.api [9] at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1460) at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:72) at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1843) at java.lang.ClassLoader.loadClass(ClassLoader.java:356) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at org.jboss.netty.handler.codec.serialization.ClassLoaderClassResolver.resolve(ClassLoaderClassResolver.java:30) at org.jboss.netty.handler.codec.serialization.CachingClassResolver.resolve(CachingClassResolver.java:39) at org.jboss.netty.handler.codec.serialization.CompactObjectInputStream.readClassDescriptor(CompactObjectInputStream.java:55) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583) ... 28 more 

Gracias, Nick.

¿Esto realmente suena como un serio defecto en la deserialización? Un deserializador decente debe usar el cargador de clases de la clase que causa la carga. El cargador de clases dado solo debe usarse para el objeto de nivel superior ya que aún no hay un objeto principal.

Entonces, en este caso, el cargador de clases dado se usa para cargar PageData. El cargador de PageData se usa para cargar TableData, y el cargador de TableData debe usarse para cargar TestObject. No hay ninguna razón lógica por la que esto deba fallar a menos que el deserializador que usa realmente tenga daños cerebrales, ya que este es el modelo que usa la VM para cargar clases. Me sorprende que el deserializador de Java haga esto, considero que este comportamiento es un error grave, ya que utiliza reglas diferentes a las de la máquina virtual.

La serialización es un problema en OSGi porque la modularidad consiste en ocultar clases de implementación; La deserialización tiene una tendencia a querer acceder a estas clases privadas, la antítesis de la modularidad. Sin embargo, hay muy buenas soluciones para esto (que no incluye Dynamic-ImportPackage, que está volviendo al infierno de JAR de una manera más complicada y costosa que el simple uso de Java simple). El truco básico es tener un objeto raíz de una API pública que tenga acceso a las clases privadas / transitorias necesarias. Hmm, ¿no suena esto como un servicio?

Solución

Observando cómo son las personas negativas acerca de esto, un pequeño ejemplo de cómo puede resolver el problema con la serialización de Java (es decir, ObjectInputStream y ObjectOutputStream). En tu pregunta, mencionas ObjectDecoderInputStream, una clase con la que no estoy familiarizado.

La configuración es:

 Bundle A: class aA { B b; } (import b) Bundle B: class bB { C c; } (import c) Bundle C: class cC { } 

Así que primero serializamos un objeto:

 ByteArrayOutputStream bous = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bous); oos.writeObject(this); oos.close(); 

Ahora la parte difícil. Anulamos el método resolverObject, esto nos da la oportunidad de realizar la carga correcta de la clase …

 ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bous.toByteArray())) { Set lhs = new LinkedHashSet(); { // Keep a set if discovered class loaders lhs.add(getClass().getClassLoader()); } @Override protected Class< ? > resolveClass(ObjectStreamClass desc) throws ClassNotFoundException, IOException { for (ClassLoader cl : lhs) try { Class< ? > c = cl.loadClass(name); // we found the class, so we can use its class loader, // it is in the proper class space if the uses constraints // are set properly (and you're using bnd so you should be ok) lhs.add(c.getClassLoader()); // The paranoid among us would check // the serial uuid here ... // long uuid = desc.getSerialVersionUID(); // Field field = c.getField("serialVersionUID"); // assert uuid == field.get(null) return c; } catch (Exception e) { // Ignore } // Fallback (for void and primitives) return super.resolveClass(desc); } }; // And now we've successfully read the object ... A clone = (A) in.readObject(); 

Tenga en cuenta que esto solo funciona siempre que el gráfico transitorio se exporte correctamente. Es decir, si puede hacer new TableData entonces esto también debería funcionar. Un ejemplo que no funciona es si, por ejemplo, obtiene una implementación desde una interfaz. La clase de interfaz no está conectada a la impl. clase. Es decir, si tuviera un TableDataImpl que TableData extendido sería un error. En esos casos, necesita algún servicio para encontrar el “dominio” de la implementación.

Buena suerte.

No hay otra manera de hacer ese AFAIK.

Debe indicar explícitamente todas las dependencias que contiene el árbol de objetos deserializados en ese paquete en el que intenta hacerlo.

Podría intentar poner todos los objetos de dominio en un solo paquete, digamos modelo y luego dejar que todos los otros paquetes dependan de él.

Sí, esta es una pregunta difícil. En muchos casos, el problema es aún peor, es posible que ni siquiera se sepa qué paquetes se necesitarán para deserializar un flujo. Para estos, las dependencias de tiempo de comstackción no son lo mismo que las dependencias de tiempo de ejecución.

Para abordar estas situaciones, he usado DynamicImports-Package o la API de BundleWiring . Ambos funcionaron bastante bien, aunque las importaciones dinámicas son más fáciles.

Yo diría que aísle la parte que necesita que esta clase se cargue lo más que pueda en un paquete separado, y que ese paquete use una DynamicImport.

Buena suerte frank