001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 003 * in compliance with the License. You may obtain a copy of the License at 004 * 005 * http://www.apache.org/licenses/LICENSE-2.0 006 * 007 * Unless required by applicable law or agreed to in writing, software distributed under the License 008 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 009 * or implied. See the License for the specific language governing permissions and limitations under 010 * the License. 011 */ 012package org.atteo.moonshine.atomikos; 013 014import java.lang.management.ManagementFactory; 015import java.util.Properties; 016 017import javax.inject.Inject; 018import javax.inject.Singleton; 019import javax.jms.ConnectionFactory; 020import javax.jms.XAConnectionFactory; 021import javax.sql.DataSource; 022import javax.sql.XADataSource; 023import javax.transaction.SystemException; 024import javax.transaction.TransactionManager; 025import javax.transaction.UserTransaction; 026import javax.xml.bind.annotation.XmlElement; 027import javax.xml.bind.annotation.XmlRootElement; 028 029import org.atteo.config.XmlDefaultValue; 030import org.atteo.moonshine.jta.JtaConnectionFactoryWrapper; 031import org.atteo.moonshine.jta.JtaDataSourceWrapper; 032import org.atteo.moonshine.jta.JtaService; 033import org.atteo.moonshine.jta.PoolOptions; 034 035import com.atomikos.icatch.SysException; 036import com.atomikos.icatch.config.UserTransactionServiceImp; 037import com.atomikos.icatch.config.imp.AbstractUserTransactionServiceFactory; 038import com.atomikos.icatch.jta.UserTransactionManager; 039import com.atomikos.icatch.standalone.UserTransactionServiceFactory; 040import com.atomikos.jdbc.AtomikosDataSourceBean; 041import com.atomikos.jms.AtomikosConnectionFactoryBean; 042import com.google.inject.Module; 043import com.google.inject.PrivateModule; 044import com.google.inject.Provider; 045 046/** 047 * Atomikos JTA implementation. 048 * <p> 049 * Note: Atomikos can be started only once in the same JVM. This is because of limitation 050 * of the Atomikos itself. (It used static fields in {@link com.atomikos.icatch.system.Configuration} class.) 051 * You will get {@link IllegalStateException} when started the second time. 052 * </p> 053 */ 054@XmlRootElement(name = "atomikos") 055public class Atomikos extends JtaService { 056 /** 057 * Specifies the maximum number of active transactions. 058 * <p> 059 * A negative value means infinite amount. You will get an IllegalStateException 060 * with error message "Max number of active transactions reached" if you call 061 * {@link UserTransaction#begin()} while there are already n concurrent transactions running, 062 * n being this value. 063 * </p> 064 */ 065 @XmlElement 066 private Integer maxActiveTransactions = -1; 067 068 @XmlElement 069 @XmlDefaultValue("${dataHome}/atomikos/logs") 070 private String logDirectory; 071 072 @XmlElement 073 @XmlDefaultValue("${dataHome}/atomikos/") 074 private String consoleOutputDirectory; 075 076 /** 077 * The default timeout (in seconds) that is set for transactions when no timeout is specified. 078 */ 079 @XmlElement 080 @XmlDefaultValue("60") 081 private Integer transactionTimeout; 082 083 private UserTransactionManager manager; 084 private UserTransactionServiceImp service; 085 private static boolean initialized = false; 086 087 private synchronized static void turnOn() { 088 if (initialized) { 089 throw new IllegalStateException("Atomikos cannot be started two times in the same JVM"); 090 } 091 initialized = true; 092 } 093 094 private synchronized static void turnOff() { 095 initialized = false; 096 } 097 098 public class ManagerProvider implements Provider<UserTransactionManager> { 099 @Override 100 public UserTransactionManager get() { 101 turnOn(); 102 System.setProperty(UserTransactionServiceImp.NO_FILE_PROPERTY_NAME, "true"); 103 System.setProperty(UserTransactionServiceImp.HIDE_INIT_FILE_PATH_PROPERTY_NAME, 104 "true"); 105 System.setProperty("com.atomikos.icatch.service", 106 UserTransactionServiceFactory.class.getCanonicalName()); 107 108 Properties properties = new Properties(); 109 properties.setProperty(AbstractUserTransactionServiceFactory.MAX_ACTIVES_PROPERTY_NAME, 110 Integer.toString(maxActiveTransactions)); 111 String tmName = "TM_" + ManagementFactory.getRuntimeMXBean().getName(); 112 if (tmName.length() > 30) { 113 tmName = tmName.substring(0, 30); 114 } 115 properties.setProperty(AbstractUserTransactionServiceFactory.TM_UNIQUE_NAME_PROPERTY_NAME, tmName); 116 properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_NAME_PROPERTY_NAME, 117 "log_"); 118 properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_DIR_PROPERTY_NAME, 119 logDirectory); 120 properties.setProperty(AbstractUserTransactionServiceFactory.OUTPUT_DIR_PROPERTY_NAME, 121 consoleOutputDirectory); 122 properties.setProperty(AbstractUserTransactionServiceFactory.THREADED_2PC_PROPERTY_NAME, 123 "false"); 124 properties.setProperty(AbstractUserTransactionServiceFactory.DEFAULT_JTA_TIMEOUT_PROPERTY_NAME, 125 Integer.toString(transactionTimeout * 1000)); 126 service = new UserTransactionServiceImp(properties); 127 try { 128 service.init(); 129 } catch (SysException e) { 130 throw new RuntimeException(e.getErrors().pop().toString(), e); 131 } 132 133 manager = new UserTransactionManager(); 134 manager.setStartupTransactionService(false); 135 try { 136 manager.init(); 137 } catch (SystemException e) { 138 throw new RuntimeException(e); 139 } 140 return manager; 141 } 142 } 143 144 private static class AtomikosDataSourceWrapper implements JtaDataSourceWrapper { 145 @Inject 146 UserTransactionManager userTransactionManager; 147 148 @Override 149 public DataSource wrap(String name, XADataSource xaDataSource, PoolOptions poolOptions, String testQuery) { 150 AtomikosDataSourceBean wrapped = new AtomikosDataSourceBean(); 151 wrapped.setXaDataSource(xaDataSource); 152 wrapped.setUniqueResourceName(name); 153 154 if (poolOptions == null) { 155 poolOptions = new PoolOptions(); 156 } 157 if (poolOptions.getMaxLifeTime() != null && poolOptions.getMaxLifeTime() != 0) { 158 wrapped.setMaxLifetime(poolOptions.getMaxLifeTime()); 159 } else { 160 // test query is only needed when we don't know how long Atomikos can keep connections in the pool 161 wrapped.setTestQuery(testQuery); 162 } 163 164 if (poolOptions.getMinPoolSize() != null) { 165 wrapped.setMinPoolSize(poolOptions.getMinPoolSize()); 166 } 167 if (poolOptions.getMaxPoolSize() != null) { 168 wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize()); 169 } 170 if (poolOptions.getMaxIdleTime() != null) { 171 wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime()); 172 } 173 if (poolOptions.getReapTimeout() != null) { 174 wrapped.setReapTimeout(poolOptions.getReapTimeout()); 175 } 176 return wrapped; 177 } 178 179 @Override 180 public void close(DataSource dataSource) { 181 ((AtomikosDataSourceBean) dataSource).close(); 182 } 183 } 184 185 private static class AtomikosConnectionFactoryWrapper implements JtaConnectionFactoryWrapper { 186 @Override 187 public ConnectionFactory wrap(String name, XAConnectionFactory xaFactory, 188 PoolOptions poolOptions) { 189 AtomikosConnectionFactoryBean wrapped = new AtomikosConnectionFactoryBean(); 190 wrapped.setXaConnectionFactory(xaFactory); 191 wrapped.setUniqueResourceName(name); 192 if (poolOptions == null) { 193 return wrapped; 194 } 195 196 if (poolOptions.getMinPoolSize() != null) { 197 wrapped.setMinPoolSize(poolOptions.getMinPoolSize()); 198 } 199 if (poolOptions.getMaxPoolSize() != null) { 200 wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize()); 201 } 202 if (poolOptions.getMaxIdleTime() != null) { 203 wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime()); 204 } 205 206 if (poolOptions.getReapTimeout() != null) { 207 wrapped.setReapTimeout(poolOptions.getReapTimeout()); 208 } 209 return wrapped; 210 } 211 212 @Override 213 public void close(ConnectionFactory connectionFactory) { 214 ((AtomikosConnectionFactoryBean) connectionFactory).close(); 215 } 216 } 217 218 @Override 219 public Module configure() { 220 return new PrivateModule() { 221 @Override 222 protected void configure() { 223 bind(UserTransactionManager.class).toProvider(new ManagerProvider()).in(Singleton.class); 224 bind(TransactionManager.class).to(UserTransactionManager.class); 225 expose(TransactionManager.class); 226 bind(UserTransaction.class).to(UserTransactionManager.class); 227 expose(UserTransaction.class); 228 bind(JtaDataSourceWrapper.class).to(AtomikosDataSourceWrapper.class).in(Singleton.class); 229 expose(JtaDataSourceWrapper.class); 230 bind(JtaConnectionFactoryWrapper.class).to(AtomikosConnectionFactoryWrapper.class).in(Singleton.class); 231 expose(JtaConnectionFactoryWrapper.class); 232 } 233 }; 234 } 235 236 @Override 237 public void close() { 238 if (manager != null) { 239 manager.close(); 240 } 241 if (service != null) { 242 service.shutdownWait(); 243 } 244 turnOff(); 245 } 246}