diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCBrokerFactory.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCBrokerFactory.java index 974f32686a..fdaa546a68 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCBrokerFactory.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCBrokerFactory.java @@ -40,7 +40,6 @@ import org.apache.openjpa.jdbc.schema.SchemaTool; import org.apache.openjpa.kernel.AbstractBrokerFactory; import org.apache.openjpa.kernel.Bootstrap; -import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.kernel.BrokerImpl; import org.apache.openjpa.kernel.StoreManager; import org.apache.openjpa.lib.conf.ConfigurationProvider; @@ -160,35 +159,49 @@ protected BrokerImpl newBrokerImpl(String user, String pass) { } } + /** + * Return the class loader for the {@link org.apache.openjpa.kernel.BrokerFactory} + * schema management operations, which are not associated with any broker. + * + * This is the thread context class loader, which is what + * {@code BrokerImpl.initialize()} assigns to the loader a broker later reports from + * {@code Broker#getClassLoader()}, and what {@link #postCreationCallback()} already + * passes here. No broker is created for this: a broker from {@code newBrokerImpl()} + * has not been initialized, so its class loader is still null and it supplies no + * loader at all (OPENJPA-2962). + */ + private static ClassLoader schemaManagementClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + @Override public void createPersistenceStructure(boolean createSchemas) { JDBCConfiguration conf = (JDBCConfiguration) getConfiguration(); - Broker broker = super.newBrokerImpl(conf.getConnectionUserName(), conf.getConnectionPassword()); String baseAction = createSchemas ? "createDB, add": MappingTool.ACTION_ADD; - synchronizeMappings(broker.getClassLoader(), conf, String.format("buildSchema(ForeignKeys=true,schemaAction='%s')", baseAction)); + synchronizeMappings(schemaManagementClassLoader(), conf, + String.format("buildSchema(ForeignKeys=true,schemaAction='%s')", baseAction)); } @Override public void dropPersistenceStructure(boolean dropSchemas) { JDBCConfiguration conf = (JDBCConfiguration) getConfiguration(); - Broker broker = super.newBrokerImpl(conf.getConnectionUserName(), conf.getConnectionPassword()); String baseAction = dropSchemas ? "drop, dropDB": MappingTool.ACTION_DROP; - synchronizeMappings(broker.getClassLoader(), conf, String.format("buildSchema(ForeignKeys=true,schemaAction='%s')", baseAction)); + synchronizeMappings(schemaManagementClassLoader(), conf, + String.format("buildSchema(ForeignKeys=true,schemaAction='%s')", baseAction)); } @Override public void validatePersistenceStructure() throws Exception { JDBCConfiguration conf = (JDBCConfiguration) getConfiguration(); - Broker broker = super.newBrokerImpl(conf.getConnectionUserName(), conf.getConnectionPassword()); - synchronizeMappings(broker.getClassLoader(), conf, "validate(ForeignKeys=true)"); + synchronizeMappings(schemaManagementClassLoader(), conf, "validate(ForeignKeys=true)"); } @Override public void truncateData() { JDBCConfiguration conf = (JDBCConfiguration) getConfiguration(); - Broker broker = super.newBrokerImpl(conf.getConnectionUserName(), conf.getConnectionPassword()); String baseAction = "refresh,deleteTableContents"; - synchronizeMappings(broker.getClassLoader(), conf, String.format("buildSchema(ForeignKeys=true,schemaAction='%s')", baseAction)); + synchronizeMappings(schemaManagementClassLoader(), conf, + String.format("buildSchema(ForeignKeys=true,schemaAction='%s')", baseAction)); } protected boolean synchronizeMappings(ClassLoader loader, JDBCConfiguration conf) { diff --git a/openjpa-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestJDBCBrokerFactorySchemaSpi.java b/openjpa-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestJDBCBrokerFactorySchemaSpi.java new file mode 100644 index 0000000000..8be3288448 --- /dev/null +++ b/openjpa-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestJDBCBrokerFactorySchemaSpi.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.jdbc.kernel; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; +import org.apache.openjpa.kernel.BrokerImpl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import org.junit.Before; +import org.junit.Test; + +/** + * Verifies that the {@code BrokerFactory} schema management operations do not + * instantiate a {@link BrokerImpl} and that they pass the thread context class + * loader on to the mapping tool (OPENJPA-2962). + * + * This test needs no database: {@code synchronizeMappings} is stubbed out. + */ +public class TestJDBCBrokerFactorySchemaSpi { + + static final AtomicInteger CLONES = new AtomicInteger(); + + /** + * Plugin class for {@code openjpa.BrokerImpl}. The factory obtains brokers by + * cloning the configured template instance, so counting clones counts the + * brokers a schema operation creates. + */ + public static class CountingBroker extends BrokerImpl { + private static final long serialVersionUID = 1L; + + @Override + public Object clone() throws CloneNotSupportedException { + CLONES.incrementAndGet(); + return super.clone(); + } + } + + /** + * Captures the arguments handed to the mapping synchronization instead of + * running it against a database. + */ + static class CapturingFactory extends JDBCBrokerFactory { + private static final long serialVersionUID = 1L; + + ClassLoader loader; + String action; + int calls; + + CapturingFactory(JDBCConfiguration conf) { + super(conf); + } + + @Override + protected boolean synchronizeMappings(ClassLoader loader, JDBCConfiguration conf, String action) { + this.loader = loader; + this.action = action; + this.calls++; + return false; + } + } + + private CapturingFactory factory; + + @Before + public void setUp() { + JDBCConfigurationImpl conf = new JDBCConfigurationImpl(); + conf.setBrokerImpl(CountingBroker.class.getName()); + CLONES.set(0); + factory = new CapturingFactory(conf); + } + + private void assertNoBroker(String expectedAction) { + assertEquals("schema management must not instantiate a Broker", 0, CLONES.get()); + assertEquals(1, factory.calls); + assertSame(Thread.currentThread().getContextClassLoader(), factory.loader); + assertEquals(expectedAction, factory.action); + } + + @Test + public void testCreatePersistenceStructure() { + factory.createPersistenceStructure(false); + assertNoBroker("buildSchema(ForeignKeys=true,schemaAction='add')"); + } + + @Test + public void testCreatePersistenceStructureWithSchemas() { + factory.createPersistenceStructure(true); + assertNoBroker("buildSchema(ForeignKeys=true,schemaAction='createDB, add')"); + } + + @Test + public void testDropPersistenceStructure() { + factory.dropPersistenceStructure(false); + assertNoBroker("buildSchema(ForeignKeys=true,schemaAction='drop')"); + } + + @Test + public void testDropPersistenceStructureWithSchemas() { + factory.dropPersistenceStructure(true); + assertNoBroker("buildSchema(ForeignKeys=true,schemaAction='drop, dropDB')"); + } + + @Test + public void testValidatePersistenceStructure() throws Exception { + factory.validatePersistenceStructure(); + assertNoBroker("validate(ForeignKeys=true)"); + } + + @Test + public void testTruncateData() { + factory.truncateData(); + assertNoBroker("buildSchema(ForeignKeys=true,schemaAction='refresh,deleteTableContents')"); + } +}