/** * Alipay.com Inc. * Copyright (c) 2004-2012 All Rights Reserved. */ package com.alipay.zdal.datasource.resource.connectionmanager; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.security.auth.Subject; import org.apache.log4j.Logger; import com.alipay.zdal.datasource.Service; import com.alipay.zdal.datasource.resource.JBossResourceException; import com.alipay.zdal.datasource.resource.ResourceException; import com.alipay.zdal.datasource.resource.adapter.jdbc.local.LocalTxDataSource; import com.alipay.zdal.datasource.resource.connectionmanager.InternalManagedConnectionPool.PoolParams; import com.alipay.zdal.datasource.resource.spi.ConnectionRequestInfo; import com.alipay.zdal.datasource.resource.spi.ManagedConnectionFactory; import com.alipay.zdal.datasource.tm.TransactionLocal; import com.alipay.zdal.datasource.transaction.Transaction; import com.alipay.zdal.datasource.transaction.TransactionManager; /** * The JBossManagedConnectionPool mbean configures and supplies pooling of * JBossConnectionEventListeners to the BaseConnectionManager2 mbean. * <p> * * It may be replaced by any mbean with a readable ManagedConnectionPool * attribute of type ManagedConnectionPool. Normal pooling parameters are * supplied, and the criteria to distinguish ManagedConnections is set in the * Criteria attribute. * * @author ���� * @version $Id: JBossManagedConnectionPool.java, v 0.1 2014-1-6 ����05:35:12 Exp $ */ public class JBossManagedConnectionPool implements JBossManagedConnectionPoolMBean, Service { /** The log */ private static final Logger log = Logger .getLogger(JBossManagedConnectionPool.class); /** The pooling criteria */ private String criteria = "ByNothing"; /** The pooling strategy */ private ManagedConnectionPool poolingStrategy; /** The pooling parameters */ private final PoolParams poolParams = new InternalManagedConnectionPool.PoolParams(); /** Whether to use separate pools for transactional and non-transaction use */ private boolean noTxSeparatePools; private ManagedConnectionFactory mcf; private String state; private LocalTxDataSource dataSource; private String name; public ManagedConnectionPool getPoolingStrategy() { return poolingStrategy; } public void setName(String name) { this.name = name; } public void setManagedConnectionFactory(ManagedConnectionFactory managedConnectionFactory) { this.mcf = managedConnectionFactory; } public void setDataSource(LocalTxDataSource dataSource) { this.dataSource = dataSource; } // @DOperation public void flush() { if (getPoolingStrategy() == null) throw new IllegalStateException("The connection pool is not started"); getPoolingStrategy().flush(); if (getPoolingStrategy() instanceof PreFillPoolSupport) { final PreFillPoolSupport pfs = (PreFillPoolSupport) getPoolingStrategy(); if (pfs.shouldPreFill()) pfs.prefill(noTxSeparatePools); } } // //@DAttribute public long getAvailableConnectionCount() { return (getPoolingStrategy() == null) ? 0 : getPoolingStrategy() .getAvailableConnectionCount(); } public void setAvailableConnectionCount(long availableConnectionCount) { } //@DAttribute public boolean getBackGroundValidation() { return poolParams.backgroundValidation; } //@DAttribute public long getBackGroundValidationMinutes() { return poolParams.backgroundInterval / (1000 * 60); } //@DAttribute public int getBlockingTimeoutMillis() { return poolParams.blockingTimeout; } //@DAttribute public int getConnectionCount() { return (getPoolingStrategy() == null) ? 0 : getPoolingStrategy().getConnectionCount(); } public void setConnectionCount(int connectionCount) { } //@DAttribute public int getConnectionCreatedCount() { return (getPoolingStrategy() == null) ? 0 : getPoolingStrategy() .getConnectionCreatedCount(); } public void setConnectionCreatedCount(int connectionCreatedCount) { } //@DAttribute public int getConnectionDestroyedCount() { return (getPoolingStrategy() == null) ? 0 : getPoolingStrategy() .getConnectionDestroyedCount(); } public void setConnectionDestroyedCount(int connectionDestroyedCount) { } //@DAttribute public String getCriteria() { return criteria; } //@DAttribute public long getIdleTimeoutMinutes() { return poolParams.idleTimeout / (1000 * 60); } //@DAttribute public long getInUseConnectionCount() { return (getPoolingStrategy() == null) ? 0 : getPoolingStrategy().getInUseConnectionCount(); } public void setInUseConnectionCount(long inUseConnectionCount) { } //@DAttribute public long getMaxConnectionsInUseCount() { return (getPoolingStrategy() == null) ? 0 : getPoolingStrategy() .getMaxConnectionsInUseCount(); } public void setMaxConnectionsInUseCount(int maxConnectionsInUseCount) { } //@DAttribute public int getMaxSize() { return poolParams.maxSize; } //@DAttribute public int getMinSize() { return poolParams.minSize; } //@DAttribute public boolean getNoTxSeparatePools() { return noTxSeparatePools; } //@DAttribute public boolean getPreFill() { return poolParams.prefill; } //@DAttribute public String getState() { return state; } public void setState(String state) { } //@DAttribute public boolean getUseFastFail() { return poolParams.useFastFail; } public void setBackGroundValidation(boolean backgroundValidation) { poolParams.backgroundValidation = backgroundValidation; } public void setBackGroundValidationMinutes(long backgroundValidationInterval) { poolParams.backgroundInterval = backgroundValidationInterval * 1000 * 60; } public void setBlockingTimeoutMillis(int newBlockingTimeout) { poolParams.blockingTimeout = newBlockingTimeout; } public void setCriteria(String newCriteria) { this.criteria = newCriteria; } public void setIdleTimeoutMinutes(long newIdleTimeoutMinutes) { poolParams.idleTimeout = newIdleTimeoutMinutes * 1000 * 60; } public void setMaxSize(int newMaxSize) { poolParams.maxSize = newMaxSize; } public void setMinSize(int newMinSize) { poolParams.minSize = newMinSize; } public void setNoTxSeparatePools(boolean value) { this.noTxSeparatePools = value; } public void setPreFill(boolean prefill) { poolParams.prefill = prefill; } public void setUseFastFail(boolean useFastFail) { poolParams.useFastFail = useFastFail; } // @DOperation public void start() throws Exception { state = STARTING; dataSource.start(); state = STARTED; } // @DOperation public void stop() throws Exception { state = STOPPING; dataSource.stop(); state = STOPPED; } public void startService() throws Exception { if ("ByContainerAndApplication".equals(criteria)) poolingStrategy = new PoolBySubjectAndCri(mcf, name, poolParams, noTxSeparatePools, log); else if ("ByContainer".equals(criteria)) poolingStrategy = new PoolBySubject(mcf, name, poolParams, noTxSeparatePools, log); else if ("ByApplication".equals(criteria)) poolingStrategy = new PoolByCri(mcf, name, poolParams, noTxSeparatePools, log); else // "ByNothing".equals(criteria) poolingStrategy = new OnePool(mcf, name, poolParams, noTxSeparatePools, log); } public void stopService() throws Exception { if (poolingStrategy != null) poolingStrategy.shutdown(); poolingStrategy = null; } public static class SubPoolContext { /** The subpool */ private final InternalManagedConnectionPool subPool; /** The track by transaction transaction local */ private TransactionLocal trackByTx; /** * Create a new SubPoolContext. * * @param tm the transaction manager * @param mcf the managed connection factory * @param clf the connection listener factory * @param subject the subject * @param cri the connection request info * @param poolParams the pool parameters * @param log the log */ public SubPoolContext(TransactionManager tm, ManagedConnectionFactory mcf, ConnectionListenerFactory clf, Subject subject, ConnectionRequestInfo cri, PoolParams poolParams, Logger log, String poolName) { subPool = new InternalManagedConnectionPool(mcf, clf, subject, cri, poolParams, log, poolName); if (tm != null) trackByTx = new TransactionLocal(tm); } /** * Get the sub pool * * @return the sub pool */ public InternalManagedConnectionPool getSubPool() { return subPool; } /** * Get the track by transaction * * @return the transaction local */ public TransactionLocal getTrackByTx() { return trackByTx; } /** * Initialize the subpool context */ public void initialize() { subPool.initialize(); } } /** * The base pool implementation */ public abstract static class BasePool implements ManagedConnectionPool, PreFillPoolSupport { /** The subpools */ private final ConcurrentMap<Object, SubPoolContext> subPools = new ConcurrentHashMap<Object, SubPoolContext>(); /** The managed connection factory */ private final ManagedConnectionFactory mcf; /** The connection listener factory */ private ConnectionListenerFactory clf; /** The pool parameters */ protected final PoolParams poolParams; /** * Whether to use separate pools for transactional and non-transaction * use */ protected boolean noTxSeparatePools; /** The logger */ private final Logger log; /** Is trace enabled */ private boolean traceEnabled = true; /** The poolName */ private final String poolName; public BasePool(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } /** * Create a new base pool * * @param mcf the managed connection factory * @param poolParams the pooling parameters * @param log the log */ public BasePool(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this.mcf = mcf; this.poolParams = poolParams; this.noTxSeparatePools = noTxSeparatePools; this.log = log; this.traceEnabled = log.isDebugEnabled(); this.poolName = poolName; } /** * Retrieve the key for this request * * @param subject the subject * @param cri the connection request information * @return the key * @throws ResourceException for any error */ protected abstract Object getKey(Subject subject, ConnectionRequestInfo cri, boolean separateNoTx) throws ResourceException; public ManagedConnectionFactory getManagedConnectionFactory() { return mcf; } public void setConnectionListenerFactory(ConnectionListenerFactory clf) { this.clf = clf; } public ConnectionListener getConnection(Transaction trackByTransaction, Subject subject, ConnectionRequestInfo cri) throws ResourceException { // Determine the pool key for this request boolean separateNoTx = false; if (noTxSeparatePools) separateNoTx = clf.isTransactional(); Object key = getKey(subject, cri, separateNoTx); SubPoolContext subPool = getSubPool(key, subject, cri, poolName); InternalManagedConnectionPool mcp = subPool.getSubPool(); // Are we doing track by connection? TransactionLocal trackByTx = subPool.getTrackByTx(); // Simple case if (trackByTransaction == null || trackByTx == null) { ConnectionListener cl = mcp.getConnection(subject, cri); if (traceEnabled) dump("Got connection from pool " + cl); return cl; } // Track by transaction try { trackByTx.lock(trackByTransaction); } catch (Throwable t) { JBossResourceException.rethrowAsResourceException( "Unable to get connection from the pool for tx=" + trackByTransaction, t); } try { // Already got one ConnectionListener cl = (ConnectionListener) trackByTx.get(trackByTransaction); if (cl != null) { if (traceEnabled) dump("Previous connection tracked by transaction " + cl + " tx=" + trackByTransaction); return cl; } } finally { trackByTx.unlock(trackByTransaction); } // Need a new one for this transaction // This must be done outside the tx local lock, otherwise // the tx timeout won't work and get connection can do a lot of // other work // with many opportunities for deadlocks. // Instead we do a double check after we got the transaction to see // whether another thread beat us to the punch. ConnectionListener cl = mcp.getConnection(subject, cri); if (traceEnabled) dump("Got connection from pool tracked by transaction " + cl + " tx=" + trackByTransaction); // Relock and check/set status try { trackByTx.lock(trackByTransaction); } catch (Throwable t) { mcp.returnConnection(cl, false); if (traceEnabled) dump("Had to return connection tracked by transaction " + cl + " tx=" + trackByTransaction + " error=" + t.getMessage()); JBossResourceException.rethrowAsResourceException( "Unable to get connection from the pool for tx=" + trackByTransaction, t); } try { // Check we weren't racing with another transaction ConnectionListener other = (ConnectionListener) trackByTx.get(trackByTransaction); if (other != null) { mcp.returnConnection(cl, false); if (traceEnabled) dump("Another thread already got a connection tracked by transaction " + other + " tx=" + trackByTransaction); return other; } // This is the connection for this transaction cl.setTrackByTx(true); // FIXME Ӧ����trackByTx.set(trackByTransaction, cl)�ɣ� trackByTx.set(cl); if (traceEnabled) dump("Using connection from pool tracked by transaction " + cl + " tx=" + trackByTransaction); return cl; } finally { trackByTx.unlock(trackByTransaction); } } public boolean shouldPreFill() { return getPreFill(); } public void prefill() { prefill(null, null, false); } public void prefill(boolean noTxSeperatePool) { prefill(null, null, noTxSeperatePool); } public void prefill(Subject subject, ConnectionRequestInfo cri, boolean noTxSeperatePool) { if (getPreFill()) { if (log.isDebugEnabled()) { log.debug("Attempting to prefill pool for pool with name" + poolName); } try { getSubPool(getKey(subject, cri, noTxSeparatePools), subject, cri, poolName); } catch (Throwable t) { // No real need to throw here being that pool remains in the // same state as before. log.error("Unable to prefill pool with name" + getPoolName(), t); } } } public void returnConnection(ConnectionListener cl, boolean kill) throws ResourceException { cl.setTrackByTx(false); InternalManagedConnectionPool mcp = (InternalManagedConnectionPool) cl.getContext(); mcp.returnConnection(cl, kill); if (traceEnabled) dump("Returning connection to pool " + cl); } /** * Return the inuse count * * @return the count */ public int getInUseConnectionCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionInUseCount(); } } return count; } public int getConnectionCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionCount(); } } return count; } public String getPoolName() { return poolName; } public boolean getPreFill() { return this.poolParams.prefill; } public int getConnectionCreatedCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionCreatedCount(); } } return count; } public int getConnectionDestroyedCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionDestroyedCount(); } } return count; } public long getAvailableConnectionCount() { long count = 0; synchronized (subPools) { if (subPools.size() == 0) return poolParams.maxSize; for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getAvailableConnections(); } } return count; } public int getMaxConnectionsInUseCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getMaxConnectionsInUseCount(); } } return count; } public void shutdown() { synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); subPool.getSubPool().shutdown(); } subPools.clear(); } } public void flush() { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); // FIXME Ӧ����subPool.getSubPool().flush()�ɣ� subPool.getSubPool().shutdown(); } subPools.clear(); } /** * For testing */ protected void shutdownWithoutClear() { synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); subPool.getSubPool().shutdown(); } } } /** * Get any transaction manager associated with the pool * * @return the transaction manager */ protected TransactionManager getTransactionManager() { if (clf != null) return clf.getTransactionManagerInstance(); else return null; } /** * Determine the correct pool for this request, creates a new one when * necessary * * @param key the key to the pool * @param subject the subject of the pool * @param cri the connection request info * @return the subpool context * @throws ResourceException for any error */ protected SubPoolContext getSubPool(Object key, Subject subject, ConnectionRequestInfo cri, String poolName) throws ResourceException { SubPoolContext subPool = subPools.get(key); if (subPool == null) { TransactionManager tm = getTransactionManager(); subPool = new SubPoolContext(tm, mcf, clf, subject, cri, poolParams, log, poolName); synchronized (subPools) { if (subPools.containsKey(key)) subPool = subPools.get(key); else { subPool.initialize(); subPools.put(key, subPool); } } } return subPool; } protected SubPoolContext getSubPool(Object key) { return null; } /** * Dump the stats to the trace log * * @param info some context */ private void dump(String info) { if (log.isDebugEnabled()) { StringBuffer toLog = new StringBuffer(100); toLog.append(info).append(" [InUse/Available/Max]: ["); toLog.append(this.getInUseConnectionCount()).append("/"); toLog.append(this.getAvailableConnectionCount()).append("/"); toLog.append(this.poolParams.maxSize); toLog.append("]"); log.debug(toLog); } } } /** * Pooling by subject and connection request information */ public static class PoolBySubjectAndCri extends BasePool { public PoolBySubjectAndCri(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } public PoolBySubjectAndCri(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { super(mcf, poolName, poolParams, noTxSeparatePools, log); } @Override protected Object getKey(final Subject subject, final ConnectionRequestInfo cri, final boolean separateNoTx) throws ResourceException { return new SubjectCriKey(subject, cri, separateNoTx); } @Override public void prefill() { prefill(null, null, false); } @Override public void prefill(boolean noTxSeperatePool) { prefill(null, null, noTxSeperatePool); } public void prefill(Subject subject, ConnectionRequestInfo cri) { prefill(subject, cri, false); } @Override public void prefill(Subject subject, ConnectionRequestInfo cri, boolean noTxSeperatePool) { if (getPreFill()) { log.warn("Prefill pool option was selected for pool with name " + getPoolName() + " that does not support this feature."); } } } /** * Pool by subject and criteria */ private static class SubjectCriKey { /** Identifies no subject */ private static final Subject NOSUBJECT = new Subject(); /** Identifies no connection request information */ private static final Object NOCRI = new Object(); /** The subject */ private final Subject subject; /** The connection request information */ private final Object cri; /** The cached hashCode */ private int hashCode = Integer.MAX_VALUE; /** Separate no tx */ private final boolean separateNoTx; SubjectCriKey(Subject subject, ConnectionRequestInfo cri, boolean separateNoTx) { this.subject = (subject == null) ? NOSUBJECT : subject; this.cri = (cri == null) ? NOCRI : cri; this.separateNoTx = separateNoTx; } @Override public int hashCode() { if (hashCode == Integer.MAX_VALUE) hashCode = SubjectActions.hashCode(subject) ^ cri.hashCode(); return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || (obj instanceof SubjectCriKey) == false) return false; SubjectCriKey other = (SubjectCriKey) obj; return SubjectActions.equals(subject, other.subject) && cri.equals(other.cri) && separateNoTx == other.separateNoTx; } } /** * Pool by subject */ public static class PoolBySubject extends BasePool { public PoolBySubject(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } public PoolBySubject(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { super(mcf, poolName, poolParams, noTxSeparatePools, log); } @Override protected Object getKey(final Subject subject, final ConnectionRequestInfo cri, boolean separateNoTx) { return new SubjectKey(subject, separateNoTx); } @Override public void prefill() { prefill(null, null, false); } @Override public void prefill(boolean noTxSeperatePool) { prefill(null, null, noTxSeperatePool); } public void prefill(Subject subject, ConnectionRequestInfo cri) { prefill(subject, cri, false); } @Override public void prefill(Subject subject, ConnectionRequestInfo cri, boolean noTxSeperatePool) { if (getPreFill()) { log.warn("Prefill pool option was selected for pool with name " + getPoolName() + " that does not support this feature."); } } } /** * Pool by subject */ private static class SubjectKey { /** Identifies no subject */ private static final Subject NOSUBJECT = new Subject(); /** The subject */ private final Subject subject; /** Separate no tx */ private final boolean separateNoTx; /** The cached hashCode */ private int hashCode = Integer.MAX_VALUE; SubjectKey(Subject subject, boolean separateNoTx) { this.subject = (subject == null) ? NOSUBJECT : subject; this.separateNoTx = separateNoTx; } @Override public int hashCode() { if (hashCode == Integer.MAX_VALUE) hashCode = SubjectActions.hashCode(subject); return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || (obj instanceof SubjectKey) == false) return false; SubjectKey other = (SubjectKey) obj; return SubjectActions.equals(subject, other.subject) && separateNoTx == other.separateNoTx; } } /** * Pool by connection request information */ public static class PoolByCri extends BasePool { public PoolByCri(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } public PoolByCri(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { super(mcf, poolName, poolParams, noTxSeparatePools, log); } @Override protected Object getKey(final Subject subject, final ConnectionRequestInfo cri, boolean separateNoTx) { return new CriKey(cri, separateNoTx); } @Override public void prefill() { prefill(null, null, false); } @Override public void prefill(boolean noTxSeperatePool) { prefill(null, null, noTxSeperatePool); } public void prefill(Subject subject, ConnectionRequestInfo cri) { prefill(subject, cri, false); } @Override public void prefill(Subject subject, ConnectionRequestInfo cri, boolean noTxSeperatePool) { if (getPreFill()) { log.warn("Prefill pool option was selected for pool with name " + getPoolName() + " that does not support this feature."); } } } /** * Pool by subject and criteria */ private static class CriKey { /** Identifies no connection request information */ private static final Object NOCRI = new Object(); /** The connection request information */ private final Object cri; /** Separate no tx */ private final boolean separateNoTx; /** The cached hashCode */ private int hashCode = Integer.MAX_VALUE; CriKey(ConnectionRequestInfo cri, boolean separateNoTx) { this.cri = (cri == null) ? NOCRI : cri; this.separateNoTx = separateNoTx; } @Override public int hashCode() { if (hashCode == Integer.MAX_VALUE) hashCode = cri.hashCode(); return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || (obj instanceof CriKey) == false) return false; CriKey other = (CriKey) obj; return cri.equals(other.cri) && separateNoTx == other.separateNoTx; } } /** * One pool */ public static class OnePool extends BasePool { public OnePool(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } public OnePool(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { super(mcf, poolName, poolParams, noTxSeparatePools, log); } @Override protected Object getKey(final Subject subject, final ConnectionRequestInfo cri, boolean separateNoTx) { if (separateNoTx) return Boolean.TRUE; else return Boolean.FALSE; } } private static class SubjectActions implements PrivilegedAction { Subject subject; Subject other; SubjectActions(Subject subject, Subject other) { this.subject = subject; this.other = other; } public Object run() { Object value = null; if (other == null) value = new Integer(subject.hashCode()); else value = new Boolean(subject.equals(other)); return value; } static int hashCode(Subject subject) { SubjectActions action = new SubjectActions(subject, null); Integer hash = (Integer) AccessController.doPrivileged(action); return hash.intValue(); } static boolean equals(Subject subject, Subject other) { SubjectActions action = new SubjectActions(subject, other); Boolean equals = (Boolean) AccessController.doPrivileged(action); return equals.booleanValue(); } } }