/*
* 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.activemq.artemis.core.protocol.core.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQRemoteDisconnectException;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
import org.apache.activemq.artemis.core.protocol.core.Channel;
import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
import org.apache.activemq.artemis.core.protocol.core.Packet;
import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_ID;
import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerWithKillMessage;
import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage;
import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2;
import org.apache.activemq.artemis.core.security.ActiveMQPrincipal;
import org.apache.activemq.artemis.spi.core.protocol.AbstractRemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.jboss.logging.Logger;
public class RemotingConnectionImpl extends AbstractRemotingConnection implements CoreRemotingConnection {
private static final Logger logger = Logger.getLogger(RemotingConnectionImpl.class);
private final PacketDecoder packetDecoder;
private final Map<Long, Channel> channels = new ConcurrentHashMap<>();
private final long blockingCallTimeout;
private final long blockingCallFailoverTimeout;
private final List<Interceptor> incomingInterceptors;
private final List<Interceptor> outgoingInterceptors;
private volatile boolean destroyed;
private final boolean client;
private int clientVersion;
private volatile SimpleIDGenerator idGenerator = new SimpleIDGenerator(CHANNEL_ID.USER.id);
private boolean idGeneratorSynced = false;
private final Object transferLock = new Object();
private final Object failLock = new Object();
private final SimpleString nodeID;
private String clientID;
// Constructors
// ---------------------------------------------------------------------------------
/*
* Create a client side connection
*/
public RemotingConnectionImpl(final PacketDecoder packetDecoder,
final Connection transportConnection,
final long blockingCallTimeout,
final long blockingCallFailoverTimeout,
final List<Interceptor> incomingInterceptors,
final List<Interceptor> outgoingInterceptors) {
this(packetDecoder, transportConnection, blockingCallTimeout, blockingCallFailoverTimeout, incomingInterceptors, outgoingInterceptors, true, null, null);
}
/*
* Create a server side connection
*/
RemotingConnectionImpl(final PacketDecoder packetDecoder,
final Connection transportConnection,
final List<Interceptor> incomingInterceptors,
final List<Interceptor> outgoingInterceptors,
final Executor executor,
final SimpleString nodeID) {
this(packetDecoder, transportConnection, -1, -1, incomingInterceptors, outgoingInterceptors, false, executor, nodeID);
}
private RemotingConnectionImpl(final PacketDecoder packetDecoder,
final Connection transportConnection,
final long blockingCallTimeout,
final long blockingCallFailoverTimeout,
final List<Interceptor> incomingInterceptors,
final List<Interceptor> outgoingInterceptors,
final boolean client,
final Executor executor,
final SimpleString nodeID) {
super(transportConnection, executor);
this.packetDecoder = packetDecoder;
this.blockingCallTimeout = blockingCallTimeout;
this.blockingCallFailoverTimeout = blockingCallFailoverTimeout;
this.incomingInterceptors = incomingInterceptors;
this.outgoingInterceptors = outgoingInterceptors;
this.client = client;
this.nodeID = nodeID;
transportConnection.setProtocolConnection(this);
}
// RemotingConnection implementation
// ------------------------------------------------------------
@Override
public String toString() {
return "RemotingConnectionImpl [clientID=" + clientID +
", nodeID=" +
nodeID +
", transportConnection=" +
getTransportConnection() +
"]";
}
/**
* @return the clientVersion
*/
@Override
public int getClientVersion() {
return clientVersion;
}
/**
* @param clientVersion the clientVersion to set
*/
@Override
public void setClientVersion(int clientVersion) {
this.clientVersion = clientVersion;
}
@Override
public synchronized Channel getChannel(final long channelID, final int confWindowSize) {
Channel channel = channels.get(channelID);
if (channel == null) {
channel = new ChannelImpl(this, channelID, confWindowSize, outgoingInterceptors);
channels.put(channelID, channel);
}
return channel;
}
@Override
public synchronized boolean removeChannel(final long channelID) {
return channels.remove(channelID) != null;
}
@Override
public synchronized void putChannel(final long channelID, final Channel channel) {
channels.put(channelID, channel);
}
@Override
public void fail(final ActiveMQException me, String scaleDownTargetNodeID) {
synchronized (failLock) {
if (destroyed) {
return;
}
destroyed = true;
}
if (!(me instanceof ActiveMQRemoteDisconnectException)) {
ActiveMQClientLogger.LOGGER.connectionFailureDetected(me.getMessage(), me.getType());
}
try {
transportConnection.forceClose();
} catch (Throwable e) {
ActiveMQClientLogger.LOGGER.warn(e.getMessage(), e);
}
// Then call the listeners
callFailureListeners(me, scaleDownTargetNodeID);
callClosingListeners();
internalClose();
for (Channel channel : channels.values()) {
channel.returnBlocking(me);
}
}
@Override
public void destroy() {
synchronized (failLock) {
if (destroyed) {
return;
}
destroyed = true;
}
internalClose();
callClosingListeners();
}
@Override
public boolean blockUntilWritable(int size, long timeout) {
return transportConnection.blockUntilWritable(size, timeout, TimeUnit.MILLISECONDS);
}
@Override
public void disconnect(final boolean criticalError) {
disconnect(null, criticalError);
}
@Override
public void disconnect(String scaleDownNodeID, final boolean criticalError) {
Channel channel0 = getChannel(ChannelImpl.CHANNEL_ID.PING.id, -1);
// And we remove all channels from the connection, this ensures no more packets will be processed after this
// method is
// complete
Set<Channel> allChannels = new HashSet<>(channels.values());
if (!criticalError) {
removeAllChannels();
} else {
// We can't hold a lock if a critical error is happening...
// as other threads will be holding the lock while hanging on IO
channels.clear();
}
// Now we are 100% sure that no more packets will be processed we can flush then send the disconnect
if (!criticalError) {
for (Channel channel : allChannels) {
channel.flushConfirmations();
}
}
Packet disconnect;
if (channel0.supports(PacketImpl.DISCONNECT_V2)) {
disconnect = new DisconnectMessage_V2(nodeID, scaleDownNodeID);
} else {
disconnect = new DisconnectMessage(nodeID);
}
channel0.sendAndFlush(disconnect);
}
@Override
public long generateChannelID() {
return idGenerator.generateID();
}
@Override
public synchronized void syncIDGeneratorSequence(final long id) {
if (!idGeneratorSynced) {
idGenerator = new SimpleIDGenerator(id);
idGeneratorSynced = true;
}
}
@Override
public long getIDGeneratorSequence() {
return idGenerator.getCurrentID();
}
@Override
public Object getTransferLock() {
return transferLock;
}
@Override
public boolean isClient() {
return client;
}
@Override
public boolean isDestroyed() {
return destroyed;
}
@Override
public long getBlockingCallTimeout() {
return blockingCallTimeout;
}
@Override
public long getBlockingCallFailoverTimeout() {
return blockingCallFailoverTimeout;
}
//We flush any confirmations on the connection - this prevents idle bridges for example
//sitting there with many unacked messages
@Override
public void flush() {
synchronized (transferLock) {
for (Channel channel : channels.values()) {
channel.flushConfirmations();
}
}
}
@Override
public ActiveMQPrincipal getDefaultActiveMQPrincipal() {
return getTransportConnection().getDefaultActiveMQPrincipal();
}
@Override
public boolean isSupportReconnect() {
for (Channel channel : channels.values()) {
if (channel.getConfirmationWindowSize() > 0) {
return true;
}
}
return false;
}
// Buffer Handler implementation
// ----------------------------------------------------
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
try {
final Packet packet = packetDecoder.decode(buffer);
if (logger.isTraceEnabled()) {
logger.trace("handling packet " + packet);
}
dataReceived = true;
doBufferReceived(packet);
super.bufferReceived(connectionID, buffer);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorDecodingPacket(e);
throw new IllegalStateException(e);
}
}
private void doBufferReceived(final Packet packet) {
if (ChannelImpl.invokeInterceptors(packet, incomingInterceptors, this) != null) {
return;
}
synchronized (transferLock) {
final Channel channel = channels.get(packet.getChannelID());
if (channel != null) {
channel.handlePacket(packet);
}
}
}
protected void removeAllChannels() {
// We get the transfer lock first - this ensures no packets are being processed AND
// it's guaranteed no more packets will be processed once this method is complete
synchronized (transferLock) {
channels.clear();
}
}
private void internalClose() {
// We close the underlying transport connection
getTransportConnection().close();
for (Channel channel : channels.values()) {
channel.close();
}
}
public void setClientID(String cID) {
clientID = cID;
}
public String getClientID() {
return clientID;
}
@Override
public void killMessage(SimpleString nodeID) {
if (clientVersion < DisconnectConsumerWithKillMessage.VERSION_INTRODUCED) {
return;
}
Channel clientChannel = getChannel(1, -1);
DisconnectConsumerWithKillMessage response = new DisconnectConsumerWithKillMessage(nodeID);
clientChannel.send(response, -1);
}
}