views:

44

answers:

2

Hi All,

I use EJB3 container managed persistence i.e an EntityManager is injected via @PersistenceContext annotation. The persistent context then may be propagated to nested EJBs. Transactions are also managed by the contaner (glassfish).

Usually I would drop persistence.xml into META-INF directory and the container would work out which provider to use and how to configure the EntityManagerFactory (based in hibernate specific properties).

My problem is that I need to hook into the EntityManagerFactory configuration process. Particularly I need to change discriminator values in some PersistentClasses before the EntityManagerFactory gets configure'ed (frozen for any change).

This is how I do it with Spring, but need to do similar with pure EJB3 CMP (or may be with the help of Spring).

public class AnnotationSessionFactoryBean extends  org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean {
    /** Log4j logging instance. */
    protected static Logger log = Logger.getLogger(AnnotationSessionFactoryBean.class);

    //some data preloaded from the database using jdbc
    private Map<String, DatabaseConfiguration> configs;

    @Override
    protected void postProcessAnnotationConfiguration(AnnotationConfiguration config) throws HibernateException {
        //Load and process dynamic Mappings.
        Iterator classMappingsIter = config.getClassMappings();
        while(classMappingsIter.hasNext()) {
            PersistentClass persistentClass = (PersistentClass) classMappingsIter.next();

            String discriminatorValue = persistentClass.getDiscriminatorValue();
            if(discriminatorValue != null) {
                log.debug("DiscriminatorValue before [" + discriminatorValue + "]");
                //here I replace discriminator values.
                //The Discriminator values are coded in the annotations
                //as names (words). These words need to be replaced with ids
                //previously loaded from the database using jdbc.
                //The names are constant in all environments, however the ids are
                //are different.    
                discriminatorValue = StringUtil.replacePlaceholders(discriminatorValue, configs);
                persistentClass.setDiscriminatorValue(discriminatorValue);
                log.debug("DiscriminatorValue after [" + discriminatorValue + "]");
            }


        }
        super.postProcessAnnotationConfiguration(config);
    }

    /**
     * @return the configs
     */
    public Map<String, DatabaseConfiguration> getConfigs() {
        return configs;
    }

    /**
     * @param configs the configs to set
     */
    public void setConfigs(Map<String, DatabaseConfiguration> configs) {
        this.configs = configs;
    }


}

Thanks in advance, Anton

A: 

You could override metadata annotations by providing an XML mapping file (see the Chapter 10 XML Descriptor in the JPA 1.0 specification).

Of course, this is not dynamic (unless you generate the XML mapping file using for example FreeMarker and feed values from the database).

Pascal Thivent
Pascal, I am aware of XML descriptors. I have already throught of this solution. But it's just ugly. I have everything in the java class (including the discriminator). I think configuring PersistentClasses programmaticaly is better idea than mantaining heaps of files. Another reason I need to use programmatic configuration is to set MultiMap collection type to some OneToMany Map collections. I have found that it is the only way to do that when you use annotations. In fact it easy to do with native hibernate XML, but not with annotations nor with XML descriptors.
Anton
As I said, I can do all of the above with Spring. However, the EJB3 does not seem to have such interceptor/callback to hook into configuration process.
Anton
Another idea I would think of is to use spring to configure the EntityManagerFactory and feed it to EJB container for further CMP.
Anton
+1  A: 

I think I have found the solution. The class org.hibernate.ejb.HibernatePersistence can be overridden.

public class HibernatePersistenceCustom extends org.hibernate.ejb.HibernatePersistence {
    /** Log4j logging instance. */
    protected static Logger log = Logger.getLogger(HibernatePersistenceCustom.class);

    @Override
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
        Ejb3Configuration cfg = new Ejb3Configuration();
        //here you can configure it
        doCustomConfiguration(cfg);
        Ejb3Configuration configured = cfg.configure(info, map);
        return configured != null ? configured.buildEntityManagerFactory() : null;
    }

    ...
    //other methods can also be overridden if required.


    public void doCustomConfiguration(Ejb3Configuration config) {
        //Load and process dynamic Mappings.
        Iterator classMappingsIter = config.getClassMappings();
        while(classMappingsIter.hasNext()) {
            PersistentClass persistentClass = (PersistentClass) classMappingsIter.next();

            String discriminatorValue = persistentClass.getDiscriminatorValue();
            if(discriminatorValue != null) {
                log.debug("DiscriminatorValue before [" + discriminatorValue + "]");
                //here I replace discriminator values.
                //The Discriminator values are coded in the annotations
                //as names (words). These words need to be replaced with ids
                //previously loaded from the database using jdbc.
                //The names are constant in all environments, however the ids are
                //are different.    
                discriminatorValue = StringUtil.replacePlaceholders(discriminatorValue, configs);
                persistentClass.setDiscriminatorValue(discriminatorValue);
                log.debug("DiscriminatorValue after [" + discriminatorValue + "]");
            }


        }

    } 
}

then in persistence.xml instead of org.hibernate.ejb.HibernatePersistence put com.mydomain.persistence.HibernatePersistenceCustom

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"&gt;
  <persistence-unit name="mypersistenceunit" transaction-type="JTA">
    <provider>com.mydomain.persistence.HibernatePersistenceCustom</provider>
    <jta-data-source>jdbc/mydatasource</jta-data-source>
    <properties>
      <property name="hibernate.show_sql" value="false"/>
      <property name="hibernate.format_sql" value="false"/>
      <property name="hibernate.use_sql_comments" value="false"/>
      <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.SunONETransactionManagerLookup"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
    </properties>
  </persistence-unit>
</persistence>

Have not tested it yet, but I think it will work.

Thanks

Anton
+1 Interesting. Did it work?
Pascal Thivent
Not as I expected. My solution did not work. Then I found similar solution here (http://netbeans.dzone.com/news/netbeans-jpa-part-two?page=0,2) where file META-INF/service/javax.persistence.spi.PersistenceProvider had to be changed. Original HibernatePersistence class had to be replaced with my subclass. However this solution did not work either because of original hibernate-entitymanager in classpath I think. So I ended up with applying this patch http://opensource.atlassian.com/projects/hibernate/browse/HHH-4189 which is similar to both the above solutions however more generic.
Anton
If you don't mind can you please vote for that patch?
Anton