diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java index e16f899d4..248c3d720 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java @@ -35,6 +35,7 @@ import java.util.Set; import javax.sql.DataSource; +import org.apache.commons.lang.StringUtils; import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.event.OrphanedKeyAction; import org.apache.openjpa.jdbc.conf.JDBCConfiguration; @@ -43,6 +44,7 @@ import org.apache.openjpa.jdbc.meta.Discriminator; import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.meta.ValueMapping; import org.apache.openjpa.jdbc.meta.strats.SuperclassDiscriminatorStrategy; +import org.apache.openjpa.jdbc.schema.DataSourceFactory; import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.jdbc.sql.JoinSyntaxes; import org.apache.openjpa.jdbc.sql.Joins; @@ -146,14 +148,47 @@ public class JDBCStoreManager if (lm instanceof JDBCLockManager) _lm = (JDBCLockManager) lm; - if (!ctx.isManaged() && _conf.isConnectionFactoryModeManaged()) - _ds = _conf.getDataSource2(ctx); - else - _ds = _conf.getDataSource(ctx); + _ds = getDataSource(ctx); if (_conf.getUpdateManagerInstance().orderDirty()) ctx.setOrderDirtyObjects(true); } + + private final boolean useConnectionFactory2(StoreContext ctx) { + return (!ctx.isManaged() && _conf.isConnectionFactoryModeManaged()); + } + + private final DataSource getDataSource(StoreContext ctx) { + DataSource ds; + + if (useConnectionFactory2(ctx)) { + ds = (DataSource) ctx.getConnectionFactory2(); + if (ds != null) { + ds = DataSourceFactory.decorateDataSource(ds, _conf, false); + } + else { + ds = _conf.getDataSource2(ctx); + } + } else { + ds = (DataSource) ctx.getConnectionFactory(); + if (ds != null) { + ds = DataSourceFactory.decorateDataSource(ds, _conf, false); + } + else { + ds = _conf.getDataSource(ctx); + } + } + return ds; + } + + private boolean useContextToGetDataSource(StoreContext ctx) { + // configuration check to enable goes here. + if (StringUtils.isBlank(ctx.getConnectionFactoryName()) + && StringUtils.isBlank(ctx.getConnectionFactory2Name())) { + return false; + } + return true; + } public JDBCConfiguration getConfiguration() { return _conf; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java index bf696e944..bfc2ddf86 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java @@ -183,6 +183,11 @@ public abstract class AbstractBrokerFactory } public Broker newBroker(String user, String pass, boolean managed, int connRetainMode, boolean findExisting) { + return newBroker(user, pass, managed, connRetainMode, findExisting, "", ""); + } + + public Broker newBroker(String user, String pass, boolean managed, int connRetainMode, boolean findExisting, + String cf1Name, String cf2Name) { try { assertOpen(); makeReadOnly(); @@ -192,6 +197,8 @@ public abstract class AbstractBrokerFactory broker = findBroker(user, pass, managed); if (broker == null) { broker = newBrokerImpl(user, pass); + broker.setConnectionFactoryName(cf1Name); + broker.setConnectionFactory2Name(cf2Name); initializeBroker(managed, connRetainMode, broker, false); } return broker; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerFactory.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerFactory.java index 9c135280e..34a87005a 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerFactory.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerFactory.java @@ -74,6 +74,29 @@ public interface BrokerFactory */ public Broker newBroker(String user, String pass, boolean managed, int connRetainMode, boolean findExisting); + + /** + * Return a new broker using the supplied + * + * + * @param user Username to use when obtaining a connection. Will be ignored if a connection factory is + * obtained from JNDI. + * @param pass Password to use when obtaining a connection. Will be ignored if a connection factory is + * obtained from JNDI. + * @param managed Whether managed transactions will be used by this Broker + * @param connRetainMode {@link ConnectionRetainMode} + * @param findExisting Whether the internal pool of brokers should be used. + * @param cfName JTA ConnectionFactory to use + * @param cf2Name Non-JTA ConnectionFactory to use. + * @return A Broker which matches the provided criteria. + */ + public Broker newBroker(String user, String pass, boolean managed, + int connRetainMode, boolean findExisting, String cfName, String cf2Name); /** * Register a listener for lifecycle-related events on the specified diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java index b670f93a4..65c83f306 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java @@ -47,6 +47,7 @@ import org.apache.commons.collections.iterators.IteratorChain; import org.apache.commons.collections.map.IdentityMap; import org.apache.commons.collections.map.LinkedMap; import org.apache.commons.collections.set.MapBackedSet; +import org.apache.commons.lang.StringUtils; import org.apache.openjpa.conf.Compatibility; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.datacache.DataCache; @@ -60,6 +61,7 @@ import org.apache.openjpa.event.RemoteCommitEventManager; import org.apache.openjpa.event.TransactionEvent; import org.apache.openjpa.event.TransactionEventManager; import org.apache.openjpa.kernel.exps.ExpressionParser; +import org.apache.openjpa.lib.conf.Configurations; import org.apache.openjpa.lib.log.Log; import org.apache.openjpa.lib.util.J2DoPrivHelper; import org.apache.openjpa.lib.util.Localizer; @@ -144,6 +146,9 @@ public class BrokerImpl private static final int FLAG_TRANS_ENDING = 2 << 11; private static final Object[] EMPTY_OBJECTS = new Object[0]; + + private String _connectionFactoryName = ""; + private String _connectionFactory2Name = ""; private static final Localizer _loc = Localizer.forPackage(BrokerImpl.class); @@ -4970,4 +4975,60 @@ public class BrokerImpl public boolean isFromWriteBehindCallback() { return _fromWriteBehindCallback; } + + /** + * Return the 'JTA' connectionFactoryName + */ + public String getConnectionFactoryName() { + return _connectionFactoryName; + } + + /** + * Set the 'JTA' ConnectionFactoryName. Input will be trimmed to null before being stored. + */ + public void setConnectionFactoryName(String connectionFactoryName) { + this._connectionFactoryName = StringUtils.trimToNull(connectionFactoryName); + } + + /** + * Return the 'NonJTA' ConnectionFactoryName. + */ + public String getConnectionFactory2Name() { + return _connectionFactory2Name; + } + + /** + * Set the 'NonJTA' ConnectionFactoryName. Input will be trimmed to null before being stored. + */ + public void setConnectionFactory2Name(String connectionFactory2Name) { + this._connectionFactory2Name = StringUtils.trimToNull(connectionFactory2Name); + } + + /** + * Return the 'JTA' ConnectionFactory, looking it up from JNDI if needed. + * + * @return the JTA connection factory or null if connectionFactoryName is blank. + */ + public Object getConnectionFactory() { + if(StringUtils.isNotBlank(_connectionFactoryName)) { + return Configurations.lookup(_connectionFactoryName, "openjpa.ConnectionFactory", _log ); + } + else { + return null; + } + } + + /** + * Return the 'NonJTA' ConnectionFactory, looking it up from JNDI if needed. + * + * @return the NonJTA connection factory or null if connectionFactoryName is blank. + */ + public Object getConnectionFactory2() { + if(StringUtils.isNotBlank(_connectionFactory2Name)) { + return Configurations.lookup(_connectionFactory2Name, "openjpa.ConnectionFactory2", _log); + } + else { + return null; + } + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java index d035eb114..d237e53ec 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java @@ -1418,4 +1418,28 @@ public class DelegatingBroker public void setCachePreparedQuery(boolean flag) { _broker.setCachePreparedQuery(flag); } + + public String getConnectionFactoryName() { + return _broker.getConnectionFactoryName(); + } + + public void setConnectionFactoryName(String connectionFactoryName) { + _broker.setConnectionFactoryName(connectionFactoryName); + } + + public String getConnectionFactory2Name() { + return _broker.getConnectionFactory2Name(); + } + + public void setConnectionFactory2Name(String connectionFactory2Name) { + _broker.setConnectionFactory2Name(connectionFactory2Name); + } + + public Object getConnectionFactory() { + return _broker.getConnectionFactory(); + } + + public Object getConnectionFactory2() { + return _broker.getConnectionFactory2(); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBrokerFactory.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBrokerFactory.java index 08672d4c1..533e1f8bd 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBrokerFactory.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBrokerFactory.java @@ -148,9 +148,13 @@ public class DelegatingBrokerFactory public Broker newBroker(String user, String pass, boolean managed, int connRetainMode, boolean findExisting) { + return newBroker(user, pass, managed, connRetainMode, findExisting, "", ""); + } + public Broker newBroker(String user, String pass, boolean managed, + int connRetainMode, boolean findExisting, String cfName, String cf2Name) { try { return _factory.newBroker(user, pass, managed, connRetainMode, - findExisting); + findExisting, cfName, cf2Name); } catch (RuntimeException re) { throw translate(re); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java index ccdd2574a..0fd516710 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java @@ -456,4 +456,38 @@ public interface StoreContext { * Releases the internal lock. */ public void unlock (); + + /** + * Return the 'JTA' connectionFactoryName + */ + public String getConnectionFactoryName(); + + /** + * Set the 'JTA' ConnectionFactoryName. + */ + public void setConnectionFactoryName(String connectionFactoryName); + + /** + * Return the 'NonJTA' ConnectionFactoryName. + */ + public String getConnectionFactory2Name(); + + /** + * Set the 'NonJTA' ConnectionFactoryName. + */ + public void setConnectionFactory2Name(String connectionFactory2Name); + + /** + * Return the 'JTA' ConnectionFactory, looking it up from JNDI if needed. + * + * @return the JTA connection factory or null if connectionFactoryName is blank. + */ + public Object getConnectionFactory(); + + /** + * Return the 'NonJTA' ConnectionFactory, looking it up from JNDI if needed. + * + * @return the NonJTA connection factory or null if connectionFactoryName is blank. + */ + public Object getConnectionFactory2(); } diff --git a/openjpa-persistence-jdbc/pom.xml b/openjpa-persistence-jdbc/pom.xml index 718fb2505..349330a9b 100644 --- a/openjpa-persistence-jdbc/pom.xml +++ b/openjpa-persistence-jdbc/pom.xml @@ -712,6 +712,12 @@ jaxb-impl test + + simple-jndi + simple-jndi + 0.11.4 + test + diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conf/Person.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conf/Person.java new file mode 100644 index 000000000..ab16c2f81 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conf/Person.java @@ -0,0 +1,55 @@ +package org.apache.openjpa.persistence.conf; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Version; + +// override defaults to attempt to prevent collisions. +@Entity(name="confPerson") +@Table(name="CONF_PERSON") +public class Person { + + @Id + private int id; + + @Version + private int version; + + @Column(length=16) + private String name; + + public Person() { + super(); + } + + public Person(int id) { + super(); + setId(id); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conf/TestSwitchConnection.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conf/TestSwitchConnection.java new file mode 100644 index 000000000..c2d5ef286 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/conf/TestSwitchConnection.java @@ -0,0 +1,107 @@ +package org.apache.openjpa.persistence.conf; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.RollbackException; + +import org.apache.openjpa.persistence.test.AbstractPersistenceTestCase; + +public class TestSwitchConnection extends AbstractPersistenceTestCase { + private String defaultJndiName = "jdbc/mocked"; + private String[] jndiNames = { "jdbc/mocked1" }; + + protected void initEMF(String cfName) { + EntityManagerFactory emf = getEmf("openjpa.ConnectionFactoryName", cfName); + + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + em.createQuery("Delete from confPerson").executeUpdate(); + em.getTransaction().commit(); + em.close(); + + emf.close(); + } + + protected EntityManagerFactory getEmf(String cfPropertyName, String cfPropertyValue) { + // null out the driver to prevent system properties from taking effect. + // do not set connectionFactoryModeManaged - or connectionFactory2 will be used. + return createEMF( + "openjpa.ConnectionDriverName", "", + cfPropertyName, cfPropertyValue, + Person.class); + } + + protected EntityManager getEm(EntityManagerFactory emf, String name, String value) { + Map props = new HashMap(); + props.put(name, value); + return emf.createEntityManager(props); + } + + protected void createTables() { + // create an EMF for each database; + initEMF(defaultJndiName); + initEMF(jndiNames[0]); + } + + public void testConnectionFactoryName() { + // split out so that we can try javax.persistence.jtaDataSource in the future. + overridePropertyOnEM("openjpa.ConnectionFactoryName", jndiNames[0]); + } + + public void overridePropertyOnEM(String name, String value) { + // TODO Disable for non derby. + createTables(); + + // use the default JndiName for the base EntityManagerFactory + EntityManagerFactory emf = getEmf(name, defaultJndiName); + assertNotNull(emf); + + EntityManager em = emf.createEntityManager(); + assertNotNull(em); + + EntityManager em1 = getEm(emf, name, value); + assertNotNull(em1); + + // 'prove' that we're using a different database by inserting the same row + em.getTransaction().begin(); + em.persist(new Person(1)); + em.getTransaction().commit(); + + em1.getTransaction().begin(); + em1.persist(new Person(1)); + em1.getTransaction().commit(); + + em.clear(); + em1.clear(); + + // sanity test, make sure inserting the same row again fails. + + em.getTransaction().begin(); + em.persist(new Person(1)); + try { + em.getTransaction().commit(); + fail("Should not be able to commit the same row a second time"); + } catch (RollbackException rbe) { + assertTrue(rbe.getCause() instanceof EntityExistsException); + // expected + } + + em1.getTransaction().begin(); + em1.persist(new Person(1)); + try { + em1.getTransaction().commit(); + fail("Should not be able to commit the same row a second time"); + } catch (RollbackException rbe) { + assertTrue(rbe.getCause() instanceof EntityExistsException); + // expected + } + + em.close(); + em1.close(); + emf.close(); + } +} diff --git a/openjpa-persistence-jdbc/src/test/resources/jndi.properties b/openjpa-persistence-jdbc/src/test/resources/jndi.properties new file mode 100644 index 000000000..72b75310d --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/resources/jndi.properties @@ -0,0 +1,4 @@ + java.naming.factory.initial=org.osjava.sj.SimpleContextFactory + org.osjava.sj.root=src/test/resources/simple-jndi + org.osjava.sj.colon.replace=-- + org.osjava.sj.delimiter=/ diff --git a/openjpa-persistence-jdbc/src/test/resources/simple-jndi/jdbc/default.properties b/openjpa-persistence-jdbc/src/test/resources/simple-jndi/jdbc/default.properties new file mode 100644 index 000000000..5800ae0d2 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/resources/simple-jndi/jdbc/default.properties @@ -0,0 +1,11 @@ +mocked/type=javax.sql.DataSource +mocked/driver=org.apache.derby.jdbc.EmbeddedDriver +mocked/url=jdbc:derby:target/database/jpa-jndi-database;create=true +mocked/user=app +mocked/password=app + +mocked1/type=javax.sql.DataSource +mocked1/driver=org.apache.derby.jdbc.EmbeddedDriver +mocked1/url=jdbc:derby:target/database/jpa-jndi-database1;create=true +mocked1/user=app +mocked1/password=app diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java index f9a140537..ffefcfe68 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java @@ -197,7 +197,20 @@ public class EntityManagerFactoryImpl } } - Broker broker = _factory.newBroker(user, pass, managed, retainMode, false); + // javax.persistence.jtaDataSource and openjpa.ConnectionFactory name are equivalent. + // prefer javax.persistence for now. + String cfName = (String) Configurations.removeProperty("jtaDataSource", props); + if(cfName == null) { + cfName = (String) Configurations.removeProperty("ConnectionFactoryName", props); + } + + String cf2Name = (String) Configurations.removeProperty("nonJtaDataSource", props); + + if(cf2Name == null) { + cf2Name = (String) Configurations.removeProperty("ConnectionFactory2Name", props); + } + + Broker broker = _factory.newBroker(user, pass, managed, retainMode, false, cfName, cf2Name); // add autodetach for close and rollback conditions to the configuration broker.setAutoDetach(AutoDetach.DETACH_CLOSE, true);