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}