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 */
049@XmlRootElement(name = "atomikos")
050public class Atomikos extends JtaService {
051    /**
052     * Specifies the maximum number of active transactions.
053     * <p>
054     * A negative value means infinite amount. You will get an IllegalStateException
055     * with error message "Max number of active transactions reached" if you call
056     * {@link UserTransaction#begin()} while there are already n concurrent transactions running,
057     * n being this value.
058     * </p>
059     */
060    @XmlElement
061    private Integer maxActiveTransactions = -1;
062
063    @XmlElement
064    @XmlDefaultValue("${dataHome}/atomikos/logs")
065    private String logDirectory;
066
067    @XmlElement
068    @XmlDefaultValue("${dataHome}/atomikos/")
069    private String consoleOutputDirectory;
070
071    /**
072     * The default timeout (in seconds) that is set for transactions when no timeout is specified.
073     */
074    @XmlElement
075    @XmlDefaultValue("60")
076    private Integer transactionTimeout;
077
078    private UserTransactionManager manager;
079    private UserTransactionServiceImp service;
080
081    public class ManagerProvider implements Provider<UserTransactionManager> {
082        @Override
083        public UserTransactionManager get() {
084            System.setProperty(UserTransactionServiceImp.NO_FILE_PROPERTY_NAME, "true");
085            System.setProperty(UserTransactionServiceImp.HIDE_INIT_FILE_PATH_PROPERTY_NAME,
086                    "true");
087            System.setProperty("com.atomikos.icatch.service",
088                    UserTransactionServiceFactory.class.getCanonicalName());
089
090            Properties properties = new Properties();
091            properties.setProperty(AbstractUserTransactionServiceFactory.MAX_ACTIVES_PROPERTY_NAME,
092                    Integer.toString(maxActiveTransactions));
093            properties.setProperty(AbstractUserTransactionServiceFactory.TM_UNIQUE_NAME_PROPERTY_NAME,
094                    "TM_" + ManagementFactory.getRuntimeMXBean().getName());
095            properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_NAME_PROPERTY_NAME,
096                    "log_");
097            properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_DIR_PROPERTY_NAME,
098                    logDirectory);
099            properties.setProperty(AbstractUserTransactionServiceFactory.OUTPUT_DIR_PROPERTY_NAME,
100                    consoleOutputDirectory);
101            properties.setProperty(AbstractUserTransactionServiceFactory.THREADED_2PC_PROPERTY_NAME,
102                    "false");
103            properties.setProperty(AbstractUserTransactionServiceFactory.DEFAULT_JTA_TIMEOUT_PROPERTY_NAME,
104                    Integer.toString(transactionTimeout * 1000));
105            service = new UserTransactionServiceImp(properties);
106            try {
107                service.init();
108            } catch (SysException e) {
109                throw new RuntimeException(e.getErrors().pop().toString(), e);
110            }
111
112            manager = new UserTransactionManager();
113            manager.setStartupTransactionService(false);
114            try {
115                manager.init();
116            } catch (SystemException e) {
117                throw new RuntimeException(e);
118            }
119            return manager;
120        }
121    }
122
123    private static class AtomikosDataSourceWrapper implements JtaDataSourceWrapper {
124        @Inject
125        UserTransactionManager userTransactionManager;
126
127        @Override
128        public DataSource wrap(String name, XADataSource xaDataSource, PoolOptions poolOptions, String testQuery) {
129            AtomikosDataSourceBean wrapped = new AtomikosDataSourceBean();
130            wrapped.setXaDataSource(xaDataSource);
131            wrapped.setUniqueResourceName(name);
132
133            if (poolOptions == null) {
134                poolOptions = new PoolOptions();
135            }
136            if (poolOptions.getMaxLifeTime() != null && poolOptions.getMaxLifeTime() != 0) {
137                wrapped.setMaxLifetime(poolOptions.getMaxLifeTime());
138            } else {
139                // test query is only needed when we don't know how long Atomikos can keep connections in the pool
140                wrapped.setTestQuery(testQuery);
141            }
142
143            if (poolOptions.getMinPoolSize() != null) {
144                wrapped.setMinPoolSize(poolOptions.getMinPoolSize());
145            }
146            if (poolOptions.getMaxPoolSize() != null) {
147                wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize());
148            }
149            if (poolOptions.getMaxIdleTime() != null) {
150                wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime());
151            }
152            if (poolOptions.getReapTimeout() != null) {
153                wrapped.setReapTimeout(poolOptions.getReapTimeout());
154            }
155            return wrapped;
156        }
157
158        @Override
159        public void close(DataSource dataSource) {
160            ((AtomikosDataSourceBean) dataSource).close();
161        }
162    }
163
164    private static class AtomikosConnectionFactoryWrapper implements JtaConnectionFactoryWrapper {
165        @Override
166        public ConnectionFactory wrap(String name, XAConnectionFactory xaFactory,
167                PoolOptions poolOptions) {
168            AtomikosConnectionFactoryBean wrapped = new AtomikosConnectionFactoryBean();
169            wrapped.setXaConnectionFactory(xaFactory);
170            wrapped.setUniqueResourceName(name);
171            if (poolOptions == null) {
172                return wrapped;
173            }
174
175            if (poolOptions.getMinPoolSize() != null) {
176                wrapped.setMinPoolSize(poolOptions.getMinPoolSize());
177            }
178            if (poolOptions.getMaxPoolSize() != null) {
179                wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize());
180            }
181            if (poolOptions.getMaxIdleTime() != null) {
182                wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime());
183            }
184
185            if (poolOptions.getReapTimeout() != null) {
186                wrapped.setReapTimeout(poolOptions.getReapTimeout());
187            }
188            return wrapped;
189        }
190
191        @Override
192        public void close(ConnectionFactory connectionFactory) {
193            ((AtomikosConnectionFactoryBean) connectionFactory).close();
194        }
195    }
196
197    @Override
198    public Module configure() {
199        return new PrivateModule() {
200            @Override
201            protected void configure() {
202                bind(UserTransactionManager.class).toProvider(new ManagerProvider()).in(Singleton.class);
203                bind(TransactionManager.class).to(UserTransactionManager.class);
204                expose(TransactionManager.class);
205                bind(UserTransaction.class).to(UserTransactionManager.class);
206                expose(UserTransaction.class);
207                bind(JtaDataSourceWrapper.class).to(AtomikosDataSourceWrapper.class).in(Singleton.class);
208                expose(JtaDataSourceWrapper.class);
209                bind(JtaConnectionFactoryWrapper.class).to(AtomikosConnectionFactoryWrapper.class).in(Singleton.class);
210                expose(JtaConnectionFactoryWrapper.class);
211            }
212        };
213    }
214
215    @Override
216    public void close() {
217        if (manager != null) {
218            manager.close();
219        }
220        if (service != null) {
221            service.shutdownWait();
222        }
223    }
224}