Hibernate-Extender or Hibernate, Spring and OSGi


    Unfortunately, at the moment, Hibernate does not have the necessary integration mechanisms for working in the OSGi environment, although progress in this direction is noticeable (initial OSGi-fiction by splitting packets in the 4th branch). This encourages the development of their own mechanisms, which requires considerable additional effort.

    This article is intended for those developers who are interested in: how can I use Hibernate with a bunch of Spring + OSGi; what is the Extender pattern; how to implement ClassLoader with specific behavior; as supported by Hibernate in the Spring Framework and a bit about extending this code. Of course, to read the article you need to understand the technologies of Spring, Hibernate, OSGi, and also understand the main problems of code execution in a multi-threaded environment. Those who are unfamiliar with using the Spring Framework in an OSGi environment can refer to the introductory article “Using Spring in an OSGi Container” .

    All the code presented is part of the training project, the link to which is located at the end of the article. Therefore, please consider all the examples presented as a prototype rather than fragments ready to use.

    Extender Pattern



    The software running in the OSGi container consists of separate modules (bundle), each of which performs its specific duties. What to do when one module should use some complex runtime mechanisms located in another module? The answer is obvious - use OSGi services. And what to do when these mechanisms require, for example, initialization (registering a servlet in a servlet container or, as in our case, registering mapped classes of Hibernate entities). The answer to this question is already less obvious. In fact, there are only two main options: the first - each module that needs this initialization calls it on its own (for example, from the BundleActivator); the second is that a module that needs such initialization has some characteristic metadata by which another module can initiate all the necessary work, since OSGi has a well-developed event system. The second option has a definite advantage - we do not distribute a bunch of the same code across the modules and do not burden them with additional work, but we transfer this obligation to a separate module. Actually, Spring DM works on this principle. This principle of operation is the Extender pattern [http://www.aqute.biz/Snippets/Extender] and it is very common in the OSGi world. Actually, Spring DM works on this principle. This principle of operation is the Extender pattern [http://www.aqute.biz/Snippets/Extender] and it is very common in the OSGi world. Actually, Spring DM works on this principle. This principle of operation is the Extender pattern [http://www.aqute.biz/Snippets/Extender] and it is very common in the OSGi world.

    Typical Extender Workflow


    Description of the overall picture of the work of Hibernate-Extender



    We begin to discuss the general scheme of work of Hibernate-Extender. First of all, you need to select metadata thanks to which our extender will distinguish modules that need to be expanded from non-modules. Let it be the Entity-Classes heading in META-INF / MANIFEST.MF containing the list of classes of Hibernate entities (lovers of xml-mapping will be out of work for now). Next, you need to receive events from the modules entering the Active state and from the modules leaving this state. To do this, we use the OSGi event mechanism and implement the SynchronousBundleListener interface. We use a synchronous event handler from the modules in order to immediately respond to changes in the state of the modules before calling the asynchronous BundleListener. In doing so, we must provide the shortest possible time for processing the event, so as not to block the thread calling our handler. This is achieved by implementing the Producer-Consumer pattern, where our event handler acts as Producer, which passes information about the modules to Consumer, which performs all lengthy operations in a separate thread. In our case, it reinitializes the Hibernate runtime structures.

    The Extender mechanism is fairly generalized, so the implementation below follows the Dependency Inversion Principle (SOLID) to facilitate code reuse.

    public class Extender implements SynchronousBundleListener {
      private BundleContext bundleContext;
      private Executor executor;
      private BlockingQueue bundles 
        = new LinkedBlockingQueue();
      private BundleConsumer bundleConsumer;
      private ExtendedBundleFactory extendedBundleFactory;
      // getter's и setter's
      @Override
      public void bundleChanged(BundleEvent event) {
        Actions  action = null;
        switch (event.getType()) {
        case BundleEvent.STARTED: 
          action = Actions.ADDING;
          break;
        case BundleEvent.STOPPING:
          action = Actions.REMOVING;
          break;
        };
        if (action == null) return;
        ExtendedBundle extendedBundle = 
            extendedBundleFactory.newExtendedFactory(
                event.getBundle(), 
                action
                );
        try {
          bundles.put(extendedBundle);
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
        }    
      }
      public void initialize() {
        getBundleConsumer().initialize(getBundles());
        getExecutor().execute(bundleConsumer);
        getBundleContext().addBundleListener(this);
        for (Bundle bundle: getBundleContext().getBundles()) {
          if (Bundle.ACTIVE != bundle.getState()) continue;
          ExtendedBundle extendedBundle = 
              extendedBundleFactory.newExtendedFactory(
                  bundle, 
                  Actions.ADDING
                  );
          try {
            bundles.put(extendedBundle);
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
          }
        }
      }
    }
    


    Implementation of Extender involves: BundleContext in which Extender itself is registered as a listener for events from modules; Executor is the implementation of the executor from the java.util.concurrent framework, in which the consumer executes long-term operations; BlockingQueue A blocking queue with which Producer-Consumer execution threads are synchronized; BundleConsumer specific consumer implementation of event modules; ExtendedBundleFactory factory "extended" modules.

    The public void initialize () method is called immediately after constructing the object. In this method, we register our Extender as a handler, start the consumer execution and process the modules that are already in the OSGi container, in case Extender starts after the extensible modules.

    Bundleconsumer



    BundleConsumer is an interface, an object with a type that implements this interface must process the modules queued by Extender.

    public interface BundleConsumer extends Runnable {
      void run();
      void initialize(
          BlockingQueue newBundles
          );
    }
    


    It’s clear from the code above that this is just Runnable with an additional initialization method. Everything interesting should happen in the implementation of the void run () method. Let's look at a specific implementation of this interface, which does the basic work for our Hibernate-Extender.

    public class SessionFactoryCreator implements BundleConsumer {
      private volatile Extendable extendable;
      private volatile ExtenderClassLoader extenderClassLoader;
      private volatile BlockingQueue newBundles;
      // getter's, setter's и прочие методы
      @Override
      public void run() {
        ExtendedBundle bundle = null;
        while (!Thread.currentThread().isInterrupted()) {
          try {
            bundle = newBundles.take();
          }
          catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
          }
          if (!bundle.isValidForExtending()) continue;
          if (Actions.ADDING.equals(bundle.getAction())) {
            addBundle(bundle);
          }
          else if (Actions.REMOVING.equals(bundle.getAction())) {
            removeBundle(bundle);
          }
        }
      }
      private void addBundle(ExtendedBundle bundle) {
        bundle.extend();
        extenderClassLoader.addBundle(bundle);
        extendable.addBundle(bundle);
        bundle.markAsCompleted();
      }
      private void removeBundle(ExtendedBundle bundle) {
        bundle.extend();
        extendable.removeBundle(bundle);
        extenderClassLoader.removeBundle(bundle);
        bundle.markAsCompleted();
      }
    }
    


    The main loop in the void run () method handles the newBundles queue. If there are no extensible modules in the queue, execution is blocked in the call to the newBundles.take () method. When expandable modules appear, they move one at a time. On each of these modules, the isValidForExtending () method is called, which should return the true value if the module is suitable for extension. This verification method can take considerable time when accessing the internal resources of packages (for example, calling I / O operations), therefore it is called here, and not in Extender. If the module is suitable for extension, then meta information is checked about the action that needs to be performed on it.Actions.ADDING or Actions.REMOVING.

    I want to draw the reader’s attention to the conditions for exiting the main processing loop! Thread.currentThread (). IsInterrupted (), it ensures that the thread terminates correctly when the interrupt flag is set. Another place where this flag is used is the handling of the exception thrown by the blocking method newBundles.take () upon occurrence of which the flag for interrupting the stream is set and the main loop exits.

    Class attributes are marked as volatile to provide guarantees of visibility in a multi-threaded environment. Creation of an object occurs in one thread, and further use in another. The implementation of BlockingQueue is thread safe, so its use in a multi-threaded environment does not require additional synchronization.

    HibernateSessionFactoryBean



    A large number of various frameworks is not accidental. Using third-party, well-tested, ready-to-use code greatly facilitates development. Therefore, we will try not to invent too much. Spring Framework supports integration with Hibernate. One of the main classes of this integration is LocalSessionFactoryBean. It initializes and creates a session factory, which is then used in any code using the Hibernate API. This class has a descendant of AnnotationSessionFactoryBean, which adds some functionality with support for annotations. From the latter, we will inherit our implementation, the code of which is presented below.

    public class HibernateSessionFactoryBean 
        extends AnnotationSessionFactoryBean {
      private SessionFactory sessionFactoryProxy;
      private Lock sessionFactoryAccessLock = 
          new ReentrantLock(true);
      private ClassLoader classLoader;
      private Set> annotatedClasses
        = new CopyOnWriteArraySet>();
      /**
       * Установить свой ClassLoader
       * @param classLoader
       */
      public void setClassLoader(ClassLoader classLoader) {
        super.setBeanClassLoader(classLoader);
        this.classLoader = classLoader;    
      }
      /**
       * Отключения установки ClassLoader контейнером через интерфейс BeanClassLoaderAware
       * @param beanClassLoader
       */
      @Override
      public void setBeanClassLoader(ClassLoader beanClassLoader) {
      }
      @Override
      public void afterPropertiesSet() throws Exception {
        //Установка своего ClassLoader
        ProxyFactory.classLoaderProvider = new ClassLoaderProvider() {
          public ClassLoader get(ProxyFactory pf) {
            return HibernateSessionFactoryBean.this.classLoader;
          }
        };
        super.afterPropertiesSet();        
        sessionFactoryProxy = createSessionFactoryProxy();
      }
      @Override
      public SessionFactory getObject() {
        return sessionFactoryProxy;
      }
      /**
       * Пересоздать фабрику сессий Hibernate
       */
      protected void recreateSessionFactory() throws Exception {
        sessionFactoryAccessLock
          .tryLock(3000, TimeUnit.MILLISECONDS);
        try {
          //разрушение всего runtime-контекста связанного с Hibernate
          super.destroy();
          //создание нового runtime-контекста связанного с Hibernate
          super.afterPropertiesSet();
        }
        finally {
          sessionFactoryAccessLock.unlock();
        }
      }
      protected SessionFactory createSessionFactoryProxy()
          throws InstantiationException,
            IllegalAccessException, InterruptedException {
        // создание фабрики прокси объектов
        // для интерфейсов SessionFactory и Extendable
        ProxyFactory sessionFactoryProxyFactory = new ProxyFactory();
        sessionFactoryProxyFactory.setInterfaces(
            new Class[] {SessionFactory.class, Extendable.class}
            );
        //Не обрабатываем вызов метода finalize
        sessionFactoryProxyFactory.setFilter(new MethodFilter() {
          public boolean isHandled(Method m) {
            return !m.getName().equals("finalize");
          }
        });    
        Class sessionFactoryProxyClass = 
          sessionFactoryProxyFactory.createClass();
        MethodHandler mi = new MethodHandler() {
          public Object invoke(
              Object self, 
              Method thisMethod, 
              Method proceed,
              Object[] args) throws Throwable {
            Object result = null;
            //Переопределение TCCL
            ClassLoader defaultClassLoader = 
                Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);
            try {
              if (thisMethod.getName().contains("addBundle")) {
                addBundle((HibernateBundle) args[0]);
              }
              else if (thisMethod.getName().contains("removeBundle")) {
                removeBundle((HibernateBundle) args[0]);
              }
              else {
                sessionFactoryAccessLock
                  .tryLock(3000, TimeUnit.MILLISECONDS);
                try {
                  //выполнить оригинальный метод SessionFactory
                  result = thisMethod
                    .invoke(getSessionFactory(), args);
                }
                finally {
                  sessionFactoryAccessLock.unlock();
                }
              }
            }
            finally {
              //Восстановление TCCL действующего до переопределения
              Thread.currentThread()
                .setContextClassLoader(defaultClassLoader);
            }
            return result;
          }
        };
        SessionFactory sessionFactory = 
            (SessionFactory)sessionFactoryProxyClass.newInstance();
        ((ProxyObject)sessionFactory).setHandler(mi);
        return sessionFactory;
      }
      private void addBundle(HibernateBundle bundle) throws Exception {
        for (String entityClassName: bundle.getEntityClassNames()) {
          annotatedClasses.add(classLoader.loadClass(entityClassName));
        }
        setAnnotatedClasses(
          annotatedClasses.toArray(
            new Class[annotatedClasses.size()]));
        recreateSessionFactory();
      }
      private void removeBundle(HibernateBundle bundle) throws Exception {
        for (Class annotatedClass: annotatedClasses) {
          if (bundle.getEntityClassNames().contains(
            annotatedClass.getCanonicalName())) {
            annotatedClasses.remove(annotatedClass);
          }
        }
        setAnnotatedClasses(
          annotatedClasses.toArray(
            new Class[annotatedClasses.size()]));
        recreateSessionFactory();
      }
    }
    


    The main plan for executing this code is as follows. AbstractSessionFactoryBean implements the FactoryBean interface, processing the creation of a class object that implements this interface in the Spring container takes place in a special way. See the Spring Framework documentation for more on this. It is worth mentioning only that it is just a factory of SessionFactory objects, and not its concrete implementation. Instead of the SessionFactory object created in LocalSessionFactoryBean, the code returns a proxy object that forwards almost all calls to the native SessionFactory methods while protecting any calls to it with a global lock (this architecture can be reworked in terms of performance, in addition, the possibility of re-creating the SessionFactory when using Session objects is not taken into account )

    The object proxy method call handler uses the Thread Context Classloader (TCCL) override. As TCCL, at the time of the method call, our own Classloader sets up the topic that will be discussed later.

    OSGi and ClassLoader



    In a typical jvm-based application, class loaders are hierarchically linked (reference to the parent) and load classes and resources by first delegating to their parent and, if unsuccessful, by themselves. For OSGi environments, this behavior is not suitable. Similarly, it is not suitable for our case with Extender. In the OSGi container, each module gets its own class loader which fully meets its (module) needs. And the module class loaders are connected to a more complex delegation network. Each such loader has a link to the loader of the framework class, as well as to the loaders of the modules from which the import is performed. This delegation network is built as a result of the Resolving process in the OSGi container.

    Class Loader Delegation Network in OSGi


    In the case of Extender, a typical module class loader is not suitable, because it is not possible to declare imported entity classes in MANIFEST.MF. Such classes are simply unknown at the time of Extender development. Therefore, you need to implement your own bootloader with the behavior we need. The class loader code for Extender is presented below.

    public class ExtenderClassLoader extends ClassLoader {
      private volatile ClassLoader defaultClassLoader = null;
      private Set bundles = 
        new CopyOnWriteArraySet();
      public ClassLoader getDefaultClassLoader() {
        return defaultClassLoader;
      }
      public void setDefaultClassLoader(
        ClassLoader defaultClassLoader) {
        this.defaultClassLoader = defaultClassLoader;
      }
      public void addBundle(
        ExtendedBundle bundle) {
        bundles.add(bundle);
      }
      public void removeBundle(ExtendedBundle bundle) {
        bundles.remove(bundle);
      }
      @Override
      public Class loadClass(
        String name) throws ClassNotFoundException {
        Class c = null;
        for (ExtendedBundle bundle: bundles) {
          try {
            c = bundle.loadClass(name);
            break;
          }
          catch (ClassNotFoundException e) {}
        }
        if (c == null && getDefaultClassLoader() != null) {
          try {
            c = getDefaultClassLoader().loadClass(name);
          }
          catch (ClassNotFoundException e) {}
        }
        if (c == null) throw new ClassNotFoundException(name);
        return c;
      }
      // прочие методы
    }
    


    Let's take a closer look at the code below. The main logic of his work is in the loadClass method. It must be emphasized that similarly implemented methods for loading resources that are omitted in this fragment. ExtenderClassLoader contains a collection of modules containing entity classes and delegates the loading of classes and resources to them. This provides access to the required code without the need for import. ExtenderClassLoader delegates the loading of the remaining necessary classes to the default bootloader. The thread safety of this code is ensured by using a non-blocking implementation of the many CopyOnWriteArraySet and thread-safe class loaders.

    Putting it all together



    At this point, the reader should have an understanding of how the basic elements of Hibernate-Extender work. Now you need to assemble all the parts together. To do this, you must configure the module to work in the OSGi environment with spring dm. For a better understanding of the configuration of objects, consider the class diagram of our system.

    UML class interaction diagram


    According to established canons, the configuration of the spring dm module is split into at least two files. The first is the actual configuration of the spring container. The second is the configuration specific to the OSGi environment. In our case, these are context.xml and osgi-context.xml files.

    The Hibernate-Extender configuration for spring dm is identical to the class diagram. At the beginning of the context.xml configuration file, the dataSource object is described - this is the data source with the help of which all interaction with the database is carried out. Next comes the configuration of the transactionManager object that provides transaction support in the Spring environment. The sessionFactory object is an instance of the HibernateSessionFactoryBean class described above that is responsible for creating the SessionFactory. The sessionFactory reference is passed to transactionManager. The next object in the configuration is extenderClassLoader in which, when created as defaultClassLoader, the default class loader is installed in the Spring environment. The sessionFactoryCreator object does all the basic work of initiating the reconfiguration of the Hibernate environment, taking into account the changed data about the modules. The central object of our module is Extender which, in the process of creation, receives references to objects: bundleContext, new nameless Executor, new nameless ExtendedBundleFactory and sessionFactoryCreator. In the second osgi-context.xml configuration file, OSGi services are declared: dataSource, transactionManager, sessionFactory.

    context.xml


    osgi-context.xml

    
           org.springframework.transaction.support.ResourceTransactionManager
          


    Deficiencies of the prototype



    Like any prototype, Hibernate-Extender has a number of flaws. On the one hand, it is difficult to convey the main idea if it is mixed with a large number of secondary details. On the other hand, one cannot but mention the very obvious flaws that prevent the use of this code in a real application.

    From the spring configuration, the first thing that catches your eye is the presence of setting the properties of the data source directly in the context.xml file itself. Any real application requires a change to this. For example, installing this data from an external properties file using the Spring mechanism of property-placeholder or implementing a more complex system that provides changes to the database connection parameters at run time.

    In the above listings, the code responsible for logging is omitted, without which almost any use of the module is unthinkable.

    The described implementation has the following serious drawback. What happens if a module is added to an extension when another, already extended module, performs an operation using the Hibernate API and is inside the transaction? Such an operation, under the current implementation, is doomed to failure, although if this is taken into account in the code, the only inconvenience will be the handling of specific exceptions related to invoking operations on the now no longer relevant Session object.

    And the last drawback that I want to highlight is the performance issue that occurs when reconfiguring the Hibernate runtime environment that occurs after each change to many extensible modules.

    Third-party OSGi fiction



    Many java libraries are already shipped ready to run in the OSGi environment, but unfortunately, not all. There are two ways out of this, either use OSGi-modified versions prepared by a third party (for example, the SpringSource Enterprise Bundle Repository [http://ebr.springsource.com/repository/app/]), or prepare such versions of the libraries yourself. In the project with the source code of the examples, such preparation is performed (subproject lib). I want to note that for a complex library this is by no means a trivial task. You need to understand the structure of the library for exporting packages that can be used by third-party code. Additional problems may arise due to internal mechanisms of libraries that are poorly compatible or incompatible with the OSGi environment. But all this, in most cases, is a solvable task.

    Conclusion



    This concludes the description of the Extender approach to working with Hibernate in OSGi. The only thing left is to add how you can use the resulting module. The extensible module in the Entity-Classes header in META-INF / MANIFEST.MF should put a list of full Hibernate entity class names. Next, when you run this module, Extender will load these classes using our ClassLoader implementation and reinitialize the Hibernate runtime environment. In addition, a module requiring the use of the Hibernate API must import the OSGi service SessionFactory and ResourceTransactionManager. After that, the module can use the Hibernate API in the Spring environment in the usual manner (according to the official guide).
    The full Extender code along with unit and integration tests is available in the googlecode repository, the link to which is located at the end of the article. This code will be developed, so everyone who is interested in it can follow the changes. Who has the desire to participate in the development of this code can contact me. Well, as usual, who has any questions, welcome to comment!

    Additional sources



    code.google.com/p/iconcerto/source/browse
    www.aqute.biz/Bnd/Bnd
    www.aqute.biz/Snippets/Extender
    static.springsource.org/spring/docs/3.1.x/spring-framework-reference / html
    docs.jboss.org/hibernate/core/3.6/reference/en-US/html
    static.springsource.org/osgi/docs/1.2.1/reference/html

    Also popular now: