/* * 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.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import io.netty.channel.ChannelPipeline; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.BaseInterceptor; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener; import org.apache.activemq.artemis.api.core.client.TopologyMember; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.protocol.ServerPacketDecoder; import org.apache.activemq.artemis.core.protocol.core.Channel; import org.apache.activemq.artemis.core.protocol.core.ChannelHandler; 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.ServerSessionPacketHandler; import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_ID; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V2; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V3; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2; import org.apache.activemq.artemis.core.remoting.CloseListener; import org.apache.activemq.artemis.core.remoting.impl.netty.ActiveMQFrameDecoder2; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.jboss.logging.Logger; public class CoreProtocolManager implements ProtocolManager<Interceptor> { private static final Logger logger = Logger.getLogger(CoreProtocolManager.class); private static final List<String> websocketRegistryNames = Collections.EMPTY_LIST; private final ActiveMQServer server; private final List<Interceptor> incomingInterceptors; private final List<Interceptor> outgoingInterceptors; private final CoreProtocolManagerFactory protocolManagerFactory; private final Map<SimpleString, RoutingType> prefixes = new HashMap<>(); public CoreProtocolManager(final CoreProtocolManagerFactory factory, final ActiveMQServer server, final List<Interceptor> incomingInterceptors, List<Interceptor> outgoingInterceptors) { this.protocolManagerFactory = factory; this.server = server; this.incomingInterceptors = incomingInterceptors; this.outgoingInterceptors = outgoingInterceptors; } @Override public ProtocolManagerFactory<Interceptor> getFactory() { return protocolManagerFactory; } @Override public void updateInterceptors(List<BaseInterceptor> incoming, List<BaseInterceptor> outgoing) { this.incomingInterceptors.clear(); this.incomingInterceptors.addAll(getFactory().filterInterceptors(incoming)); this.outgoingInterceptors.clear(); this.outgoingInterceptors.addAll(getFactory().filterInterceptors(outgoing)); } @Override public boolean acceptsNoHandshake() { return false; } @Override public ConnectionEntry createConnectionEntry(final Acceptor acceptorUsed, final Connection connection) { final Configuration config = server.getConfiguration(); Executor connectionExecutor = server.getExecutorFactory().getExecutor(); final CoreRemotingConnection rc = new RemotingConnectionImpl(ServerPacketDecoder.INSTANCE, connection, incomingInterceptors, outgoingInterceptors, config.isAsyncConnectionExecutionEnabled() ? connectionExecutor : null, server.getNodeID()); Channel channel1 = rc.getChannel(CHANNEL_ID.SESSION.id, -1); ChannelHandler handler = new ActiveMQPacketHandler(this, server, channel1, rc); channel1.setHandler(handler); long ttl = ActiveMQClient.DEFAULT_CONNECTION_TTL; if (config.getConnectionTTLOverride() != -1) { ttl = config.getConnectionTTLOverride(); } final ConnectionEntry entry = new ConnectionEntry(rc, connectionExecutor, System.currentTimeMillis(), ttl); final Channel channel0 = rc.getChannel(ChannelImpl.CHANNEL_ID.PING.id, -1); channel0.setHandler(new LocalChannelHandler(config, entry, channel0, acceptorUsed, rc)); server.getClusterManager().addClusterChannelHandler(rc.getChannel(CHANNEL_ID.CLUSTER.id, -1), acceptorUsed, rc, server.getActivation()); return entry; } private final Map<String, ServerSessionPacketHandler> sessionHandlers = new ConcurrentHashMap<>(); ServerSessionPacketHandler getSessionHandler(final String sessionName) { return sessionHandlers.get(sessionName); } void addSessionHandler(final String name, final ServerSessionPacketHandler handler) { sessionHandlers.put(name, handler); } @Override public void removeHandler(final String name) { sessionHandlers.remove(name); } @Override public void handleBuffer(RemotingConnection connection, ActiveMQBuffer buffer) { } @Override public void addChannelHandlers(ChannelPipeline pipeline) { pipeline.addLast("activemq-decoder", new ActiveMQFrameDecoder2()); } @Override public boolean isProtocol(byte[] array) { return isArtemis(ActiveMQBuffers.wrappedBuffer(array)); } @Override public void handshake(NettyServerConnection connection, ActiveMQBuffer buffer) { //if we are not an old client then handshake if (isArtemis(buffer)) { buffer.skipBytes(7); } } @Override public List<String> websocketSubprotocolIdentifiers() { return websocketRegistryNames; } @Override public void setAnycastPrefix(String anycastPrefix) { for (String prefix : anycastPrefix.split(",")) { prefixes.put(SimpleString.toSimpleString(prefix), RoutingType.ANYCAST); } } @Override public void setMulticastPrefix(String multicastPrefix) { for (String prefix : multicastPrefix.split(",")) { prefixes.put(SimpleString.toSimpleString(prefix), RoutingType.MULTICAST); } } @Override public Map<SimpleString, RoutingType> getPrefixes() { return prefixes; } private boolean isArtemis(ActiveMQBuffer buffer) { return buffer.getByte(0) == 'A' && buffer.getByte(1) == 'R' && buffer.getByte(2) == 'T' && buffer.getByte(3) == 'E' && buffer.getByte(4) == 'M' && buffer.getByte(5) == 'I' && buffer.getByte(6) == 'S'; } @Override public String toString() { return "CoreProtocolManager(server=" + server + ")"; } private class LocalChannelHandler implements ChannelHandler { private final Configuration config; private final ConnectionEntry entry; private final Channel channel0; private final Acceptor acceptorUsed; private final CoreRemotingConnection rc; private LocalChannelHandler(final Configuration config, final ConnectionEntry entry, final Channel channel0, final Acceptor acceptorUsed, final CoreRemotingConnection rc) { this.config = config; this.entry = entry; this.channel0 = channel0; this.acceptorUsed = acceptorUsed; this.rc = rc; } @Override public void handlePacket(final Packet packet) { if (packet.getType() == PacketImpl.PING) { Ping ping = (Ping) packet; if (config.getConnectionTTLOverride() == -1) { // Allow clients to specify connection ttl entry.ttl = ping.getConnectionTTL(); } // Just send a ping back channel0.send(packet); } else if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY || packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) { SubscribeClusterTopologyUpdatesMessage msg = (SubscribeClusterTopologyUpdatesMessage) packet; if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) { channel0.getConnection().setClientVersion(((SubscribeClusterTopologyUpdatesMessageV2) msg).getClientVersion()); } final ClusterTopologyListener listener = new ClusterTopologyListener() { @Override public void nodeUP(final TopologyMember topologyMember, final boolean last) { try { final Pair<TransportConfiguration, TransportConfiguration> connectorPair = BackwardsCompatibilityUtils.getTCPair(channel0.getConnection().getClientVersion(), topologyMember); final String nodeID = topologyMember.getNodeId(); // Using an executor as most of the notifications on the Topology // may come from a channel itself // What could cause deadlocks entry.connectionExecutor.execute(new Runnable() { @Override public void run() { if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) { channel0.send(new ClusterTopologyChangeMessage_V3(topologyMember.getUniqueEventID(), nodeID, topologyMember.getBackupGroupName(), topologyMember.getScaleDownGroupName(), connectorPair, last)); } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { channel0.send(new ClusterTopologyChangeMessage_V2(topologyMember.getUniqueEventID(), nodeID, topologyMember.getBackupGroupName(), connectorPair, last)); } else { channel0.send(new ClusterTopologyChangeMessage(nodeID, connectorPair, last)); } } }); } catch (RejectedExecutionException ignored) { // this could happen during a shutdown and we don't care, if we lost a nodeDown during a shutdown // what can we do anyways? } } @Override public void nodeDown(final long uniqueEventID, final String nodeID) { // Using an executor as most of the notifications on the Topology // may come from a channel itself // What could cause deadlocks try { entry.connectionExecutor.execute(new Runnable() { @Override public void run() { if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { channel0.send(new ClusterTopologyChangeMessage_V2(uniqueEventID, nodeID)); } else { channel0.send(new ClusterTopologyChangeMessage(nodeID)); } } }); } catch (RejectedExecutionException ignored) { // this could happen during a shutdown and we don't care, if we lost a nodeDown during a shutdown // what can we do anyways? } } @Override public String toString() { return "Remote Proxy on channel " + Integer.toHexString(System.identityHashCode(this)); } }; if (acceptorUsed.getClusterConnection() != null) { acceptorUsed.getClusterConnection().addClusterTopologyListener(listener); rc.addCloseListener(new CloseListener() { @Override public void connectionClosed() { acceptorUsed.getClusterConnection().removeClusterTopologyListener(listener); } }); } else { // if not clustered, we send a single notification to the client containing the node-id where the server is connected to // This is done this way so Recovery discovery could also use the node-id for non-clustered setups entry.connectionExecutor.execute(new Runnable() { @Override public void run() { String nodeId = server.getNodeID().toString(); Pair<TransportConfiguration, TransportConfiguration> emptyConfig = new Pair<>(null, null); if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) { channel0.send(new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(), nodeId, null, emptyConfig, true)); } else { channel0.send(new ClusterTopologyChangeMessage(nodeId, emptyConfig, true)); } } }); } } } private Pair<TransportConfiguration, TransportConfiguration> getPair(TransportConfiguration conn, boolean isBackup) { if (isBackup) { return new Pair<>(null, conn); } return new Pair<>(conn, null); } } }