/* * 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.lang.reflect.Field; 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; private boolean allConnectionsEqual = true; private boolean assumeOneMatch = false; private int connectionMaxWaitMilliseconds = 5000; private int connectionMaxIdleMinutes = 15; private ManagedConnectionFactory mcf; private int validationIntervalMs = -1; public boolean isAssumeOneMatch() { return assumeOneMatch; } public void setAssumeOneMatch(final boolean assumeOneMatch) { this.assumeOneMatch = assumeOneMatch; } public ManagedConnectionFactory getMcf() { return mcf; } public void setMcf(final ManagedConnectionFactory mcf) { this.mcf = mcf; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public ClassLoader getClassLoader() { return classLoader; } public void setClassLoader(final ClassLoader classLoader) { this.classLoader = classLoader; } public TransactionManager getTransactionManager() { return transactionManager; } public void setTransactionManager(final TransactionManager transactionManager) { this.transactionManager = transactionManager; } public String getTransactionSupport() { return transactionSupport; } public void setTransactionSupport(final String transactionSupport) { this.transactionSupport = transactionSupport; } public boolean isPooling() { return pooling; } public void setPooling(final boolean pooling) { this.pooling = pooling; } public String getPartitionStrategy() { return partitionStrategy; } public void setPartitionStrategy(final String partitionStrategy) { this.partitionStrategy = partitionStrategy; } public int getPoolMaxSize() { return poolMaxSize; } public void setPoolMaxSize(final int poolMaxSize) { this.poolMaxSize = poolMaxSize; } public int getPoolMinSize() { return poolMinSize; } public void setPoolMinSize(final int poolMinSize) { this.poolMinSize = poolMinSize; } public boolean isAllConnectionsEqual() { return allConnectionsEqual; } public void setAllConnectionsEqual(final boolean allConnectionsEqual) { this.allConnectionsEqual = allConnectionsEqual; } public int getConnectionMaxWaitMilliseconds() { return connectionMaxWaitMilliseconds; } public void setConnectionMaxWaitMilliseconds(final int connectionMaxWaitMilliseconds) { this.connectionMaxWaitMilliseconds = connectionMaxWaitMilliseconds; } public void setConnectionMaxWaitTime(final 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(final int connectionMaxIdleMinutes) { this.connectionMaxIdleMinutes = connectionMaxIdleMinutes; } public void setConnectionMaxIdleTime(final 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 validationIntervalMs < 0 ? -1 : (int) TimeUnit.MILLISECONDS.toMinutes(validationIntervalMs); } public void setValidationInterval(final int validationInterval) { this.validationIntervalMs = validationInterval < 0 ? -1 : (int) TimeUnit.MINUTES.toMillis(validationInterval); } public void setValidationInterval(final Duration validationInterval) { if (validationInterval.getUnit() == null) { validationInterval.setUnit(TimeUnit.MINUTES); } validationIntervalMs = (int) validationInterval.getUnit().toMillis(validationInterval.getTime()); } public GenericConnectionManager create() { final 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(); } final TransactionSupport txSupport = createTransactionSupport(); final 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, name); } final GenericConnectionManager mgr; if (validationIntervalMs >= 0 && mcf instanceof ValidatingManagedConnectionFactory) { if (name == null) { name = getClass().getSimpleName(); } mgr = new ValidatingGenericConnectionManager(txSupport, poolingSupport, null, new AutoConnectionTracker(), tm, mcf, name, classLoader, validationIntervalMs); } else { mgr = new GenericConnectionManager(txSupport, poolingSupport, null, new AutoConnectionTracker(), tm, mcf, name, classLoader); } return mgr; } 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, assumeOneMatch); } 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, assumeOneMatch, true, false); } else if ("by-subject".equalsIgnoreCase(partitionStrategy)) { // partition by caller subject return new PartitionedPool(poolMaxSize, poolMinSize, connectionMaxWaitMilliseconds, connectionMaxIdleMinutes, allConnectionsEqual, !allConnectionsEqual, assumeOneMatch, false, true); } throw new IllegalArgumentException("Unknown partition strategy " + partitionStrategy); } private class SimpleRecoverableTransactionManager implements RecoverableTransactionManager { private final TransactionManager delegate; private final String name; public SimpleRecoverableTransactionManager(final TransactionManager transactionManager, final String name) { this.delegate = transactionManager; this.name = name; } @Override public void recoveryError(final Exception e) { throw new UnsupportedOperationException(); } public void registerNamedXAResourceFactory(final NamedXAResourceFactory namedXAResourceFactory) { if ((name == null && namedXAResourceFactory == null || (namedXAResourceFactory != null && namedXAResourceFactory.getName() == null)) || (name != null && namedXAResourceFactory != null && name.equals(namedXAResourceFactory.getName()))) { return; } throw new UnsupportedOperationException(); } public void unregisterNamedXAResourceFactory(final String namedXAResourceFactoryName) { if ((name == null && namedXAResourceFactoryName == null) || (name != null && name.equals(namedXAResourceFactoryName))) { return; } 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(final 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 { final Field resizeLock = AbstractSinglePoolConnectionInterceptor.class.getDeclaredField("resizeLock"); if (!resizeLock.isAccessible()) { resizeLock.setAccessible(true); } foundLock = (ReadWriteLock) resizeLock.get(current); } catch (final IllegalAccessException | NoSuchFieldException e) { // no-op } break; } // look next try { current = (ConnectionInterceptor) Reflections.get(current, "next"); } catch (final Exception e) { current = null; } } while (current != null); this.lock = foundLock; Object foundPool = null; if (current instanceof AbstractSinglePoolConnectionInterceptor) { foundPool = Reflections.get(current, "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, autoConnectionTracker); } 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; private final AutoConnectionTracker autoConnectionTracker; public ValidatingTask(final ConnectionInterceptor stack, final ReadWriteLock lock, final Object pool, final AutoConnectionTracker autoConnectionTracker) { this.stack = stack; this.lock = lock; this.pool = pool == null ? new Object() : pool; this.autoConnectionTracker = autoConnectionTracker; if (!SinglePoolConnectionInterceptor.class.isInstance(stack) && !SinglePoolMatchAllConnectionInterceptor.class.isInstance(stack)) { log.info("stack " + stack + " currently not supported, only AutoConnectionTracker ref will be used for validation"); } } @Override public void run() { synchronized (pool) { if (lock != null) { lock.writeLock().lock(); } try { final Map<ManagedConnection, ManagedConnectionInfo> connections; if (stack instanceof SinglePoolConnectionInterceptor) { connections = new HashMap<>(); for (final ManagedConnectionInfo info : (List<ManagedConnectionInfo>) pool) { connections.put(info.getManagedConnection(), info); } } else if (stack instanceof SinglePoolMatchAllConnectionInterceptor) { connections = (Map<ManagedConnection, ManagedConnectionInfo>) pool; } else { connections = new HashMap<>(); } for (final ManagedConnectionInfo info : autoConnectionTracker.connections()) { connections.put(info.getManagedConnection(), info); } // destroy invalid connections try { final Set<ManagedConnection> invalids = ValidatingManagedConnectionFactory.class.cast(getManagedConnectionFactory()) .getInvalidConnections(connections.keySet()); if (invalids != null) { for (final ManagedConnection invalid : invalids) { final ManagedConnectionInfo mci = connections.get(invalid); if (mci != null) { stack.returnConnection(new ConnectionInfo(mci), ConnectionReturnAction.DESTROY); continue; } log.error("Can't find " + invalid + " in " + pool); } } } catch (final ResourceException e) { log.error(e.getMessage(), e); } } finally { if (lock != null) { lock.writeLock().unlock(); } } } } } } }