/** * Processes pin requests. * * <p>A pin request goes through several steps to pin a file on a pool: * * <p>- Create DB entry in state PINNING - Optionally read the name space entry - Select a read pool * (which may involve staging) - Update DB entry with the pool name - Create sticky flag on pool - * Update DB entry to state PINNED * * <p>If during any step the entry is no longer in PINNING then the operation is aborted. * * <p>If a DB error occurs it is considered fatal and the pinning operation is not completed. The DB * entry will stay in PINNING until either explicitly unpinned or it expires. * * <p>Database operations are blocking. Communication with PoolManager and pools is asynchronous. */ public class PinRequestProcessor implements CellMessageReceiver { private static final Logger _log = LoggerFactory.getLogger(PinRequestProcessor.class); /** The delay we use after a pin request failed and before retrying the request. */ private static final long RETRY_DELAY = SECONDS.toMillis(30); /** * The delay we use after transient failures that should be retried immediately. The small delay * prevents tight retry loops. */ private static final long SMALL_DELAY = MILLISECONDS.toMillis(10); /** Safety margin added to the lifetime of the sticky bit to account for clock drift. */ private static final long CLOCK_DRIFT_MARGIN = MINUTES.toMillis(30); private static final Set<FileAttribute> REQUIRED_ATTRIBUTES = PoolMgrSelectReadPoolMsg.getRequiredAttributes(); private ScheduledExecutorService _executor; private PinDao _dao; private CellStub _poolStub; private CellStub _pnfsStub; private CellStub _poolManagerStub; private CheckStagePermission _checkStagePermission; private long _maxLifetime; private TimeUnit _maxLifetimeUnit; private PoolMonitor _poolMonitor; @Required public void setExecutor(ScheduledExecutorService executor) { _executor = executor; } @Required public void setDao(PinDao dao) { _dao = dao; } @Required public void setPoolStub(CellStub stub) { _poolStub = stub; } @Required public void setPnfsStub(CellStub stub) { _pnfsStub = stub; } @Required public void setPoolManagerStub(CellStub stub) { _poolManagerStub = stub; } @Required public void setStagePermission(CheckStagePermission checker) { _checkStagePermission = checker; } @Required public void setMaxLifetime(long maxLifetime) { _maxLifetime = maxLifetime; } @Required public void setPoolMonitor(PoolMonitor poolMonitor) { _poolMonitor = poolMonitor; } public long getMaxLifetime() { return _maxLifetime; } @Required public void setMaxLifetimeUnit(TimeUnit unit) { _maxLifetimeUnit = unit; } public TimeUnit getMaxLifetimeUnit() { return _maxLifetimeUnit; } private void enforceLifetimeLimit(PinManagerPinMessage message) { if (_maxLifetime > -1) { long millis = _maxLifetimeUnit.toMillis(_maxLifetime); long requestedLifetime = message.getLifetime(); if (requestedLifetime == -1) { message.setLifetime(millis); } else { message.setLifetime(Math.min(millis, requestedLifetime)); } } } public MessageReply<PinManagerPinMessage> messageArrived(PinManagerPinMessage message) throws CacheException { MessageReply<PinManagerPinMessage> reply = new MessageReply<>(); enforceLifetimeLimit(message); PinTask task = createTask(message, reply); if (task != null) { if (!task.getFileAttributes().isDefined(REQUIRED_ATTRIBUTES)) { rereadNameSpaceEntry(task); } else { selectReadPool(task); } } return reply; } protected EnumSet<RequestContainerV5.RequestState> checkStaging(PinTask task) { try { Subject subject = task.getSubject(); StorageInfo info = task.getFileAttributes().getStorageInfo(); return _checkStagePermission.canPerformStaging(subject, info) ? RequestContainerV5.allStates : RequestContainerV5.allStatesExceptStage; } catch (PatternSyntaxException | IOException ex) { _log.error("Failed to check stage permission: " + ex); } return RequestContainerV5.allStatesExceptStage; } private void retry(final PinTask task, long delay) { if (!task.isValidIn(delay)) { fail(task, CacheException.TIMEOUT, "Pin request TTL exceeded"); } else { _executor.schedule( new Runnable() { @Override public void run() { try { rereadNameSpaceEntry(task); } catch (CacheException e) { fail(task, e.getRc(), e.getMessage()); } catch (RuntimeException e) { fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString()); } } }, delay, MILLISECONDS); } } private void fail(PinTask task, int rc, String error) { try { task.fail(rc, error); clearPin(task); } catch (RuntimeException e) { _log.error(e.toString()); } } private void rereadNameSpaceEntry(final PinTask task) throws CacheException { /* Ensure that task is still valid and stays valid for the * duration of the name space lookup. */ refreshTimeout(task, getExpirationTimeForNameSpaceLookup()); /* We allow the set of provided attributes to be incomplete * and thus add attributes required by pool manager. */ Set<FileAttribute> attributes = EnumSet.noneOf(FileAttribute.class); attributes.addAll(task.getFileAttributes().getDefinedAttributes()); attributes.addAll(PoolMgrSelectReadPoolMsg.getRequiredAttributes()); _pnfsStub.send( new PnfsGetFileAttributes(task.getPnfsId(), attributes), PnfsGetFileAttributes.class, new AbstractMessageCallback<PnfsGetFileAttributes>() { @Override public void success(PnfsGetFileAttributes msg) { try { task.setFileAttributes(msg.getFileAttributes()); /* Ensure that task is still valid * and stays valid for the duration * of the pool selection. */ refreshTimeout(task, getExpirationTimeForPoolSelection()); selectReadPool(task); } catch (CacheException e) { fail(task, e.getRc(), e.getMessage()); } catch (RuntimeException e) { fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString()); } } @Override public void failure(int rc, Object error) { fail(task, rc, error.toString()); } @Override public void noroute(CellPath path) { /* PnfsManager is unreachable. We * expect this to be a transient * problem and retry in a moment. */ retry(task, RETRY_DELAY); } @Override public void timeout(CellPath path) { /* PnfsManager did not respond. We * expect this to be a transient * problem and retry in a moment. */ retry(task, SMALL_DELAY); } }); } private void selectReadPool(final PinTask task) throws CacheException { try { PoolSelector poolSelector = _poolMonitor.getPoolSelector(task.getFileAttributes(), task.getProtocolInfo(), null); PoolInfo pool = poolSelector.selectPinPool(); setPool(task, pool.getName()); setStickyFlag(task, pool.getName(), pool.getAddress()); } catch (FileNotOnlineCacheException e) { askPoolManager(task); } } private void askPoolManager(final PinTask task) { PoolMgrSelectReadPoolMsg msg = new PoolMgrSelectReadPoolMsg( task.getFileAttributes(), task.getProtocolInfo(), task.getReadPoolSelectionContext(), checkStaging(task)); msg.setSubject(task.getSubject()); msg.setSkipCostUpdate(true); _poolManagerStub.send( msg, PoolMgrSelectReadPoolMsg.class, new AbstractMessageCallback<PoolMgrSelectReadPoolMsg>() { @Override public void success(PoolMgrSelectReadPoolMsg msg) { try { /* Pool manager expects us * to keep some state * between retries. */ task.setReadPoolSelectionContext(msg.getContext()); /* Store the pool name in * the DB so we know what to * clean up if something * fails. */ String poolName = msg.getPoolName(); CellAddressCore poolAddress = msg.getPoolAddress(); task.getFileAttributes().getLocations().add(poolName); setPool(task, poolName); setStickyFlag(task, poolName, poolAddress); } catch (CacheException e) { fail(task, e.getRc(), e.getMessage()); } catch (RuntimeException e) { fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString()); } } @Override public void failure(int rc, Object error) { /* Pool manager expects us to * keep some state between * retries. */ task.setReadPoolSelectionContext(getReply().getContext()); switch (rc) { case CacheException.OUT_OF_DATE: /* Pool manager asked for a * refresh of the request. * Retry right away. */ retry(task, 0); break; case CacheException.FILE_NOT_IN_REPOSITORY: case CacheException.PERMISSION_DENIED: fail(task, rc, error.toString()); break; default: /* Ideally we would delegate the retry to the door, * but for the time being the retry is dealed with * by pin manager. */ retry(task, RETRY_DELAY); break; } } @Override public void noroute(CellPath path) { /* Pool manager is * unreachable. We expect this * to be transient and retry in * a moment. */ retry(task, RETRY_DELAY); } @Override public void timeout(CellPath path) { /* Pool manager did not * respond. We expect this to be * transient and retry in a * moment. */ retry(task, SMALL_DELAY); } }); } private void setStickyFlag( final PinTask task, final String poolName, CellAddressCore poolAddress) { /* The pin lifetime should be from the moment the file is * actually pinned. Due to staging and pool to pool transfers * this may be much later than when the pin was requested. */ Date pinExpiration = task.freezeExpirationTime(); /* To allow for some drift in clocks we add a safety margin to * the lifetime of the sticky bit. */ long poolExpiration = (pinExpiration == null) ? -1 : pinExpiration.getTime() + CLOCK_DRIFT_MARGIN; PoolSetStickyMessage msg = new PoolSetStickyMessage( poolName, task.getPnfsId(), true, task.getSticky(), poolExpiration); _poolStub.send( new CellPath(poolAddress), msg, PoolSetStickyMessage.class, new AbstractMessageCallback<PoolSetStickyMessage>() { @Override public void success(PoolSetStickyMessage msg) { try { setToPinned(task); task.success(); } catch (CacheException e) { fail(task, e.getRc(), e.getMessage()); } catch (RuntimeException e) { fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString()); } } @Override public void failure(int rc, Object error) { switch (rc) { case CacheException.POOL_DISABLED: /* Pool manager had outdated * information about the pool. Give * it a chance to be updated and * then retry. */ retry(task, RETRY_DELAY); break; case CacheException.FILE_NOT_IN_REPOSITORY: /* Pnfs manager had stale location * information. The pool clears * this information as a result of * this error, so we retry in a * moment. */ retry(task, SMALL_DELAY); break; default: fail(task, rc, error.toString()); break; } } @Override public void noroute(CellPath path) { /* The pool must have gone down. Give * pool manager a moment to notice this * and then retry. */ retry(task, RETRY_DELAY); } @Override public void timeout(CellPath path) { /* No response from pool. Typically this is * because the pool is overloaded. */ fail(task, CacheException.TIMEOUT, "No reply from " + path); } }); } private Date getExpirationTimeForNameSpaceLookup() { long now = System.currentTimeMillis(); long timeout = _pnfsStub.getTimeoutInMillis(); return new Date(now + 2 * (timeout + RETRY_DELAY)); } private Date getExpirationTimeForPoolSelection() { long now = System.currentTimeMillis(); long timeout = _poolManagerStub.getTimeoutInMillis(); return new Date(now + 2 * (timeout + RETRY_DELAY)); } private Date getExpirationTimeForSettingFlag() { long now = System.currentTimeMillis(); long timeout = _poolStub.getTimeoutInMillis(); return new Date(now + 2 * timeout); } @Transactional protected PinTask createTask( PinManagerPinMessage message, MessageReply<PinManagerPinMessage> reply) { PnfsId pnfsId = message.getFileAttributes().getPnfsId(); if (message.getRequestId() != null) { Pin pin = _dao.getPin(pnfsId, message.getRequestId()); if (pin != null) { /* In this case the request is a resubmission. If the * previous pin completed then use it. Otherwise abort the * previous pin and create a new one. */ if (pin.getState() == PINNED) { message.setPin(pin); reply.reply(message); return null; } pin.setState(UNPINNING); pin.setRequestId(null); _dao.storePin(pin); } } Pin pin = new Pin(message.getSubject(), pnfsId); pin.setRequestId(message.getRequestId()); pin.setSticky("PinManager-" + UUID.randomUUID().toString()); pin.setExpirationTime(getExpirationTimeForPoolSelection()); return new PinTask(message, reply, _dao.storePin(pin)); } /** * Load the pin belonging to the PinTask. * * @throw CacheException if the pin no longer exists or is no longer in PINNING. */ protected Pin loadPinBelongingTo(PinTask task) throws CacheException { Pin pin = _dao.getPin(task.getPinId(), task.getSticky(), PINNING); if (pin == null) { throw new CacheException("Operation was aborted"); } return pin; } @Transactional(isolation = REPEATABLE_READ) protected void refreshTimeout(PinTask task, Date date) throws CacheException { Pin pin = loadPinBelongingTo(task); pin.setExpirationTime(date); task.setPin(_dao.storePin(pin)); } @Transactional(isolation = REPEATABLE_READ) protected void setPool(PinTask task, String pool) throws CacheException { Pin pin = loadPinBelongingTo(task); pin.setExpirationTime(getExpirationTimeForSettingFlag()); pin.setPool(pool); task.setPin(_dao.storePin(pin)); } @Transactional(isolation = REPEATABLE_READ) protected void setToPinned(PinTask task) throws CacheException { Pin pin = loadPinBelongingTo(task); pin.setExpirationTime(task.getExpirationTime()); pin.setState(PINNED); task.setPin(_dao.storePin(pin)); } @Transactional protected void clearPin(PinTask task) { if (task.getPool() != null) { /* If the pin record expired or the pin was explicitly * unpinned, then the unpin processor may already have * submitted a request to the pool to clear the sticky * flag. Although out of order delivery of messages is * unlikely, if it would happen then we have a race * between the set sticky and clear sticky messages. To * cover this case we delete the old record and create a * fresh one in UNPINNING. */ _dao.deletePin(task.getPin()); Pin pin = new Pin(task.getSubject(), task.getPnfsId()); pin.setState(UNPINNING); _dao.storePin(pin); } else { /* We didn't create a sticky flag yet, so there is no * reason to keep the record. It will expire by itself, * but we delete the record now to avoid that we get * tickets from admins wondering why they have records * staying in PINNING. */ _dao.deletePin(task.getPin()); } } }
/** Convenience "factory" class to facilitate opening a {@link Connection} to an AMQP broker. */ public class ConnectionFactory implements Cloneable { /** Default user name */ public static final String DEFAULT_USER = "******"; /** Default password */ public static final String DEFAULT_PASS = "******"; /** Default virtual host */ public static final String DEFAULT_VHOST = "/"; /** Default maximum channel number; zero for unlimited */ public static final int DEFAULT_CHANNEL_MAX = 0; /** Default maximum frame size; zero means no limit */ public static final int DEFAULT_FRAME_MAX = 0; /** Default heart-beat interval; 60 seconds */ public static final int DEFAULT_HEARTBEAT = 60; /** The default host */ public static final String DEFAULT_HOST = "localhost"; /** 'Use the default port' port */ public static final int USE_DEFAULT_PORT = -1; /** The default non-ssl port */ public static final int DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT; /** The default ssl port */ public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671; /** The default TCP connection timeout: 60 seconds */ public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; /** * The default AMQP 0-9-1 connection handshake timeout. See DEFAULT_CONNECTION_TIMEOUT for TCP * (socket) connection timeout. */ public static final int DEFAULT_HANDSHAKE_TIMEOUT = 10000; /** The default shutdown timeout; zero means wait indefinitely */ public static final int DEFAULT_SHUTDOWN_TIMEOUT = 10000; /** The default continuation timeout for RPC calls in channels: 10 minutes */ public static final int DEFAULT_CHANNEL_RPC_TIMEOUT = (int) MINUTES.toMillis(10); private static final String PREFERRED_TLS_PROTOCOL = "TLSv1.2"; private static final String FALLBACK_TLS_PROTOCOL = "TLSv1"; private String username = DEFAULT_USER; private String password = DEFAULT_PASS; private String virtualHost = DEFAULT_VHOST; private String host = DEFAULT_HOST; private int port = USE_DEFAULT_PORT; private int requestedChannelMax = DEFAULT_CHANNEL_MAX; private int requestedFrameMax = DEFAULT_FRAME_MAX; private int requestedHeartbeat = DEFAULT_HEARTBEAT; private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; private int handshakeTimeout = DEFAULT_HANDSHAKE_TIMEOUT; private int shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT; private Map<String, Object> _clientProperties = AMQConnection.defaultClientProperties(); private SocketFactory factory = SocketFactory.getDefault(); private SaslConfig saslConfig = DefaultSaslConfig.PLAIN; private ExecutorService sharedExecutor; private ThreadFactory threadFactory = Executors.defaultThreadFactory(); // minimises the number of threads rapid closure of many // connections uses, see rabbitmq/rabbitmq-java-client#86 private ExecutorService shutdownExecutor; private ScheduledExecutorService heartbeatExecutor; private SocketConfigurator socketConf = new DefaultSocketConfigurator(); private ExceptionHandler exceptionHandler = new DefaultExceptionHandler(); private boolean automaticRecovery = true; private boolean topologyRecovery = true; // long is used to make sure the users can use both ints // and longs safely. It is unlikely that anybody'd need // to use recovery intervals > Integer.MAX_VALUE in practice. private long networkRecoveryInterval = 5000; private MetricsCollector metricsCollector; private boolean nio = false; private FrameHandlerFactory frameHandlerFactory; private NioParams nioParams = new NioParams(); private SSLContext sslContext; /** * Continuation timeout on RPC calls. * * @since 4.1.0 */ private int channelRpcTimeout = DEFAULT_CHANNEL_RPC_TIMEOUT; /** @return the default host to use for connections */ public String getHost() { return host; } /** @param host the default host to use for connections */ public void setHost(String host) { this.host = host; } public static int portOrDefault(int port, boolean ssl) { if (port != USE_DEFAULT_PORT) return port; else if (ssl) return DEFAULT_AMQP_OVER_SSL_PORT; else return DEFAULT_AMQP_PORT; } /** @return the default port to use for connections */ public int getPort() { return portOrDefault(port, isSSL()); } /** * Set the target port. * * @param port the default port to use for connections */ public void setPort(int port) { this.port = port; } /** * Retrieve the user name. * * @return the AMQP user name to use when connecting to the broker */ public String getUsername() { return this.username; } /** * Set the user name. * * @param username the AMQP user name to use when connecting to the broker */ public void setUsername(String username) { this.username = username; } /** * Retrieve the password. * * @return the password to use when connecting to the broker */ public String getPassword() { return this.password; } /** * Set the password. * * @param password the password to use when connecting to the broker */ public void setPassword(String password) { this.password = password; } /** * Retrieve the virtual host. * * @return the virtual host to use when connecting to the broker */ public String getVirtualHost() { return this.virtualHost; } /** * Set the virtual host. * * @param virtualHost the virtual host to use when connecting to the broker */ public void setVirtualHost(String virtualHost) { this.virtualHost = virtualHost; } /** * Convenience method for setting the fields in an AMQP URI: host, port, username, password and * virtual host. If any part of the URI is ommited, the ConnectionFactory's corresponding variable * is left unchanged. * * @param uri is the AMQP URI containing the data */ public void setUri(URI uri) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { if ("amqp".equals(uri.getScheme().toLowerCase())) { // nothing special to do } else if ("amqps".equals(uri.getScheme().toLowerCase())) { setPort(DEFAULT_AMQP_OVER_SSL_PORT); useSslProtocol(); } else { throw new IllegalArgumentException("Wrong scheme in AMQP URI: " + uri.getScheme()); } String host = uri.getHost(); if (host != null) { setHost(host); } int port = uri.getPort(); if (port != -1) { setPort(port); } String userInfo = uri.getRawUserInfo(); if (userInfo != null) { String userPass[] = userInfo.split(":"); if (userPass.length > 2) { throw new IllegalArgumentException("Bad user info in AMQP " + "URI: " + userInfo); } setUsername(uriDecode(userPass[0])); if (userPass.length == 2) { setPassword(uriDecode(userPass[1])); } } String path = uri.getRawPath(); if (path != null && path.length() > 0) { if (path.indexOf('/', 1) != -1) { throw new IllegalArgumentException("Multiple segments in " + "path of AMQP URI: " + path); } setVirtualHost(uriDecode(uri.getPath().substring(1))); } } /** * Convenience method for setting the fields in an AMQP URI: host, port, username, password and * virtual host. If any part of the URI is ommited, the ConnectionFactory's corresponding variable * is left unchanged. Note that not all valid AMQP URIs are accepted; in particular, the hostname * must be given if the port, username or password are given, and escapes in the hostname are not * permitted. * * @param uriString is the AMQP URI containing the data */ public void setUri(String uriString) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { setUri(new URI(uriString)); } private String uriDecode(String s) { try { // URLDecode decodes '+' to a space, as for // form encoding. So protect plus signs. return URLDecoder.decode(s.replace("+", "%2B"), "US-ASCII"); } catch (IOException e) { throw new RuntimeException(e); } } /** * Retrieve the requested maximum channel number * * @return the initially requested maximum channel number; zero for unlimited */ public int getRequestedChannelMax() { return this.requestedChannelMax; } /** * Set the requested maximum channel number * * @param requestedChannelMax initially requested maximum channel number; zero for unlimited */ public void setRequestedChannelMax(int requestedChannelMax) { this.requestedChannelMax = requestedChannelMax; } /** * Retrieve the requested maximum frame size * * @return the initially requested maximum frame size, in octets; zero for unlimited */ public int getRequestedFrameMax() { return this.requestedFrameMax; } /** * Set the requested maximum frame size * * @param requestedFrameMax initially requested maximum frame size, in octets; zero for unlimited */ public void setRequestedFrameMax(int requestedFrameMax) { this.requestedFrameMax = requestedFrameMax; } /** * Retrieve the requested heartbeat interval. * * @return the initially requested heartbeat interval, in seconds; zero for none */ public int getRequestedHeartbeat() { return this.requestedHeartbeat; } /** * Set the TCP connection timeout. * * @param timeout connection TCP establishment timeout in milliseconds; zero for infinite */ public void setConnectionTimeout(int timeout) { if (timeout < 0) { throw new IllegalArgumentException("TCP connection timeout cannot be negative"); } this.connectionTimeout = timeout; } /** * Retrieve the TCP connection timeout. * * @return the TCP connection timeout, in milliseconds; zero for infinite */ public int getConnectionTimeout() { return this.connectionTimeout; } /** * Retrieve the AMQP 0-9-1 protocol handshake timeout. * * @return the AMQP0-9-1 protocol handshake timeout, in milliseconds */ public int getHandshakeTimeout() { return handshakeTimeout; } /** * Set the AMQP0-9-1 protocol handshake timeout. * * @param timeout the AMQP0-9-1 protocol handshake timeout, in milliseconds */ public void setHandshakeTimeout(int timeout) { if (timeout < 0) { throw new IllegalArgumentException("handshake timeout cannot be negative"); } this.handshakeTimeout = timeout; } /** * Set the shutdown timeout. This is the amount of time that Consumer implementations have to * continue working through deliveries (and other Consumer callbacks) <b>after</b> the connection * has closed but before the ConsumerWorkService is torn down. If consumers exceed this timeout * then any remaining queued deliveries (and other Consumer callbacks, <b>including</b> the * Consumer's handleShutdownSignal() invocation) will be lost. * * @param shutdownTimeout shutdown timeout in milliseconds; zero for infinite; default 10000 */ public void setShutdownTimeout(int shutdownTimeout) { this.shutdownTimeout = shutdownTimeout; } /** * Retrieve the shutdown timeout. * * @return the shutdown timeout, in milliseconds; zero for infinite */ public int getShutdownTimeout() { return shutdownTimeout; } /** * Set the requested heartbeat timeout. Heartbeat frames will be sent at about 1/2 the timeout * interval. If server heartbeat timeout is configured to a non-zero value, this method can only * be used to lower the value; otherwise any value provided by the client will be used. * * @param requestedHeartbeat the initially requested heartbeat timeout, in seconds; zero for none * @see <a href="http://rabbitmq.com/heartbeats.html">RabbitMQ Heartbeats Guide</a> */ public void setRequestedHeartbeat(int requestedHeartbeat) { this.requestedHeartbeat = requestedHeartbeat; } /** * Retrieve the currently-configured table of client properties that will be sent to the server * during connection startup. Clients may add, delete, and alter keys in this table. Such changes * will take effect when the next new connection is started using this factory. * * @return the map of client properties * @see #setClientProperties */ public Map<String, Object> getClientProperties() { return _clientProperties; } /** * Replace the table of client properties that will be sent to the server during subsequent * connection startups. * * @param clientProperties the map of extra client properties * @see #getClientProperties */ public void setClientProperties(Map<String, Object> clientProperties) { _clientProperties = clientProperties; } /** * Gets the sasl config to use when authenticating * * @return the sasl config * @see com.rabbitmq.client.SaslConfig */ public SaslConfig getSaslConfig() { return saslConfig; } /** * Sets the sasl config to use when authenticating * * @param saslConfig * @see com.rabbitmq.client.SaslConfig */ public void setSaslConfig(SaslConfig saslConfig) { this.saslConfig = saslConfig; } /** Retrieve the socket factory used to make connections with. */ public SocketFactory getSocketFactory() { return this.factory; } /** * Set the socket factory used to make connections with. Can be used to enable SSL connections by * passing in a javax.net.ssl.SSLSocketFactory instance. * * @see #useSslProtocol */ public void setSocketFactory(SocketFactory factory) { this.factory = factory; } /** * Get the socket configurator. * * @see #setSocketConfigurator(SocketConfigurator) */ @SuppressWarnings("unused") public SocketConfigurator getSocketConfigurator() { return socketConf; } /** * Set the socket configurator. This gets a chance to "configure" a socket before it has been * opened. The default socket configurator disables Nagle's algorithm. * * @param socketConfigurator the configurator to use */ public void setSocketConfigurator(SocketConfigurator socketConfigurator) { this.socketConf = socketConfigurator; } /** * Set the executor to use for consumer operation dispatch by default for newly created * connections. All connections that use this executor share it. * * <p>It's developer's responsibility to shut down the executor when it is no longer needed. * * @param executor executor service to be used for consumer operation */ public void setSharedExecutor(ExecutorService executor) { this.sharedExecutor = executor; } /** * Set the executor to use for connection shutdown. All connections that use this executor share * it. * * <p>It's developer's responsibility to shut down the executor when it is no longer needed. * * @param executor executor service to be used for connection shutdown */ public void setShutdownExecutor(ExecutorService executor) { this.shutdownExecutor = executor; } /** * Set the executor to use to send heartbeat frames. All connections that use this executor share * it. * * <p>It's developer's responsibility to shut down the executor when it is no longer needed. * * @param executor executor service to be used to send heartbeat */ public void setHeartbeatExecutor(ScheduledExecutorService executor) { this.heartbeatExecutor = executor; } /** * Retrieve the thread factory used to instantiate new threads. * * @see ThreadFactory */ public ThreadFactory getThreadFactory() { return threadFactory; } /** * Set the thread factory used to instantiate new threads. * * @see ThreadFactory */ public void setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; } /** * Get the exception handler. * * @see com.rabbitmq.client.ExceptionHandler */ public ExceptionHandler getExceptionHandler() { return exceptionHandler; } /** * Set the exception handler to use for newly created connections. * * @see com.rabbitmq.client.ExceptionHandler */ public void setExceptionHandler(ExceptionHandler exceptionHandler) { if (exceptionHandler == null) { throw new IllegalArgumentException("exception handler cannot be null!"); } this.exceptionHandler = exceptionHandler; } public boolean isSSL() { return getSocketFactory() instanceof SSLSocketFactory; } /** * Convenience method for setting up a SSL socket factory/engine, using the DEFAULT_SSL_PROTOCOL * and a trusting TrustManager. */ public void useSslProtocol() throws NoSuchAlgorithmException, KeyManagementException { useSslProtocol( computeDefaultTlsProcotol( SSLContext.getDefault().getSupportedSSLParameters().getProtocols())); } /** * Convenience method for setting up a SSL socket factory/engine, using the supplied protocol and * a very trusting TrustManager. */ public void useSslProtocol(String protocol) throws NoSuchAlgorithmException, KeyManagementException { useSslProtocol(protocol, new NullTrustManager()); } /** * Convenience method for setting up an SSL socket factory/engine. Pass in the SSL protocol to * use, e.g. "TLSv1" or "TLSv1.2". * * @param protocol SSL protocol to use. */ public void useSslProtocol(String protocol, TrustManager trustManager) throws NoSuchAlgorithmException, KeyManagementException { SSLContext c = SSLContext.getInstance(protocol); c.init(null, new TrustManager[] {trustManager}, null); useSslProtocol(c); } /** * Convenience method for setting up an SSL socket factory/engine. Pass in an initialized * SSLContext. * * @param context An initialized SSLContext */ public void useSslProtocol(SSLContext context) { setSocketFactory(context.getSocketFactory()); this.sslContext = context; } public static String computeDefaultTlsProcotol(String[] supportedProtocols) { if (supportedProtocols != null) { for (String supportedProtocol : supportedProtocols) { if (PREFERRED_TLS_PROTOCOL.equalsIgnoreCase(supportedProtocol)) { return supportedProtocol; } } } return FALLBACK_TLS_PROTOCOL; } /** * Returns true if <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, false otherwise * * @return true if automatic connection recovery is enabled, false otherwise * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public boolean isAutomaticRecoveryEnabled() { return automaticRecovery; } /** * Enables or disables <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic * connection recovery</a>. * * @param automaticRecovery if true, enables connection recovery * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public void setAutomaticRecoveryEnabled(boolean automaticRecovery) { this.automaticRecovery = automaticRecovery; } /** * Returns true if topology recovery is enabled, false otherwise * * @return true if topology recovery is enabled, false otherwise * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ @SuppressWarnings("unused") public boolean isTopologyRecoveryEnabled() { return topologyRecovery; } /** * Enables or disables topology recovery * * @param topologyRecovery if true, enables topology recovery * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public void setTopologyRecoveryEnabled(boolean topologyRecovery) { this.topologyRecovery = topologyRecovery; } public void setMetricsCollector(MetricsCollector metricsCollector) { this.metricsCollector = metricsCollector; } public MetricsCollector getMetricsCollector() { return metricsCollector; } protected synchronized FrameHandlerFactory createFrameHandlerFactory() throws IOException { if (nio) { if (this.frameHandlerFactory == null) { if (this.nioParams.getNioExecutor() == null && this.nioParams.getThreadFactory() == null) { this.nioParams.setThreadFactory(getThreadFactory()); } this.frameHandlerFactory = new SocketChannelFrameHandlerFactory(connectionTimeout, nioParams, isSSL(), sslContext); } return this.frameHandlerFactory; } else { return new SocketFrameHandlerFactory( connectionTimeout, factory, socketConf, isSSL(), this.shutdownExecutor); } } /** * Create a new broker connection, picking the first available address from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param addrs an array of known broker addresses (hostname/port pairs) to try in order * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(Address[] addrs) throws IOException, TimeoutException { return newConnection(this.sharedExecutor, Arrays.asList(addrs), null); } /** * Create a new broker connection, picking the first available address from the list provided by * the {@link AddressResolver}. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address provided by the {@link * AddressResolver}. * * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to * connect to * @return an interface to the connection * @throws IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection(AddressResolver addressResolver) throws IOException, TimeoutException { return newConnection(this.sharedExecutor, addressResolver, null); } /** * Create a new broker connection with a client-provided name, picking the first available address * from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param addrs an array of known broker addresses (hostname/port pairs) to try in order * @param clientProvidedName application-specific connection name, will be displayed in the * management UI if RabbitMQ server supports it. This value doesn't have to be unique and * cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed * to be human-readable. * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(Address[] addrs, String clientProvidedName) throws IOException, TimeoutException { return newConnection(this.sharedExecutor, Arrays.asList(addrs), clientProvidedName); } /** * Create a new broker connection, picking the first available address from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param addrs a List of known broker addresses (hostname/port pairs) to try in order * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(List<Address> addrs) throws IOException, TimeoutException { return newConnection(this.sharedExecutor, addrs, null); } /** * Create a new broker connection with a client-provided name, picking the first available address * from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param addrs a List of known broker addresses (hostname/port pairs) to try in order * @param clientProvidedName application-specific connection name, will be displayed in the * management UI if RabbitMQ server supports it. This value doesn't have to be unique and * cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed * to be human-readable. * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(List<Address> addrs, String clientProvidedName) throws IOException, TimeoutException { return newConnection(this.sharedExecutor, addrs, clientProvidedName); } /** * Create a new broker connection, picking the first available address from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param executor thread execution service for consumers on the connection * @param addrs an array of known broker addresses (hostname/port pairs) to try in order * @return an interface to the connection * @throws java.io.IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection(ExecutorService executor, Address[] addrs) throws IOException, TimeoutException { return newConnection(executor, Arrays.asList(addrs), null); } /** * Create a new broker connection with a client-provided name, picking the first available address * from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param executor thread execution service for consumers on the connection * @param addrs an array of known broker addresses (hostname/port pairs) to try in order * @param clientProvidedName application-specific connection name, will be displayed in the * management UI if RabbitMQ server supports it. This value doesn't have to be unique and * cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed * to be human-readable. * @return an interface to the connection * @throws java.io.IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection( ExecutorService executor, Address[] addrs, String clientProvidedName) throws IOException, TimeoutException { return newConnection(executor, Arrays.asList(addrs), clientProvidedName); } /** * Create a new broker connection, picking the first available address from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param executor thread execution service for consumers on the connection * @param addrs a List of known broker addrs (hostname/port pairs) to try in order * @return an interface to the connection * @throws java.io.IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection(ExecutorService executor, List<Address> addrs) throws IOException, TimeoutException { return newConnection(executor, addrs, null); } /** * Create a new broker connection, picking the first available address from the list provided by * the {@link AddressResolver}. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address provided by the {@link * AddressResolver}. * * @param executor thread execution service for consumers on the connection * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to * connect to * @return an interface to the connection * @throws java.io.IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection(ExecutorService executor, AddressResolver addressResolver) throws IOException, TimeoutException { return newConnection(executor, addressResolver, null); } /** * Create a new broker connection with a client-provided name, picking the first available address * from the list. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address from the provided list. * * @param executor thread execution service for consumers on the connection * @param addrs a List of known broker addrs (hostname/port pairs) to try in order * @param clientProvidedName application-specific connection name, will be displayed in the * management UI if RabbitMQ server supports it. This value doesn't have to be unique and * cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed * to be human-readable. * @return an interface to the connection * @throws java.io.IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection( ExecutorService executor, List<Address> addrs, String clientProvidedName) throws IOException, TimeoutException { return newConnection(executor, createAddressResolver(addrs), clientProvidedName); } /** * Create a new broker connection with a client-provided name, picking the first available address * from the list provided by the {@link AddressResolver}. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Future reconnection attempts will pick a random accessible address provided by the {@link * AddressResolver}. * * @param executor thread execution service for consumers on the connection * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to * connect to * @param clientProvidedName application-specific connection name, will be displayed in the * management UI if RabbitMQ server supports it. This value doesn't have to be unique and * cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed * to be human-readable. * @return an interface to the connection * @throws java.io.IOException if it encounters a problem * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a> */ public Connection newConnection( ExecutorService executor, AddressResolver addressResolver, String clientProvidedName) throws IOException, TimeoutException { if (this.metricsCollector == null) { this.metricsCollector = new NoOpMetricsCollector(); } // make sure we respect the provided thread factory FrameHandlerFactory fhFactory = createFrameHandlerFactory(); ConnectionParams params = params(executor); // set client-provided via a client property if (clientProvidedName != null) { Map<String, Object> properties = new HashMap<String, Object>(params.getClientProperties()); properties.put("connection_name", clientProvidedName); params.setClientProperties(properties); } if (isAutomaticRecoveryEnabled()) { // see com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory#newConnection AutorecoveringConnection conn = new AutorecoveringConnection(params, fhFactory, addressResolver, metricsCollector); conn.init(); return conn; } else { List<Address> addrs = addressResolver.getAddresses(); IOException lastException = null; for (Address addr : addrs) { try { FrameHandler handler = fhFactory.create(addr); AMQConnection conn = new AMQConnection(params, handler, metricsCollector); conn.start(); this.metricsCollector.newConnection(conn); return conn; } catch (IOException e) { lastException = e; } } throw (lastException != null) ? lastException : new IOException("failed to connect"); } } public ConnectionParams params(ExecutorService consumerWorkServiceExecutor) { ConnectionParams result = new ConnectionParams(); result.setUsername(username); result.setPassword(password); result.setConsumerWorkServiceExecutor(consumerWorkServiceExecutor); result.setVirtualHost(virtualHost); result.setClientProperties(getClientProperties()); result.setRequestedFrameMax(requestedFrameMax); result.setRequestedChannelMax(requestedChannelMax); result.setShutdownTimeout(shutdownTimeout); result.setSaslConfig(saslConfig); result.setNetworkRecoveryInterval(networkRecoveryInterval); result.setTopologyRecovery(topologyRecovery); result.setExceptionHandler(exceptionHandler); result.setThreadFactory(threadFactory); result.setHandshakeTimeout(handshakeTimeout); result.setRequestedHeartbeat(requestedHeartbeat); result.setShutdownExecutor(shutdownExecutor); result.setHeartbeatExecutor(heartbeatExecutor); result.setChannelRpcTimeout(channelRpcTimeout); return result; } /** * Create a new broker connection. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Reconnection attempts will always use the address configured on {@link ConnectionFactory}. * * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection() throws IOException, TimeoutException { return newConnection( this.sharedExecutor, Collections.singletonList(new Address(getHost(), getPort()))); } /** * Create a new broker connection. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Reconnection attempts will always use the address configured on {@link ConnectionFactory}. * * @param connectionName arbitrary sring for connection name client property * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(String connectionName) throws IOException, TimeoutException { return newConnection( this.sharedExecutor, Collections.singletonList(new Address(getHost(), getPort())), connectionName); } /** * Create a new broker connection. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Reconnection attempts will always use the address configured on {@link ConnectionFactory}. * * @param executor thread execution service for consumers on the connection * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(ExecutorService executor) throws IOException, TimeoutException { return newConnection(executor, Collections.singletonList(new Address(getHost(), getPort()))); } /** * Create a new broker connection. * * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}. * Reconnection attempts will always use the address configured on {@link ConnectionFactory}. * * @param executor thread execution service for consumers on the connection * @param connectionName arbitrary sring for connection name client property * @return an interface to the connection * @throws IOException if it encounters a problem */ public Connection newConnection(ExecutorService executor, String connectionName) throws IOException, TimeoutException { return newConnection( executor, Collections.singletonList(new Address(getHost(), getPort())), connectionName); } protected AddressResolver createAddressResolver(List<Address> addresses) { if (addresses.size() == 1) { return new DnsRecordIpAddressResolver(addresses.get(0), isSSL()); } else { return new ListAddressResolver(addresses); } } @Override public ConnectionFactory clone() { try { return (ConnectionFactory) super.clone(); } catch (CloneNotSupportedException e) { throw new Error(e); } } /** * Returns automatic connection recovery interval in milliseconds. * * @return how long will automatic recovery wait before attempting to reconnect, in ms; default is * 5000 */ public long getNetworkRecoveryInterval() { return networkRecoveryInterval; } /** * Sets connection recovery interval. Default is 5000. * * @param networkRecoveryInterval how long will automatic recovery wait before attempting to * reconnect, in ms */ public void setNetworkRecoveryInterval(int networkRecoveryInterval) { this.networkRecoveryInterval = networkRecoveryInterval; } /** * Sets connection recovery interval. Default is 5000. * * @param networkRecoveryInterval how long will automatic recovery wait before attempting to * reconnect, in ms */ public void setNetworkRecoveryInterval(long networkRecoveryInterval) { this.networkRecoveryInterval = networkRecoveryInterval; } /** * Sets the parameters when using NIO. * * @param nioParams * @see NioParams */ public void setNioParams(NioParams nioParams) { this.nioParams = nioParams; } /** * Use non-blocking IO (NIO) for communication with the server. With NIO, several connections * created from the same {@link ConnectionFactory} can use the same IO thread. * * <p>A client process using a lot of not-so-active connections can benefit from NIO, as it would * use fewer threads than with the traditional, blocking IO mode. * * <p>Use {@link NioParams} to tune NIO and a {@link SocketChannelConfigurator} to configure the * underlying {@link java.nio.channels.SocketChannel}s for connections. * * @see NioParams * @see SocketChannelConfigurator * @see java.nio.channels.SocketChannel * @see java.nio.channels.Selector */ public void useNio() { this.nio = true; } /** * Use blocking IO for communication with the server. With blocking IO, each connection creates * its own thread to read data from the server. */ public void useBlockingIo() { this.nio = false; } /** * Set the continuation timeout for RPC calls in channels. Default is 10 minutes. 0 means no * timeout. * * @param channelRpcTimeout */ public void setChannelRpcTimeout(int channelRpcTimeout) { if (channelRpcTimeout < 0) { throw new IllegalArgumentException("Timeout cannot be less than 0"); } this.channelRpcTimeout = channelRpcTimeout; } /** * Get the timeout for RPC calls in channels. * * @return */ public int getChannelRpcTimeout() { return channelRpcTimeout; } }