| /** |
| * 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.geronimo.connector.outbound; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.resource.ResourceException; |
| import javax.resource.spi.ManagedConnection; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * SinglePoolConnectionInterceptor chooses a single connection from the pool. If selectOneAssumeMatch |
| * is true, it simply returns the selected connection. |
| * THIS SHOULD BE USED ONLY IF MAXIMUM SPEED IS ESSENTIAL AND YOU HAVE THOROUGLY CHECKED THAT |
| * MATCHING WOULD SUCCEED ON THE SELECTED CONNECTION. (i.e., read the docs on your connector |
| * to find out how matching works) |
| * If selectOneAssumeMatch is false, it checks with the ManagedConnectionFactory that the |
| * selected connection does match before returning it: if not it throws an exception. |
| * |
| * @version $Rev$ $Date$ |
| */ |
| public class SinglePoolConnectionInterceptor extends AbstractSinglePoolConnectionInterceptor { |
| private static final Log log = LogFactory.getLog(SinglePoolConnectionInterceptor.class.getName()); |
| |
| private boolean selectOneAssumeMatch; |
| |
| private PoolDeque pool; |
| |
| public SinglePoolConnectionInterceptor(final ConnectionInterceptor next, |
| int maxSize, |
| int minSize, |
| int blockingTimeoutMilliseconds, |
| int idleTimeoutMinutes, |
| boolean selectOneAssumeMatch) { |
| super(next, maxSize, minSize, blockingTimeoutMilliseconds, idleTimeoutMinutes); |
| pool = new PoolDeque(maxSize); |
| this.selectOneAssumeMatch = selectOneAssumeMatch; |
| } |
| |
| protected void internalGetConnection(ConnectionInfo connectionInfo) throws ResourceException { |
| synchronized (pool) { |
| if (destroyed) { |
| throw new ResourceException("ManagedConnection pool has been destroyed"); |
| } |
| |
| ManagedConnectionInfo newMCI = null; |
| if (pool.isEmpty()) { |
| next.getConnection(connectionInfo); |
| connectionCount++; |
| if (log.isTraceEnabled()) { |
| log.trace("Supplying new connection MCI: " + connectionInfo.getManagedConnectionInfo() + " MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " from pool: " + this); |
| } |
| return; |
| } else { |
| newMCI = pool.removeLast(); |
| } |
| if (connectionCount < minSize) { |
| timer.schedule(new FillTask(connectionInfo), 10); |
| } |
| if (selectOneAssumeMatch) { |
| connectionInfo.setManagedConnectionInfo(newMCI); |
| if (log.isTraceEnabled()) { |
| log.trace("Supplying pooled connection without checking matching MCI: " + connectionInfo.getManagedConnectionInfo() + " MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " from pool: " + this); |
| } |
| return; |
| } |
| try { |
| ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo(); |
| ManagedConnection matchedMC = |
| newMCI |
| .getManagedConnectionFactory() |
| .matchManagedConnections(Collections.singleton(newMCI.getManagedConnection()), |
| mci.getSubject(), |
| mci.getConnectionRequestInfo()); |
| if (matchedMC != null) { |
| connectionInfo.setManagedConnectionInfo(newMCI); |
| if (log.isTraceEnabled()) { |
| log.trace("Supplying pooled connection MCI: " + connectionInfo.getManagedConnectionInfo() + " MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " from pool: " + this); |
| } |
| } else { |
| //matching failed. |
| ConnectionInfo returnCI = new ConnectionInfo(); |
| returnCI.setManagedConnectionInfo(newMCI); |
| returnConnection(returnCI, |
| ConnectionReturnAction.RETURN_HANDLE); |
| throw new ResourceException("The pooling strategy does not match the MatchManagedConnections implementation. Please investigate and reconfigure this pool"); |
| } |
| } catch (ResourceException e) { |
| //something is wrong: destroy connection, rethrow, release permit |
| ConnectionInfo returnCI = new ConnectionInfo(); |
| returnCI.setManagedConnectionInfo(newMCI); |
| returnConnection(returnCI, |
| ConnectionReturnAction.DESTROY); |
| throw e; |
| } |
| } |
| } |
| |
| protected void internalDestroy() { |
| synchronized (pool) { |
| while (!pool.isEmpty()) { |
| ManagedConnection mc = pool.removeLast().getManagedConnection(); |
| if (mc != null) { |
| try { |
| mc.destroy(); |
| } |
| catch (ResourceException re) { } // ignore |
| } |
| } |
| } |
| } |
| |
| protected boolean internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) { |
| ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo(); |
| ManagedConnection mc = mci.getManagedConnection(); |
| if (connectionReturnAction == ConnectionReturnAction.RETURN_HANDLE) { |
| try { |
| mc.cleanup(); |
| } catch (ResourceException e) { |
| connectionReturnAction = ConnectionReturnAction.DESTROY; |
| } |
| } |
| boolean wasInPool = false; |
| synchronized (pool) { |
| // a bit redundant with returnConnection check in AbstractSinglePoolConnectionInterceptor, |
| // but checking here closes a small timing hole... |
| if (destroyed) { |
| try { |
| mc.destroy(); |
| } |
| catch (ResourceException re) { } // ignore |
| return pool.remove(mci); |
| } |
| |
| if (shrinkLater > 0) { |
| //nothing can get in the pool while shrinkLater > 0, so wasInPool is false here. |
| connectionReturnAction = ConnectionReturnAction.DESTROY; |
| shrinkLater--; |
| } else if (connectionReturnAction == ConnectionReturnAction.RETURN_HANDLE) { |
| mci.setLastUsed(System.currentTimeMillis()); |
| pool.add(mci); |
| return wasInPool; |
| } else { |
| wasInPool = pool.remove(mci); |
| } |
| } |
| //we must destroy connection. |
| next.returnConnection(connectionInfo, connectionReturnAction); |
| connectionCount--; |
| return wasInPool; |
| } |
| |
| public int getPartitionMaxSize() { |
| return pool.capacity(); |
| } |
| |
| protected void transferConnections(int maxSize, int shrinkNow) { |
| //1st example: copy 0 (none) |
| //2nd example: copy 10 (all) |
| PoolDeque oldPool = pool; |
| pool = new PoolDeque(maxSize); |
| //since we have replaced pool already, pool.remove will be very fast:-) |
| for (int i = 0; i < shrinkNow; i++) { |
| ConnectionInfo killInfo = new ConnectionInfo(oldPool.peek(i)); |
| internalReturn(killInfo, ConnectionReturnAction.DESTROY); |
| } |
| for (int i = shrinkNow; i < connectionCount; i++) { |
| pool.add(oldPool.peek(i)); |
| } |
| } |
| |
| public int getIdleConnectionCount() { |
| return pool.currentSize(); |
| } |
| |
| |
| protected void getExpiredManagedConnectionInfos(long threshold, List<ManagedConnectionInfo> killList) { |
| synchronized (pool) { |
| for (int i = 0; i < pool.currentSize(); i++) { |
| ManagedConnectionInfo mci = pool.peek(i); |
| if (mci.getLastUsed() < threshold) { |
| killList.add(mci); |
| } |
| } |
| } |
| } |
| |
| protected boolean addToPool(ManagedConnectionInfo mci) { |
| boolean added; |
| synchronized (pool) { |
| connectionCount++; |
| added = getPartitionMaxSize() > getIdleConnectionCount(); |
| if (added) { |
| pool.add(mci); |
| } |
| } |
| return added; |
| } |
| |
| static class PoolDeque { |
| |
| private final ManagedConnectionInfo[] deque; |
| private final int first = 0; |
| private int last = -1; |
| |
| public PoolDeque(int size) { |
| deque = new ManagedConnectionInfo[size]; |
| } |
| |
| //internal |
| public boolean isEmpty() { |
| return first > last; |
| } |
| |
| //internal |
| public void add(ManagedConnectionInfo mci) { |
| if (last == deque.length - 1) { |
| throw new IllegalStateException("deque is full: contents: " + Arrays.asList(deque)); |
| } |
| deque[++last] = mci; |
| } |
| |
| //internal |
| public ManagedConnectionInfo peek(int i) { |
| if (i < first || i > last) { |
| throw new IllegalStateException("index is out of current range"); |
| } |
| return deque[i]; |
| } |
| |
| //internal |
| public ManagedConnectionInfo removeLast() { |
| if (isEmpty()) { |
| throw new IllegalStateException("deque is empty"); |
| } |
| |
| return deque[last--]; |
| } |
| |
| //internal |
| public boolean remove(ManagedConnectionInfo mci) { |
| for (int i = first; i <= last; i++) { |
| if (deque[i] == mci) { |
| for (int j = i + 1; j <= last; j++) { |
| deque[j - 1] = deque[j]; |
| } |
| last--; |
| return true; |
| } |
| |
| } |
| return false; |
| } |
| |
| //internal |
| public int capacity() { |
| return deque.length; |
| } |
| |
| //internal |
| public int currentSize() { |
| return last - first + 1; |
| } |
| } |
| |
| } |