/* * 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.openwire; import javax.jms.IllegalStateException; import javax.jms.InvalidClientIDException; import javax.jms.InvalidDestinationException; import javax.jms.JMSSecurityException; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.activemq.advisory.AdvisorySupport; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException; import org.apache.activemq.artemis.api.core.ActiveMQRemoteDisconnectException; import org.apache.activemq.artemis.api.core.ActiveMQSecurityException; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; import org.apache.activemq.artemis.core.io.IOCallback; import org.apache.activemq.artemis.core.persistence.OperationContext; import org.apache.activemq.artemis.core.postoffice.Binding; import org.apache.activemq.artemis.core.postoffice.Bindings; import org.apache.activemq.artemis.core.postoffice.QueueBinding; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQCompositeConsumerBrokerExchange; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQConnectionContext; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQConsumer; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQConsumerBrokerExchange; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQProducerBrokerExchange; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQSession; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQSingleConsumerBrokerExchange; import org.apache.activemq.artemis.core.protocol.openwire.util.OpenWireUtil; import org.apache.activemq.artemis.core.remoting.FailureListener; import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.SecurityAuth; import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.BindingQueryResult; import org.apache.activemq.artemis.core.server.MessageReference; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.ServerConsumer; import org.apache.activemq.artemis.core.server.ServerSession; import org.apache.activemq.artemis.core.server.SlowConsumerDetectionListener; import org.apache.activemq.artemis.core.server.TempQueueObserver; import org.apache.activemq.artemis.core.server.impl.RefsOperation; import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.TransactionOperationAbstract; import org.apache.activemq.artemis.core.transaction.TransactionPropertyIndexes; import org.apache.activemq.artemis.spi.core.protocol.AbstractRemotingConnection; import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.utils.UUIDGenerator; import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQMessage; import org.apache.activemq.command.ActiveMQTempQueue; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.command.BrokerInfo; import org.apache.activemq.command.BrokerSubscriptionInfo; import org.apache.activemq.command.Command; import org.apache.activemq.command.ConnectionControl; import org.apache.activemq.command.ConnectionError; import org.apache.activemq.command.ConnectionId; import org.apache.activemq.command.ConnectionInfo; import org.apache.activemq.command.ConsumerControl; import org.apache.activemq.command.ConsumerId; import org.apache.activemq.command.ConsumerInfo; import org.apache.activemq.command.ControlCommand; import org.apache.activemq.command.DataArrayResponse; import org.apache.activemq.command.DestinationInfo; import org.apache.activemq.command.ExceptionResponse; import org.apache.activemq.command.FlushCommand; import org.apache.activemq.command.IntegerResponse; import org.apache.activemq.command.KeepAliveInfo; import org.apache.activemq.command.Message; import org.apache.activemq.command.MessageAck; import org.apache.activemq.command.MessageDispatch; import org.apache.activemq.command.MessageDispatchNotification; import org.apache.activemq.command.MessagePull; import org.apache.activemq.command.ProducerAck; import org.apache.activemq.command.ProducerId; import org.apache.activemq.command.ProducerInfo; import org.apache.activemq.command.RemoveSubscriptionInfo; import org.apache.activemq.command.Response; import org.apache.activemq.command.SessionId; import org.apache.activemq.command.SessionInfo; import org.apache.activemq.command.ShutdownInfo; import org.apache.activemq.command.TransactionId; import org.apache.activemq.command.TransactionInfo; import org.apache.activemq.command.WireFormatInfo; import org.apache.activemq.command.XATransactionId; import org.apache.activemq.openwire.OpenWireFormat; import org.apache.activemq.state.CommandVisitor; import org.apache.activemq.state.ConnectionState; import org.apache.activemq.state.ConsumerState; import org.apache.activemq.state.ProducerState; import org.apache.activemq.state.SessionState; import org.apache.activemq.transport.TransmitCallback; import org.apache.activemq.util.ByteSequence; import org.apache.activemq.wireformat.WireFormat; import org.jboss.logging.Logger; /** * Represents an activemq connection. */ public class OpenWireConnection extends AbstractRemotingConnection implements SecurityAuth, TempQueueObserver { private static final Logger logger = Logger.getLogger(OpenWireConnection.class); private static final KeepAliveInfo PING = new KeepAliveInfo(); private final OpenWireProtocolManager protocolManager; private boolean destroyed = false; private final Object sendLock = new Object(); private final OpenWireFormat wireFormat; private AMQConnectionContext context; private final AtomicBoolean stopping = new AtomicBoolean(false); private final Map<String, SessionId> sessionIdMap = new ConcurrentHashMap<>(); private final Map<ConsumerId, AMQConsumerBrokerExchange> consumerExchanges = new ConcurrentHashMap<>(); private final Map<ProducerId, AMQProducerBrokerExchange> producerExchanges = new ConcurrentHashMap<>(); private final Map<SessionId, AMQSession> sessions = new ConcurrentHashMap<>(); private ConnectionState state; private volatile boolean noLocal; /** * Openwire doesn't sen transactions associated with any sessions. * It will however send beingTX / endTX as it would be doing it with XA Transactions. * But always without any association with Sessions. * This collection will hold nonXA transactions. Hopefully while they are in transit only. */ private final Map<TransactionId, Transaction> txMap = new ConcurrentHashMap<>(); private volatile AMQSession advisorySession; private final ActiveMQServer server; /** * This is to be used with connection operations that don't have a session. * Such as TM operations. */ private ServerSession internalSession; private final OperationContext operationContext; private volatile long lastSent = -1; private ConnectionEntry connectionEntry; private boolean useKeepAlive; private long maxInactivityDuration; private final Set<SimpleString> knownDestinations = new ConcurrentHashSet<>(); private AtomicBoolean disableTtl = new AtomicBoolean(false); public OpenWireConnection(Connection connection, ActiveMQServer server, Executor executor, OpenWireProtocolManager openWireProtocolManager, OpenWireFormat wf) { super(connection, executor); this.server = server; this.operationContext = server.newOperationContext(); this.protocolManager = openWireProtocolManager; this.wireFormat = wf; this.useKeepAlive = openWireProtocolManager.isUseKeepAlive(); this.maxInactivityDuration = openWireProtocolManager.getMaxInactivityDuration(); } // SecurityAuth implementation @Override public String getUsername() { ConnectionInfo info = getConnectionInfo(); if (info == null) { return null; } return info.getUserName(); } public OperationContext getOperationContext() { return operationContext; } // SecurityAuth implementation @Override public RemotingConnection getRemotingConnection() { return this; } // SecurityAuth implementation @Override public String getPassword() { ConnectionInfo info = getConnectionInfo(); if (info == null) { return null; } return info.getPassword(); } private ConnectionInfo getConnectionInfo() { if (state == null) { return null; } ConnectionInfo info = state.getInfo(); if (info == null) { return null; } return info; } //tells the connection that //some bytes just sent public void bufferSent() { lastSent = System.currentTimeMillis(); } @Override public void bufferReceived(Object connectionID, ActiveMQBuffer buffer) { super.bufferReceived(connectionID, buffer); try { recoverOperationContext(); Command command = (Command) wireFormat.unmarshal(buffer); boolean responseRequired = command.isResponseRequired(); int commandId = command.getCommandId(); // ignore pings if (command.getClass() != KeepAliveInfo.class) { Response response = null; try { setLastCommand(command); response = command.visit(commandProcessorInstance); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn("Errors occurred during the buffering operation ", e); if (responseRequired) { response = convertException(e); } } finally { setLastCommand(null); } if (response instanceof ExceptionResponse) { if (!responseRequired) { Throwable cause = ((ExceptionResponse) response).getException(); serviceException(cause); response = null; } } if (responseRequired) { if (response == null) { response = new Response(); response.setCorrelationId(commandId); } } // The context may have been flagged so that the response is not // sent. if (context != null) { if (context.isDontSendReponse()) { context.setDontSendReponse(false); response = null; } } sendAsyncResponse(commandId, response); } } catch (Exception e) { ActiveMQServerLogger.LOGGER.debug(e); sendException(e); } finally { clearupOperationContext(); } } /** It will send the response through the operation context, as soon as everything is confirmed on disk */ private void sendAsyncResponse(final int commandId, final Response response) throws Exception { if (response != null) { operationContext.executeOnCompletion(new IOCallback() { @Override public void done() { if (!protocolManager.isStopping()) { try { response.setCorrelationId(commandId); dispatchSync(response); } catch (Exception e) { sendException(e); } } } @Override public void onError(int errorCode, String errorMessage) { sendException(new IOException(errorCode + "-" + errorMessage)); } }); } } public void sendException(Exception e) { Response resp = convertException(e); if (context != null) { Command command = context.getLastCommand(); if (command != null) { resp.setCorrelationId(command.getCommandId()); } } try { dispatch(resp); } catch (IOException e2) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e2); } } private Response convertException(Exception e) { Response resp; if (e instanceof ActiveMQSecurityException) { resp = new ExceptionResponse(new JMSSecurityException(e.getMessage())); } else if (e instanceof ActiveMQNonExistentQueueException) { resp = new ExceptionResponse(new InvalidDestinationException(e.getMessage())); } else { resp = new ExceptionResponse(e); } return resp; } private void setLastCommand(Command command) { if (context != null) { context.setLastCommand(command); } } @Override public void destroy() { fail(null, null); } @Override public boolean isClient() { return false; } @Override public boolean isDestroyed() { return destroyed; } @Override public void disconnect(boolean criticalError) { this.disconnect(null, null, criticalError); } @Override public void flush() { checkInactivity(); } private void checkInactivity() { if (!this.useKeepAlive) { return; } long dur = System.currentTimeMillis() - lastSent; if (dur >= this.maxInactivityDuration / 2) { this.sendCommand(PING); } } private void callFailureListeners(final ActiveMQException me) { final List<FailureListener> listenersClone = new ArrayList<>(failureListeners); for (final FailureListener listener : listenersClone) { try { listener.connectionFailed(me, false); } catch (final Throwable t) { // Failure of one listener to execute shouldn't prevent others // from // executing ActiveMQServerLogger.LOGGER.errorCallingFailureListener(t); } } } // send a WireFormatInfo to the peer public void sendHandshake() { WireFormatInfo info = wireFormat.getPreferedWireFormatInfo(); sendCommand(info); } public ConnectionState getState() { return state; } public void physicalSend(Command command) throws IOException { try { ByteSequence bytes = wireFormat.marshal(command); ActiveMQBuffer buffer = OpenWireUtil.toActiveMQBuffer(bytes); synchronized (sendLock) { getTransportConnection().write(buffer, false, false); } bufferSent(); } catch (IOException e) { throw e; } catch (Throwable t) { ActiveMQServerLogger.LOGGER.error("error sending", t); } } public void dispatchAsync(Command message) throws Exception { dispatchSync(message); } public void dispatchSync(Command message) throws Exception { processDispatch(message); } public void serviceException(Throwable e) throws Exception { ConnectionError ce = new ConnectionError(); ce.setException(e); dispatchAsync(ce); } public void dispatch(Command command) throws IOException { this.physicalSend(command); } protected void processDispatch(Command command) throws IOException { MessageDispatch messageDispatch = (MessageDispatch) (command.isMessageDispatch() ? command : null); try { if (!stopping.get()) { if (messageDispatch != null) { protocolManager.preProcessDispatch(messageDispatch); } dispatch(command); } } catch (IOException e) { if (messageDispatch != null) { TransmitCallback sub = messageDispatch.getTransmitCallback(); protocolManager.postProcessDispatch(messageDispatch); if (sub != null) { sub.onFailure(); } messageDispatch = null; throw e; } } finally { if (messageDispatch != null) { TransmitCallback sub = messageDispatch.getTransmitCallback(); protocolManager.postProcessDispatch(messageDispatch); if (sub != null) { sub.onSuccess(); } } } } private void addConsumerBrokerExchange(ConsumerId id, AMQSession amqSession, List<AMQConsumer> consumerList) { AMQConsumerBrokerExchange result = consumerExchanges.get(id); if (result == null) { if (consumerList.size() == 1) { result = new AMQSingleConsumerBrokerExchange(amqSession, consumerList.get(0)); } else { result = new AMQCompositeConsumerBrokerExchange(amqSession, consumerList); } synchronized (consumerExchanges) { consumerExchanges.put(id, result); } } } private AMQProducerBrokerExchange getProducerBrokerExchange(ProducerId id) throws IOException { AMQProducerBrokerExchange result = producerExchanges.get(id); if (result == null) { synchronized (producerExchanges) { result = new AMQProducerBrokerExchange(); result.setConnectionContext(context); //todo implement reconnect https://issues.apache.org/jira/browse/ARTEMIS-194 //todo: this used to check for && this.acceptorUsed.isAuditNetworkProducers() if (context.isReconnect() || (context.isNetworkConnection())) { // once implemented ARTEMIS-194, we need to set the storedSequenceID here somehow // We have different semantics on Artemis Journal, but we could adapt something for this // TBD during the implementation of ARTEMIS-194 result.setLastStoredSequenceId(0); } SessionState ss = state.getSessionState(id.getParentId()); if (ss != null) { result.setProducerState(ss.getProducerState(id)); ProducerState producerState = ss.getProducerState(id); if (producerState != null && producerState.getInfo() != null) { ProducerInfo info = producerState.getInfo(); } } producerExchanges.put(id, result); } } return result; } public void deliverMessage(MessageDispatch dispatch) { Message m = dispatch.getMessage(); if (m != null) { long endTime = System.currentTimeMillis(); m.setBrokerOutTime(endTime); } sendCommand(dispatch); } public WireFormat getMarshaller() { return this.wireFormat; } private void shutdown(boolean fail) { if (fail) { transportConnection.forceClose(); } else { transportConnection.close(); } } private void disconnect(ActiveMQException me, String reason, boolean fail) { if (context == null || destroyed) { return; } // Don't allow things to be added to the connection state while we // are shutting down. // is it necessary? even, do we need state at all? state.shutdown(); // Then call the listeners // this should closes underlying sessions callFailureListeners(me); // this should clean up temp dests synchronized (sendLock) { callClosingListeners(); } destroyed = true; //before closing transport, sendCommand the last response if any Command command = context.getLastCommand(); if (command != null && command.isResponseRequired()) { Response lastResponse = new Response(); lastResponse.setCorrelationId(command.getCommandId()); try { dispatchSync(lastResponse); } catch (Throwable e) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e); } } } @Override public void disconnect(String reason, boolean fail) { this.disconnect(null, reason, fail); } @Override public void fail(ActiveMQException me, String message) { if (me != null) { //filter it like the other protocols if (!(me instanceof ActiveMQRemoteDisconnectException)) { ActiveMQClientLogger.LOGGER.connectionFailureDetected(me.getMessage(), me.getType()); } } try { protocolManager.removeConnection(this.getConnectionInfo(), me); } catch (InvalidClientIDException e) { ActiveMQServerLogger.LOGGER.warn("Couldn't close connection because invalid clientID", e); } shutdown(true); } public void setAdvisorySession(AMQSession amqSession) { this.advisorySession = amqSession; } public AMQSession getAdvisorySession() { return this.advisorySession; } public AMQConnectionContext getContext() { return this.context; } public void updateClient(ConnectionControl control) throws Exception { if (protocolManager.isUpdateClusterClients()) { dispatchAsync(control); } } public AMQConnectionContext initContext(ConnectionInfo info) throws Exception { WireFormatInfo wireFormatInfo = wireFormat.getPreferedWireFormatInfo(); // Older clients should have been defaulting this field to true.. but // they were not. if (wireFormatInfo != null && wireFormatInfo.getVersion() <= 2) { info.setClientMaster(true); } state = new ConnectionState(info); context = new AMQConnectionContext(); state.reset(info); // Setup the context. String clientId = info.getClientId(); context.setBroker(protocolManager); context.setClientId(clientId); context.setClientMaster(info.isClientMaster()); context.setConnection(this); context.setConnectionId(info.getConnectionId()); // for now we pass the manager as the connector and see what happens // it should be related to activemq's Acceptor context.setFaultTolerant(info.isFaultTolerant()); context.setUserName(info.getUserName()); context.setWireFormatInfo(wireFormatInfo); context.setReconnect(info.isFailoverReconnect()); context.setConnectionState(state); if (info.getClientIp() == null) { info.setClientIp(getRemoteAddress()); } createInternalSession(info); return context; } private void createInternalSession(ConnectionInfo info) throws Exception { internalSession = server.createSession(UUIDGenerator.getInstance().generateStringUUID(), context.getUserName(), info.getPassword(), -1, this, true, false, false, false, null, null, true, operationContext, protocolManager.getPrefixes()); } //raise the refCount of context public void reconnect(AMQConnectionContext existingContext, ConnectionInfo info) { this.context = existingContext; WireFormatInfo wireFormatInfo = wireFormat.getPreferedWireFormatInfo(); // Older clients should have been defaulting this field to true.. but // they were not. if (wireFormatInfo != null && wireFormatInfo.getVersion() <= 2) { info.setClientMaster(true); } if (info.getClientIp() == null) { info.setClientIp(getRemoteAddress()); } state = new ConnectionState(info); state.reset(info); context.setConnection(this); context.setConnectionState(state); context.setClientMaster(info.isClientMaster()); context.setFaultTolerant(info.isFaultTolerant()); context.setReconnect(true); context.incRefCount(); } /** * This will answer with commands to the client */ public boolean sendCommand(final Command command) { if (ActiveMQServerLogger.LOGGER.isTraceEnabled()) { ActiveMQServerLogger.LOGGER.trace("sending " + command); } if (isDestroyed()) { return false; } try { physicalSend(command); } catch (Exception e) { return false; } catch (Throwable t) { return false; } return true; } public void addDestination(DestinationInfo info) throws Exception { ActiveMQDestination dest = info.getDestination(); if (dest.isQueue()) { SimpleString qName = new SimpleString(dest.getPhysicalName()); QueueBinding binding = (QueueBinding) server.getPostOffice().getBinding(qName); if (binding == null) { if (dest.isTemporary()) { internalSession.createQueue(qName, qName, RoutingType.ANYCAST, null, dest.isTemporary(), false); } else { ConnectionInfo connInfo = getState().getInfo(); CheckType checkType = dest.isTemporary() ? CheckType.CREATE_NON_DURABLE_QUEUE : CheckType.CREATE_DURABLE_QUEUE; server.getSecurityStore().check(qName, checkType, this); server.checkQueueCreationLimit(getUsername()); server.createQueue(qName, RoutingType.ANYCAST, qName, connInfo == null ? null : SimpleString.toSimpleString(connInfo.getUserName()), null,true, false); } } } if (dest.isTemporary()) { //Openwire needs to store the DestinationInfo in order to send //Advisory messages to clients this.state.addTempDestination(info); } if (!AdvisorySupport.isAdvisoryTopic(dest)) { AMQConnectionContext context = getContext(); DestinationInfo advInfo = new DestinationInfo(context.getConnectionId(), DestinationInfo.ADD_OPERATION_TYPE, dest); ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(dest); protocolManager.fireAdvisory(context, topic, advInfo); } } public void updateConsumer(ConsumerControl consumerControl) { ConsumerId consumerId = consumerControl.getConsumerId(); AMQConsumerBrokerExchange exchange = this.consumerExchanges.get(consumerId); if (exchange != null) { exchange.updateConsumerPrefetchSize(consumerControl.getPrefetch()); } } public void addConsumer(ConsumerInfo info) throws Exception { // Todo: add a destination interceptors holder here (amq supports this) SessionId sessionId = info.getConsumerId().getParentId(); ConnectionId connectionId = sessionId.getParentId(); ConnectionState cs = getState(); if (cs == null) { throw new IllegalStateException("Cannot add a consumer to a connection that had not been registered: " + connectionId); } SessionState ss = cs.getSessionState(sessionId); if (ss == null) { throw new IllegalStateException(server + " Cannot add a consumer to a session that had not been registered: " + sessionId); } // Avoid replaying dup commands if (!ss.getConsumerIds().contains(info.getConsumerId())) { AMQSession amqSession = sessions.get(sessionId); if (amqSession == null) { throw new IllegalStateException("Session not exist! : " + sessionId); } List<AMQConsumer> consumersList = amqSession.createConsumer(info, new SlowConsumerDetection()); this.addConsumerBrokerExchange(info.getConsumerId(), amqSession, consumersList); ss.addConsumer(info); amqSession.start(); if (AdvisorySupport.isAdvisoryTopic(info.getDestination())) { //advisory for temp destinations if (AdvisorySupport.isTempDestinationAdvisoryTopic(info.getDestination())) { // Replay the temporary destinations. List<DestinationInfo> tmpDests = this.protocolManager.getTemporaryDestinations(); for (DestinationInfo di : tmpDests) { ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(di.getDestination()); String originalConnectionId = di.getConnectionId().getValue(); protocolManager.fireAdvisory(context, topic, di, info.getConsumerId(), originalConnectionId); } } } } } public void setConnectionEntry(ConnectionEntry connectionEntry) { this.connectionEntry = connectionEntry; } @Override public boolean checkDataReceived() { if (disableTtl.get()) { return true; } return super.checkDataReceived(); } public void setUpTtl(final long inactivityDuration, final long inactivityDurationInitialDelay, final boolean useKeepAlive) { this.useKeepAlive = useKeepAlive; this.maxInactivityDuration = inactivityDuration; protocolManager.getScheduledPool().schedule(new Runnable() { @Override public void run() { if (inactivityDuration >= 0) { connectionEntry.ttl = inactivityDuration; } } }, inactivityDurationInitialDelay, TimeUnit.MILLISECONDS); checkInactivity(); } public void addKnownDestination(final SimpleString address) { knownDestinations.add(address); } public boolean containsKnownDestination(final SimpleString address) { return knownDestinations.contains(address); } @Override public void tempQueueDeleted(SimpleString bindingName) { ActiveMQDestination dest = new ActiveMQTempQueue(bindingName.toString()); state.removeTempDestination(dest); if (!AdvisorySupport.isAdvisoryTopic(dest)) { AMQConnectionContext context = getContext(); DestinationInfo advInfo = new DestinationInfo(context.getConnectionId(), DestinationInfo.REMOVE_OPERATION_TYPE, dest); ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(dest); try { protocolManager.fireAdvisory(context, topic, advInfo); } catch (Exception e) { logger.warn("Failed to fire advisory on " + topic, e); } } } public void disableTtl() { disableTtl.set(true); } public void enableTtl() { disableTtl.set(false); } public boolean isNoLocal() { return noLocal; } public void setNoLocal(boolean noLocal) { this.noLocal = noLocal; } public List<DestinationInfo> getTemporaryDestinations() { return state.getTempDestinations(); } class SlowConsumerDetection implements SlowConsumerDetectionListener { @Override public void onSlowConsumer(ServerConsumer consumer) { if (consumer.getProtocolData() != null && consumer.getProtocolData() instanceof AMQConsumer) { AMQConsumer amqConsumer = (AMQConsumer) consumer.getProtocolData(); ActiveMQTopic topic = AdvisorySupport.getSlowConsumerAdvisoryTopic(amqConsumer.getOpenwireDestination()); ActiveMQMessage advisoryMessage = new ActiveMQMessage(); try { advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_CONSUMER_ID, amqConsumer.getId().toString()); protocolManager.fireAdvisory(context, topic, advisoryMessage, amqConsumer.getId(), null); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn("Error during method invocation", e); } } } } public void addSessions(Set<SessionId> sessionSet) { for (SessionId sid : sessionSet) { addSession(getState().getSessionState(sid).getInfo(), true); } } public AMQSession addSession(SessionInfo ss) { return addSession(ss, false); } public AMQSession addSession(SessionInfo ss, boolean internal) { AMQSession amqSession = new AMQSession(getState().getInfo(), ss, server, this, protocolManager); amqSession.initialize(); if (internal) { amqSession.disableSecurity(); } sessions.put(ss.getSessionId(), amqSession); sessionIdMap.put(amqSession.getCoreSession().getName(), ss.getSessionId()); return amqSession; } public void removeSession(AMQConnectionContext context, SessionInfo info) throws Exception { AMQSession session = sessions.remove(info.getSessionId()); if (session != null) { session.close(); } } public AMQSession getSession(SessionId sessionId) { return sessions.get(sessionId); } public void removeDestination(ActiveMQDestination dest) throws Exception { if (dest.isQueue()) { try { server.destroyQueue(new SimpleString(dest.getPhysicalName())); } catch (ActiveMQNonExistentQueueException neq) { //this is ok, ActiveMQ 5 allows this and will actually do it quite often ActiveMQServerLogger.LOGGER.debug("queue never existed"); } } else { Bindings bindings = server.getPostOffice().getBindingsForAddress(new SimpleString(dest.getPhysicalName())); for (Binding binding : bindings.getBindings()) { Queue b = (Queue) binding.getBindable(); if (b.getConsumerCount() > 0) { throw new Exception("Destination still has an active subscription: " + dest.getPhysicalName()); } if (b.isDurable()) { throw new Exception("Destination still has durable subscription: " + dest.getPhysicalName()); } b.deleteQueue(); } } if (!AdvisorySupport.isAdvisoryTopic(dest)) { AMQConnectionContext context = getContext(); DestinationInfo advInfo = new DestinationInfo(context.getConnectionId(), DestinationInfo.REMOVE_OPERATION_TYPE, dest); ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(dest); protocolManager.fireAdvisory(context, topic, advInfo); } } /** * Checks to see if this destination exists. If it does not throw an invalid destination exception. * * @param destination */ private void validateDestination(ActiveMQDestination destination) throws Exception { if (destination.isQueue()) { SimpleString physicalName = new SimpleString(destination.getPhysicalName()); BindingQueryResult result = server.bindingQuery(physicalName); if (!result.isExists() && !result.isAutoCreateQueues()) { throw ActiveMQMessageBundle.BUNDLE.noSuchQueue(physicalName); } } } CommandProcessor commandProcessorInstance = new CommandProcessor(); // This will listen for commands through the protocolmanager public class CommandProcessor implements CommandVisitor { public AMQConnectionContext getContext() { return OpenWireConnection.this.getContext(); } @Override public Response processAddConnection(ConnectionInfo info) throws Exception { try { protocolManager.addConnection(OpenWireConnection.this, info); } catch (Exception e) { Response resp = new ExceptionResponse(e); return resp; } if (info.isManageable() && protocolManager.isUpdateClusterClients()) { // send ConnectionCommand ConnectionControl command = protocolManager.newConnectionControl(); command.setFaultTolerant(protocolManager.isFaultTolerantConfiguration()); if (info.isFailoverReconnect()) { command.setRebalanceConnection(false); } dispatchAsync(command); } return null; } @Override public Response processBrokerSubscriptionInfo(BrokerSubscriptionInfo brokerSubscriptionInfo) throws Exception { // TBD return null; } @Override public Response processAddProducer(ProducerInfo info) throws Exception { SessionId sessionId = info.getProducerId().getParentId(); ConnectionState cs = getState(); if (cs == null) { throw new IllegalStateException("Cannot add a producer to a connection that had not been registered: " + sessionId.getParentId()); } SessionState ss = cs.getSessionState(sessionId); if (ss == null) { throw new IllegalStateException("Cannot add a producer to a session that had not been registered: " + sessionId); } // Avoid replaying dup commands if (!ss.getProducerIds().contains(info.getProducerId())) { ActiveMQDestination destination = info.getDestination(); if (destination != null && !AdvisorySupport.isAdvisoryTopic(destination)) { if (destination.isQueue()) { OpenWireConnection.this.validateDestination(destination); } DestinationInfo destInfo = new DestinationInfo(getContext().getConnectionId(), DestinationInfo.ADD_OPERATION_TYPE, destination); OpenWireConnection.this.addDestination(destInfo); } ss.addProducer(info); } return null; } @Override public Response processAddConsumer(ConsumerInfo info) throws Exception { addConsumer(info); return null; } @Override public Response processRemoveDestination(DestinationInfo info) throws Exception { ActiveMQDestination dest = info.getDestination(); removeDestination(dest); return null; } @Override public Response processRemoveProducer(ProducerId id) throws Exception { return null; } @Override public Response processRemoveSession(SessionId id, long lastDeliveredSequenceId) throws Exception { SessionState session = state.getSessionState(id); if (session == null) { throw new IllegalStateException("Cannot remove session that had not been registered: " + id); } // Don't let new consumers or producers get added while we are closing // this down. session.shutdown(); // Cascade the connection stop producers. // we don't stop consumer because in core // closing the session will do the job for (ProducerId producerId : session.getProducerIds()) { try { processRemoveProducer(producerId); } catch (Throwable e) { // LOG.warn("Failed to remove producer: {}", producerId, e); } } state.removeSession(id); removeSession(context, session.getInfo()); return null; } @Override public Response processRemoveSubscription(RemoveSubscriptionInfo subInfo) throws Exception { SimpleString subQueueName = new SimpleString(org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForDurableSubscription(true, subInfo.getClientId(), subInfo.getSubscriptionName())); server.destroyQueue(subQueueName); return null; } @Override public Response processRollbackTransaction(TransactionInfo info) throws Exception { Transaction tx = lookupTX(info.getTransactionId(), null); if (info.getTransactionId().isXATransaction() && tx == null) { throw newXAException("Transaction '" + info.getTransactionId() + "' has not been started.", XAException.XAER_NOTA); } else if (tx != null) { AMQSession amqSession = (AMQSession) tx.getProtocolData(); if (amqSession != null) { amqSession.getCoreSession().resetTX(tx); try { returnReferences(tx, amqSession); } finally { amqSession.getCoreSession().resetTX(null); } } tx.rollback(); } return null; } /** * Openwire will redeliver rolled back references. * We need to return those here. */ private void returnReferences(Transaction tx, AMQSession session) throws Exception { if (session == null || session.isClosed()) { return; } RefsOperation oper = (RefsOperation) tx.getProperty(TransactionPropertyIndexes.REFS_OPERATION); if (oper != null) { List<MessageReference> ackRefs = oper.getReferencesToAcknowledge(); for (ListIterator<MessageReference> referenceIterator = ackRefs.listIterator(ackRefs.size()); referenceIterator.hasPrevious(); ) { MessageReference ref = referenceIterator.previous(); Long consumerID = ref.getConsumerId(); ServerConsumer consumer = null; if (consumerID != null) { consumer = session.getCoreSession().locateConsumer(consumerID); } if (consumer != null) { referenceIterator.remove(); ref.incrementDeliveryCount(); consumer.backToDelivering(ref); } } } } @Override public Response processShutdown(ShutdownInfo info) throws Exception { OpenWireConnection.this.shutdown(false); return null; } @Override public Response processWireFormat(WireFormatInfo command) throws Exception { wireFormat.renegotiateWireFormat(command); //throw back a brokerInfo here protocolManager.sendBrokerInfo(OpenWireConnection.this); protocolManager.setUpInactivityParams(OpenWireConnection.this, command); return null; } @Override public Response processAddDestination(DestinationInfo dest) throws Exception { Response resp = null; try { addDestination(dest); } catch (Exception e) { if (e instanceof ActiveMQSecurityException) { resp = new ExceptionResponse(new JMSSecurityException(e.getMessage())); } else { resp = new ExceptionResponse(e); } } return resp; } @Override public Response processAddSession(SessionInfo info) throws Exception { // Avoid replaying dup commands if (!state.getSessionIds().contains(info.getSessionId())) { addSession(info); state.addSession(info); } return null; } @Override public Response processBeginTransaction(TransactionInfo info) throws Exception { final TransactionId txID = info.getTransactionId(); try { internalSession.resetTX(null); if (txID.isXATransaction()) { Xid xid = OpenWireUtil.toXID(txID); internalSession.xaStart(xid); } else { Transaction transaction = internalSession.newTransaction(); txMap.put(txID, transaction); transaction.addOperation(new TransactionOperationAbstract() { @Override public void afterCommit(Transaction tx) { txMap.remove(txID); } }); } } finally { internalSession.resetTX(null); } return null; } @Override public Response processCommitTransactionOnePhase(TransactionInfo info) throws Exception { return processCommit(info, true); } private Response processCommit(TransactionInfo info, boolean onePhase) throws Exception { TransactionId txID = info.getTransactionId(); Transaction tx = lookupTX(txID, null); AMQSession session = (AMQSession) tx.getProtocolData(); tx.commit(onePhase); return null; } @Override public Response processCommitTransactionTwoPhase(TransactionInfo info) throws Exception { return processCommit(info, false); } @Override public Response processForgetTransaction(TransactionInfo info) throws Exception { TransactionId txID = info.getTransactionId(); if (txID.isXATransaction()) { try { Xid xid = OpenWireUtil.toXID(info.getTransactionId()); internalSession.xaForget(xid); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn("Error during method invocation", e); throw e; } } else { txMap.remove(txID); } return null; } @Override public Response processPrepareTransaction(TransactionInfo info) throws Exception { TransactionId txID = info.getTransactionId(); try { if (txID.isXATransaction()) { try { Xid xid = OpenWireUtil.toXID(info.getTransactionId()); internalSession.xaPrepare(xid); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn("Error during method invocation", e); throw e; } } else { Transaction tx = lookupTX(txID, null); tx.prepare(); } } finally { internalSession.resetTX(null); } return new IntegerResponse(XAResource.XA_RDONLY); } @Override public Response processEndTransaction(TransactionInfo info) throws Exception { TransactionId txID = info.getTransactionId(); if (txID.isXATransaction()) { try { Transaction tx = lookupTX(txID, null); internalSession.resetTX(tx); try { Xid xid = OpenWireUtil.toXID(info.getTransactionId()); internalSession.xaEnd(xid); } finally { internalSession.resetTX(null); } } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn("Error during method invocation", e); throw e; } } else { txMap.remove(txID); } return null; } @Override public Response processBrokerInfo(BrokerInfo arg0) throws Exception { throw new IllegalStateException("not implemented! "); } @Override public Response processConnectionControl(ConnectionControl connectionControl) throws Exception { //activemq5 keeps a var to remember only the faultTolerant flag //this can be sent over a reconnected transport as the first command //before restoring the connection. return null; } @Override public Response processConnectionError(ConnectionError arg0) throws Exception { throw new IllegalStateException("not implemented! "); } @Override public Response processConsumerControl(ConsumerControl consumerControl) throws Exception { //amq5 clients send this command to restore prefetchSize //after successful reconnect try { updateConsumer(consumerControl); } catch (Exception e) { //log error } return null; } @Override public Response processControlCommand(ControlCommand arg0) throws Exception { throw new IllegalStateException("not implemented! "); } @Override public Response processFlush(FlushCommand arg0) throws Exception { throw new IllegalStateException("not implemented! "); } @Override public Response processKeepAlive(KeepAliveInfo arg0) throws Exception { throw new IllegalStateException("not implemented! "); } @Override public Response processMessage(Message messageSend) throws Exception { ProducerId producerId = messageSend.getProducerId(); AMQProducerBrokerExchange producerExchange = getProducerBrokerExchange(producerId); final AMQConnectionContext pcontext = producerExchange.getConnectionContext(); final ProducerInfo producerInfo = producerExchange.getProducerState().getInfo(); boolean sendProducerAck = !messageSend.isResponseRequired() && producerInfo.getWindowSize() > 0 && !pcontext.isInRecoveryMode(); AMQSession session = getSession(producerId.getParentId()); Transaction tx = lookupTX(messageSend.getTransactionId(), session); session.getCoreSession().resetTX(tx); try { session.send(producerInfo, messageSend, sendProducerAck); } catch (Exception e) { if (tx != null) { tx.markAsRollbackOnly(new ActiveMQException(e.getMessage())); } throw e; } finally { session.getCoreSession().resetTX(null); } return null; } @Override public Response processMessageAck(MessageAck ack) throws Exception { AMQSession session = getSession(ack.getConsumerId().getParentId()); Transaction tx = lookupTX(ack.getTransactionId(), session); session.getCoreSession().resetTX(tx); try { AMQConsumerBrokerExchange consumerBrokerExchange = consumerExchanges.get(ack.getConsumerId()); consumerBrokerExchange.acknowledge(ack); } catch (Exception e) { if (tx != null) { tx.markAsRollbackOnly(new ActiveMQException(e.getMessage())); } } finally { session.getCoreSession().resetTX(null); } return null; } @Override public Response processMessageDispatch(MessageDispatch arg0) throws Exception { return null; } @Override public Response processMessageDispatchNotification(MessageDispatchNotification arg0) throws Exception { return null; } @Override public Response processMessagePull(MessagePull arg0) throws Exception { AMQConsumerBrokerExchange amqConsumerBrokerExchange = consumerExchanges.get(arg0.getConsumerId()); if (amqConsumerBrokerExchange == null) { throw new IllegalStateException("Consumer does not exist"); } amqConsumerBrokerExchange.processMessagePull(arg0); return null; } @Override public Response processProducerAck(ProducerAck arg0) throws Exception { // a broker doesn't do producers.. this shouldn't happen return null; } @Override public Response processRecoverTransactions(TransactionInfo info) throws Exception { List<Xid> xids = server.getResourceManager().getInDoubtTransactions(); List<TransactionId> recovered = new ArrayList<>(); for (Xid xid : xids) { XATransactionId amqXid = new XATransactionId(xid); recovered.add(amqXid); } return new DataArrayResponse(recovered.toArray(new TransactionId[recovered.size()])); } @Override public Response processRemoveConnection(ConnectionId id, long lastDeliveredSequenceId) throws Exception { //we let protocol manager to handle connection add/remove try { protocolManager.removeConnection(state.getInfo(), null); } catch (Throwable e) { // log } return null; } @Override public Response processRemoveConsumer(ConsumerId id, long lastDeliveredSequenceId) throws Exception { if (destroyed) { return null; } SessionId sessionId = id.getParentId(); SessionState ss = state.getSessionState(sessionId); if (ss == null) { throw new IllegalStateException("Cannot remove a consumer from a session that had not been registered: " + sessionId); } ConsumerState consumerState = ss.removeConsumer(id); if (consumerState == null) { throw new IllegalStateException("Cannot remove a consumer that had not been registered: " + id); } ConsumerInfo info = consumerState.getInfo(); info.setLastDeliveredSequenceId(lastDeliveredSequenceId); AMQConsumerBrokerExchange consumerBrokerExchange = consumerExchanges.remove(id); consumerBrokerExchange.removeConsumer(); return null; } } private void recoverOperationContext() { server.getStorageManager().setContext(this.operationContext); } private void clearupOperationContext() { server.getStorageManager().clearContext(); } private Transaction lookupTX(TransactionId txID, AMQSession session) throws IllegalStateException { if (txID == null) { return null; } Xid xid = null; Transaction transaction; if (txID.isXATransaction()) { xid = OpenWireUtil.toXID(txID); transaction = server.getResourceManager().getTransaction(xid); } else { transaction = txMap.get(txID); } if (transaction == null) { return null; } if (session != null && transaction.getProtocolData() != session) { transaction.setProtocolData(session); } return transaction; } public static XAException newXAException(String s, int errorCode) { XAException xaException = new XAException(s + " " + "xaErrorCode:" + errorCode); xaException.errorCode = errorCode; return xaException; } @Override public void killMessage(SimpleString nodeID) { //unsupported } }