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            String tmName = "TM_" + ManagementFactory.getRuntimeMXBean().getName();
094            if (tmName.length() > 30) {
095                tmName = tmName.substring(0, 30);
096            }
097            properties.setProperty(AbstractUserTransactionServiceFactory.TM_UNIQUE_NAME_PROPERTY_NAME, tmName);
098            properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_NAME_PROPERTY_NAME,
099                    "log_");
100            properties.setProperty(AbstractUserTransactionServiceFactory.LOG_BASE_DIR_PROPERTY_NAME,
101                    logDirectory);
102            properties.setProperty(AbstractUserTransactionServiceFactory.OUTPUT_DIR_PROPERTY_NAME,
103                    consoleOutputDirectory);
104            properties.setProperty(AbstractUserTransactionServiceFactory.THREADED_2PC_PROPERTY_NAME,
105                    "false");
106            properties.setProperty(AbstractUserTransactionServiceFactory.DEFAULT_JTA_TIMEOUT_PROPERTY_NAME,
107                    Integer.toString(transactionTimeout * 1000));
108            service = new UserTransactionServiceImp(properties);
109            try {
110                service.init();
111            } catch (SysException e) {
112                throw new RuntimeException(e.getErrors().pop().toString(), e);
113            }
114
115            manager = new UserTransactionManager();
116            manager.setStartupTransactionService(false);
117            try {
118                manager.init();
119            } catch (SystemException e) {
120                throw new RuntimeException(e);
121            }
122            return manager;
123        }
124    }
125
126    private static class AtomikosDataSourceWrapper implements JtaDataSourceWrapper {
127        @Inject
128        UserTransactionManager userTransactionManager;
129
130        @Override
131        public DataSource wrap(String name, XADataSource xaDataSource, PoolOptions poolOptions, String testQuery) {
132            AtomikosDataSourceBean wrapped = new AtomikosDataSourceBean();
133            wrapped.setXaDataSource(xaDataSource);
134            wrapped.setUniqueResourceName(name);
135
136            if (poolOptions == null) {
137                poolOptions = new PoolOptions();
138            }
139            if (poolOptions.getMaxLifeTime() != null && poolOptions.getMaxLifeTime() != 0) {
140                wrapped.setMaxLifetime(poolOptions.getMaxLifeTime());
141            } else {
142                // test query is only needed when we don't know how long Atomikos can keep connections in the pool
143                wrapped.setTestQuery(testQuery);
144            }
145
146            if (poolOptions.getMinPoolSize() != null) {
147                wrapped.setMinPoolSize(poolOptions.getMinPoolSize());
148            }
149            if (poolOptions.getMaxPoolSize() != null) {
150                wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize());
151            }
152            if (poolOptions.getMaxIdleTime() != null) {
153                wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime());
154            }
155            if (poolOptions.getReapTimeout() != null) {
156                wrapped.setReapTimeout(poolOptions.getReapTimeout());
157            }
158            return wrapped;
159        }
160
161        @Override
162        public void close(DataSource dataSource) {
163            ((AtomikosDataSourceBean) dataSource).close();
164        }
165    }
166
167    private static class AtomikosConnectionFactoryWrapper implements JtaConnectionFactoryWrapper {
168        @Override
169        public ConnectionFactory wrap(String name, XAConnectionFactory xaFactory,
170                PoolOptions poolOptions) {
171            AtomikosConnectionFactoryBean wrapped = new AtomikosConnectionFactoryBean();
172            wrapped.setXaConnectionFactory(xaFactory);
173            wrapped.setUniqueResourceName(name);
174            if (poolOptions == null) {
175                return wrapped;
176            }
177
178            if (poolOptions.getMinPoolSize() != null) {
179                wrapped.setMinPoolSize(poolOptions.getMinPoolSize());
180            }
181            if (poolOptions.getMaxPoolSize() != null) {
182                wrapped.setMaxPoolSize(poolOptions.getMaxPoolSize());
183            }
184            if (poolOptions.getMaxIdleTime() != null) {
185                wrapped.setMaxIdleTime(poolOptions.getMaxIdleTime());
186            }
187
188            if (poolOptions.getReapTimeout() != null) {
189                wrapped.setReapTimeout(poolOptions.getReapTimeout());
190            }
191            return wrapped;
192        }
193
194        @Override
195        public void close(ConnectionFactory connectionFactory) {
196            ((AtomikosConnectionFactoryBean) connectionFactory).close();
197        }
198    }
199
200    @Override
201    public Module configure() {
202        return new PrivateModule() {
203            @Override
204            protected void configure() {
205                bind(UserTransactionManager.class).toProvider(new ManagerProvider()).in(Singleton.class);
206                bind(TransactionManager.class).to(UserTransactionManager.class);
207                expose(TransactionManager.class);
208                bind(UserTransaction.class).to(UserTransactionManager.class);
209                expose(UserTransaction.class);
210                bind(JtaDataSourceWrapper.class).to(AtomikosDataSourceWrapper.class).in(Singleton.class);
211                expose(JtaDataSourceWrapper.class);
212                bind(JtaConnectionFactoryWrapper.class).to(AtomikosConnectionFactoryWrapper.class).in(Singleton.class);
213                expose(JtaConnectionFactoryWrapper.class);
214            }
215        };
216    }
217
218    @Override
219    public void close() {
220        if (manager != null) {
221            manager.close();
222        }
223        if (service != null) {
224            service.shutdownWait();
225        }
226    }
227}