Hi,
I try to apply hibernate multi-tenancy on our current project, that had been implemented with Webservice, EJB, JPA and JBoss 7.1.1 Final.
And I got a exception when I just enable my services on JBoss by administrator tool:
Code:
14:16:26,320 INFO [org.jboss.ws.common.management.DefaultEndpointRegistry] (MSC service thread 1-2) register: jboss.ws:context=test_ejb,endpoint=ContactWebService
14:16:26,320 INFO [org.jboss.as.jpa] (MSC service thread 1-4) JBAS011402: Starting Persistence Unit Service 'test_ear.ear/test_service-1.01.28.jar#xcrmPU'
14:16:26,320 INFO [org.hibernate.ejb.Ejb3Configuration] (MSC service thread 1-4) HHH000204: Processing PersistenceUnitInfo [
name: xcrmPU
...]
14:16:26,320 INFO [org.jboss.ws.common.management.DefaultEndpointRegistry] (MSC service thread 1-3) register: jboss.ws:context=test_ejb,endpoint=StudyPeriodWebService
14:16:26,351 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-4) MSC00001: Failed to start service jboss.persistenceunit."test_ear.ear/test_service-1.01.28.jar#xcrmPU": org.jboss.msc.service.StartException in service jboss.persistenceunit."test_ear.ear/test_service-1.01.28.jar#xcrmPU": Failed to start service
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1767) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) [rt.jar:1.6.0_30]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) [rt.jar:1.6.0_30]
at java.lang.Thread.run(Thread.java:662) [rt.jar:1.6.0_30]
Caused by: java.lang.NullPointerException
at org.hibernate.engine.jdbc.internal.JdbcServicesImpl$MultiTenantConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcServicesImpl.java:271)
at org.hibernate.engine.jdbc.internal.JdbcServicesImpl.configure(JdbcServicesImpl.java:119)
at org.hibernate.service.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:75)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:159)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:131)
at org.hibernate.cfg.SettingsFactory.buildSettings(SettingsFactory.java:71)
at org.hibernate.cfg.Configuration.buildSettingsInternal(Configuration.java:2270)
at org.hibernate.cfg.Configuration.buildSettings(Configuration.java:2266)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1735)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:84)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:904)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:889)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:73)
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.createContainerEntityManagerFactory(PersistenceUnitServiceImpl.java:162)
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.start(PersistenceUnitServiceImpl.java:85)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1811) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1746) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
... 3 more
What I see the JBoss didn't call my DatabaseBasedMultiTenantConnectionProvider instance, it seem it uses the default MultiTenantConnectionProvider in time it actives my ejb services (I guess).
My implementation similar to this https://hibernate.onjira.com/browse/HHH-7659 (please by pass the bug, refer the attach code, I try to implement same structure)
Here is what I have done (Sorry for long content, I don't know how to attach files):
- Config persistence.xml like this:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
<persistence-unit name="xcrmPU" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<shared-cache-mode>NONE</shared-cache-mode>
<properties>
<!-- property name="hibernate.connection.datasource" value="java:jboss/xlinedta001_TestMySql"/-->
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.format_sql" value="false"/>
<!-- property name="transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/-->
<property name="hibernate.connection.autocommit" value="true"/>
<property name="hibernate.multiTenancy" value="DATABASE"/>
<property name="hibernate.tenant_identifier_resolver" value="DatabaseBasedMultiTenantResolver"/>
<property name="hibernate.multi_tenant_connection_provider" value="DatabaseBasedMultiTenantConnectionProvider"/>
<!-- (for glassfish) property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.SunONETransactionManagerLookup"/ -->
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
</properties>
</persistence-unit>
</persistence>
- Implement DatabaseBasedMultiTenantResolver, get the user Principal from session context:
Code:
public class DatabaseBasedMultiTenantResolver implements CurrentTenantIdentifierResolver {
private static Log log = LogFactory.getLog(DatabaseBasedMultiTenantResolver.class);
@Override
public String resolveCurrentTenantIdentifier() {
log.info("resolveCurrentTenantIdentifier: ");
try {
SessionContext context = null;
try {
InitialContext ic = new InitialContext();
context =
(SessionContext) ic.lookup("java:comp/EJBContext");
log.info("Look up EJBContext by standard name: " + context);
} catch (NamingException ex) {
throw new IllegalStateException(ex);
}
String userName = context.getCallerPrincipal().getName();
log.info("Look up user name: " + userName);
return userName;
} catch (Throwable e) {
log.error("error lookup environment from session context");
}
return "TestMySql";
}
@Override
public boolean validateExistingCurrentSessions() {
return false;
}
}
- Implement DatabaseBasedMultiTenantConnectionProvider:
Code:
public class DatabaseBasedMultiTenantConnectionProvider extends AbstractMultiTenantConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
private static Log log = LogFactory.getLog(DatabaseBasedMultiTenantConnectionProvider.class);
private static final long serialVersionUID = -4491110199823075559L;
private Map<String, C3P0ConnectionProvider> providers = new HashMap<String, C3P0ConnectionProvider>();
private ServiceRegistryImplementor serviceRegistry;
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
@Override
protected ConnectionProvider getAnyConnectionProvider() {
return createDefaultConnectionProvider();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
log.info("createDefaultConnectionProvider: get connection");
return super.getConnection(tenantIdentifier);
}
private ConnectionProvider createDefaultConnectionProvider() {
log.info("createDefaultConnectionProvider: ");
Map<String, String> lSettings = new HashMap<String, String>();
lSettings.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
lSettings.put("hibernate.connection.url", "jdbc:mysql://192.168.1.10:3306/defaultDB?autoReconnect=true");
lSettings.put("hibernate.connection.username", "root");
lSettings.put("hibernate.connection.password", "dbavn");
lSettings.put("hibernate.c3p0.min_size", "5");
lSettings.put("hibernate.c3p0.max_size", "20");
lSettings.put("hibernate.c3p0.timeout", "1800");
lSettings.put("hibernate.c3p0.max_statements", "50");
lSettings.put("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect");
lSettings.put("hibernate.connection.autocommit", "true");
C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
connectionProvider.injectServices(serviceRegistry);
connectionProvider.configure(lSettings);
return connectionProvider;
}
/**
*
* @param tenantIdentifier
* @return
*/
@Override
protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
log.info("selectConnectionProvider: " + tenantIdentifier);
if (!providers.containsKey(tenantIdentifier)) {
Map<String, String> lSettings = new HashMap<String, String>();
if (tenantIdentifier.equalsIgnoreCase("TestMySql")) {
lSettings.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
lSettings.put("hibernate.connection.url", "jdbc:mysql://192.168.1.11:3306/db001?autoReconnect=true");
lSettings.put("hibernate.connection.username", "root");
lSettings.put("hibernate.connection.password", "dbavn");
lSettings.put("hibernate.c3p0.min_size", "5");
lSettings.put("hibernate.c3p0.max_size", "20");
lSettings.put("hibernate.c3p0.timeout", "1800");
lSettings.put("hibernate.c3p0.max_statements", "50");
lSettings.put("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect");
lSettings.put("hibernate.connection.autocommit", "true");
} else if (tenantIdentifier.equalsIgnoreCase("TestSqlServer")) {
lSettings.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
lSettings.put("hibernate.connection.url", "jdbc:sqlserver://192.168.1.12;databaseName=db001");
lSettings.put("hibernate.connection.username", "admin");
lSettings.put("hibernate.connection.password", "admin");
lSettings.put("hibernate.c3p0.min_size", "5");
lSettings.put("hibernate.c3p0.max_size", "20");
lSettings.put("hibernate.c3p0.timeout", "1800");
lSettings.put("hibernate.c3p0.max_statements", "50");
lSettings.put("hibernate.dialect", "org.hibernate.dialect.SQLServerDialect");
lSettings.put("hibernate.connection.autocommit", "true");
} else if (tenantIdentifier.equalsIgnoreCase("TestOracle")) {
lSettings.put("hibernate.connection.driver_class", "oracle.jdbc.driver.OracleDriver");
lSettings.put("hibernate.connection.url", "jdbc:oracle:thin:@192.168.1.13:1521:xe");
lSettings.put("hibernate.connection.username", "db001");
lSettings.put("hibernate.connection.password", "dbavn");
lSettings.put("hibernate.c3p0.min_size", "5");
lSettings.put("hibernate.c3p0.max_size", "20");
lSettings.put("hibernate.c3p0.timeout", "1800");
lSettings.put("hibernate.c3p0.max_statements", "50");
lSettings.put("hibernate.dialect", "org.hibernate.dialect.Oracle9Dialect");
lSettings.put("hibernate.connection.autocommit", "true");
} else {
return this.createDefaultConnectionProvider();
}
C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
connectionProvider.injectServices(serviceRegistry);
connectionProvider.configure(lSettings);
providers.put(tenantIdentifier, connectionProvider);
}
return providers.get(tenantIdentifier);
}
}
- The all web service methods have to programmatic log in a user like:
Code:
UsernamePasswordHandler handler = new UsernamePasswordHandler(environment, "");
LoginContext lc = null;
try {
lc = new LoginContext("module-name", handler);
lc.login();
// Everything from here on is automatically associated with
// the Subject authenticated by the login
} catch (Exception e) {
// handle exception
throw new XcrmEjbException("TEST_TENANCY", e.getMessage());
}
- Update the configuration xml to use the latest hibernate on jboss:
+ The jboss-deployment-structure.xml file:
Code:
<jboss-deployment-structure>
<ear-subdeployments-isolated>true</ear-subdeployments-isolated>
<deployment>
<!-- Exclusions allow you to prevent the server from automatically adding some dependencies -->
<exclusions>
<module name="org.hibernate"/>
</exclusions>
</deployment>
</jboss-deployment-structure>
+ The jboss-app.xml:
Code:
<jboss-app>
<loader-repository>
org.myapp:loader=SomeClassloader
<loader-repository-config>
java2ParentDelegation=false
</loader-repository-config>
</loader-repository>
</jboss-app>
+ The login-config.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="urn:jboss:bean-deployer:2.0">
<application-policy name="test">
<authentication>
<!-- Add this line to your login-config.xml to include the ClientLoginModule propogation -->
<login-module code="org.jboss.security.ClientLoginModule" flag="required" ></login-module>
</authentication>
</application-policy>
</deployment>
+ The maven profile:
Code:
<profile>
<id>glassfish</id>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.2.0.Final</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.2.0.CR1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>4.2.0.CR1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.CR1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<version>2.6</version>
<configuration>
<displayName>test_ear</displayName>
<includeInApplicationXml>true</includeInApplicationXml>
<modules>
<ejbModule>
<groupId>ch.xpertline</groupId>
<artifactId>test_service</artifactId>
</ejbModule>
<jarModule>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<jarModule>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
</modules>
</configuration>
</plugin>
<plugin>
<groupId>org.glassfish.maven.plugin</groupId>
<artifactId>maven-glassfish-plugin</artifactId>
<version>2.1</version>
<configuration>
<glassfishDirectory>${glassfish.home}</glassfishDirectory>
<user>${domain.username}</user>
<adminPassword>${domain.password}</adminPassword>
<autoCreate>true</autoCreate>
<debug>true</debug>
<echo>false</echo>
<terse>true</terse>
<skip>${test.int.skip}</skip>
<components>
<component>
<name>${project.artifactId}</name>
<artifact>${project.build.directory}/${project.build.finalName}.ear</artifact>
</component>
</components>
</configuration>
</plugin>
I don't know JBoss a lot, so all configurations I search on Internet, not sure they are correct.
Thank you!