blob: eabcece94b2a82fad2e61f482bf7c343a9f385dd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.resource;
import org.apache.geronimo.connector.outbound.AbstractSinglePoolConnectionInterceptor;
import org.apache.geronimo.connector.outbound.ConnectionInfo;
import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
import org.apache.geronimo.connector.outbound.GenericConnectionManager;
import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
import org.apache.geronimo.connector.outbound.MultiPoolConnectionInterceptor;
import org.apache.geronimo.connector.outbound.SinglePoolConnectionInterceptor;
import org.apache.geronimo.connector.outbound.SinglePoolMatchAllConnectionInterceptor;
import org.apache.geronimo.connector.outbound.SubjectSource;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.LocalTransactions;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.NoPool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.NoTransactions;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PartitionedPool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.SinglePool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions;
import org.apache.geronimo.transaction.manager.NamedXAResourceFactory;
import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.util.Duration;
import org.apache.openejb.util.reflection.Reflections;
import javax.resource.ResourceException;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ValidatingManagedConnectionFactory;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
public class GeronimoConnectionManagerFactory {
private String name;
private ClassLoader classLoader;
private TransactionManager transactionManager;
// Type of transaction used by the ConnectionManager
// local, none, or xa
private String transactionSupport;
// pooling properties
private boolean pooling = true;
private String partitionStrategy; //: none, by-subject, by-connector-properties
private int poolMaxSize = 10;
private int poolMinSize = 0;
private boolean allConnectionsEqual = true;
private int connectionMaxWaitMilliseconds = 5000;
private int connectionMaxIdleMinutes = 15;
private int validationInterval = -1;
private ManagedConnectionFactory mcf;
public ManagedConnectionFactory getMcf() {
return mcf;
}
public void setMcf(ManagedConnectionFactory mcf) {
this.mcf = mcf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public String getTransactionSupport() {
return transactionSupport;
}
public void setTransactionSupport(String transactionSupport) {
this.transactionSupport = transactionSupport;
}
public boolean isPooling() {
return pooling;
}
public void setPooling(boolean pooling) {
this.pooling = pooling;
}
public String getPartitionStrategy() {
return partitionStrategy;
}
public void setPartitionStrategy(String partitionStrategy) {
this.partitionStrategy = partitionStrategy;
}
public int getPoolMaxSize() {
return poolMaxSize;
}
public void setPoolMaxSize(int poolMaxSize) {
this.poolMaxSize = poolMaxSize;
}
public int getPoolMinSize() {
return poolMinSize;
}
public void setPoolMinSize(int poolMinSize) {
this.poolMinSize = poolMinSize;
}
public boolean isAllConnectionsEqual() {
return allConnectionsEqual;
}
public void setAllConnectionsEqual(boolean allConnectionsEqual) {
this.allConnectionsEqual = allConnectionsEqual;
}
public int getConnectionMaxWaitMilliseconds() {
return connectionMaxWaitMilliseconds;
}
public void setConnectionMaxWaitMilliseconds(int connectionMaxWaitMilliseconds) {
this.connectionMaxWaitMilliseconds = connectionMaxWaitMilliseconds;
}
public void setConnectionMaxWaitTime(Duration connectionMaxWait) {
if (connectionMaxWait.getUnit() == null) {
connectionMaxWait.setUnit(TimeUnit.MILLISECONDS);
}
final long milleseconds = TimeUnit.MILLISECONDS.convert(connectionMaxWait.getTime(), connectionMaxWait.getUnit());
setConnectionMaxWaitMilliseconds((int) milleseconds);
}
public int getConnectionMaxIdleMinutes() {
return connectionMaxIdleMinutes;
}
public void setConnectionMaxIdleMinutes(int connectionMaxIdleMinutes) {
this.connectionMaxIdleMinutes = connectionMaxIdleMinutes;
}
public void setConnectionMaxIdleTime(Duration connectionMaxIdle) {
if (connectionMaxIdle.getUnit() == null) {
connectionMaxIdle.setUnit(TimeUnit.MINUTES);
}
final long minutes = TimeUnit.MINUTES.convert(connectionMaxIdle.getTime(), connectionMaxIdle.getUnit());
setConnectionMaxIdleMinutes((int) minutes);
}
public int getValidationInterval() {
return validationInterval;
}
public void setValidationInterval(int validationInterval) {
this.validationInterval = validationInterval;
}
public void setValidationInterval(final Duration validationInterval) {
if (validationInterval.getUnit() == null) {
validationInterval.setUnit(TimeUnit.MINUTES);
}
final long minutes = TimeUnit.MINUTES.convert(validationInterval.getTime(), validationInterval.getUnit());
setValidationInterval((int) minutes);
}
public GenericConnectionManager create() {
PoolingSupport poolingSupport = createPoolingSupport();
ClassLoader classLoader = this.classLoader;
if (classLoader == null) Thread.currentThread().getContextClassLoader();
if (classLoader == null) classLoader = getClass().getClassLoader();
if (classLoader == null) classLoader = ClassLoader.getSystemClassLoader();
TransactionSupport txSupport = createTransactionSupport();
RecoverableTransactionManager tm;
if (transactionManager instanceof RecoverableTransactionManager) {
tm = (RecoverableTransactionManager) transactionManager;
} else {
if (txSupport.isRecoverable()) {
throw new OpenEJBRuntimeException("currently recoverable tx support (xa) needs a geronimo tx manager");
}
tm = new SimpleRecoverableTransactionManager(transactionManager);
}
if (validationInterval >= 0 && mcf instanceof ValidatingManagedConnectionFactory) {
return new ValidatingGenericConnectionManager(txSupport, poolingSupport,
null, new AutoConnectionTracker(), tm,
mcf, name, classLoader, validationInterval);
}
return new GenericConnectionManager(txSupport, poolingSupport,
null, new AutoConnectionTracker(), tm,
mcf, name, classLoader);
}
private TransactionSupport createTransactionSupport() {
if (transactionSupport == null || "local".equalsIgnoreCase(transactionSupport)) {
return LocalTransactions.INSTANCE;
} else if ("none".equalsIgnoreCase(transactionSupport)) {
return NoTransactions.INSTANCE;
} else if ("xa".equalsIgnoreCase(transactionSupport)) {
return new XATransactions(true, false);
} else {
throw new IllegalArgumentException("Unknown transaction type " + transactionSupport);
}
}
private PoolingSupport createPoolingSupport() {
// pooling off?
if (!pooling) {
return new NoPool();
}
if (partitionStrategy == null || "none".equalsIgnoreCase(partitionStrategy)) {
// unpartitioned pool
return new SinglePool(poolMaxSize,
poolMinSize,
connectionMaxWaitMilliseconds,
connectionMaxIdleMinutes,
allConnectionsEqual,
!allConnectionsEqual,
false);
} else if ("by-connector-properties".equalsIgnoreCase(partitionStrategy)) {
// partition by contector properties such as username and password on a jdbc connection
return new PartitionedPool(poolMaxSize,
poolMinSize,
connectionMaxWaitMilliseconds,
connectionMaxIdleMinutes,
allConnectionsEqual,
!allConnectionsEqual,
false,
true,
false);
} else if ("by-subject".equalsIgnoreCase(partitionStrategy)) {
// partition by caller subject
return new PartitionedPool(poolMaxSize,
poolMinSize,
connectionMaxWaitMilliseconds,
connectionMaxIdleMinutes,
allConnectionsEqual,
!allConnectionsEqual,
false,
false,
true);
} else {
throw new IllegalArgumentException("Unknown partition strategy " + partitionStrategy);
}
}
private class SimpleRecoverableTransactionManager implements RecoverableTransactionManager {
private final TransactionManager delegate;
public SimpleRecoverableTransactionManager(final TransactionManager transactionManager) {
delegate = transactionManager;
}
@Override
public void recoveryError(final Exception e) {
throw new UnsupportedOperationException();
}
public void registerNamedXAResourceFactory(final NamedXAResourceFactory namedXAResourceFactory) {
throw new UnsupportedOperationException();
}
public void unregisterNamedXAResourceFactory(final String namedXAResourceFactoryName) {
throw new UnsupportedOperationException();
}
@Override
public void begin() throws NotSupportedException, SystemException {
delegate.begin();
}
@Override
public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException {
delegate.commit();
}
@Override
public int getStatus() throws SystemException {
return delegate.getStatus();
}
@Override
public Transaction getTransaction() throws SystemException {
return delegate.getTransaction();
}
@Override
public void resume(final Transaction transaction) throws IllegalStateException, InvalidTransactionException, SystemException {
delegate.resume(transaction);
}
@Override
public void rollback() throws IllegalStateException, SecurityException, SystemException {
delegate.rollback();
}
@Override
public void setRollbackOnly() throws IllegalStateException, SystemException {
delegate.setRollbackOnly();
}
@Override
public void setTransactionTimeout(int i) throws SystemException {
delegate.setTransactionTimeout(i);
}
@Override
public Transaction suspend() throws SystemException {
return delegate.suspend();
}
}
private static class ValidatingGenericConnectionManager extends GenericConnectionManager {
private static final Timer TIMER = new Timer("ValidatingGenericConnectionManagerTimer", true);
private final TimerTask validatingTask;
private final long validationInterval;
private final ReadWriteLock lock;
private final Object pool;
public ValidatingGenericConnectionManager(final TransactionSupport txSupport, final PoolingSupport poolingSupport, final SubjectSource o, final AutoConnectionTracker autoConnectionTracker, final RecoverableTransactionManager tm, final ManagedConnectionFactory mcf, final String name, final ClassLoader classLoader, final long interval) {
super(txSupport, poolingSupport, o, autoConnectionTracker, tm, mcf, name, classLoader);
validationInterval = interval;
final ConnectionInterceptor stack = interceptors.getStack();
ReadWriteLock foundLock = null;
ConnectionInterceptor current = stack;
do {
if (current instanceof AbstractSinglePoolConnectionInterceptor) {
try {
foundLock = (ReadWriteLock) AbstractSinglePoolConnectionInterceptor.class.getField("resizeLock").get(current);
} catch (IllegalAccessException e) {
// no-op
} catch (NoSuchFieldException e) {
// no-op
}
break;
}
// look next
try {
current = (ConnectionInterceptor) Reflections.get(current, "next");
} catch (Exception e) {
current = null;
}
} while (current != null);
this.lock = foundLock;
Object foundPool = null;
if (current instanceof AbstractSinglePoolConnectionInterceptor) {
foundPool = Reflections.get(stack, "pool");
} else if (current instanceof MultiPoolConnectionInterceptor) {
log.warn("validation on stack " + stack + " not supported");
}
this.pool = foundPool;
if (pool != null) {
validatingTask = new ValidatingTask(current, lock, pool);
} else {
validatingTask = null;
}
}
@Override
public void doStart() throws Exception {
super.doStart();
if (validatingTask != null) {
TIMER.schedule(validatingTask, validationInterval, validationInterval);
}
}
@Override
public void doStop() throws Exception {
if (validatingTask != null) {
validatingTask.cancel();
}
super.doStop();
}
private class ValidatingTask extends TimerTask {
private final ConnectionInterceptor stack;
private final ReadWriteLock lock;
private final Object pool;
public ValidatingTask(final ConnectionInterceptor stack, final ReadWriteLock lock, final Object pool) {
this.stack = stack;
this.lock = lock;
this.pool = pool;
}
@Override
public void run() {
if (lock != null) {
lock.writeLock().lock();
}
try {
final Map<ManagedConnection, ManagedConnectionInfo> connections;
if (stack instanceof SinglePoolConnectionInterceptor) {
connections = new HashMap<ManagedConnection, ManagedConnectionInfo>();
for (final ManagedConnectionInfo info : (List<ManagedConnectionInfo>) pool) {
connections.put(info.getManagedConnection(), info);
}
} else if (stack instanceof SinglePoolMatchAllConnectionInterceptor) {
connections = (Map<ManagedConnection, ManagedConnectionInfo>) pool;
} else {
log.warn("stack " + stack + " currently not supported");
return;
}
// destroy invalid connections
try {
final Set<ManagedConnection> invalids = ValidatingManagedConnectionFactory.class.cast(getManagedConnectionFactory())
.getInvalidConnections(connections.keySet());
if (invalids != null) {
for (final ManagedConnection invalid : invalids) {
stack.returnConnection(new ConnectionInfo(connections.get(invalid)), ConnectionReturnAction.DESTROY);
}
}
} catch (ResourceException e) {
log.error(e.getMessage(), e);
}
} finally {
if (lock != null) {
lock.writeLock().unlock();
}
}
}
}
}
}