/** * 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 io.hawtjms.provider.amqp; import io.hawtjms.jms.JmsDestination; import io.hawtjms.jms.meta.JmsConnectionInfo; import io.hawtjms.jms.meta.JmsSessionId; import io.hawtjms.jms.meta.JmsSessionInfo; import io.hawtjms.provider.AsyncResult; import io.hawtjms.util.IOExceptionSupport; import java.net.URI; import java.util.HashMap; import java.util.Map; import javax.jms.JMSSecurityException; import javax.jms.Session; import org.apache.qpid.proton.ProtonFactoryLoader; import org.apache.qpid.proton.engine.Connection; import org.apache.qpid.proton.engine.EndpointState; import org.apache.qpid.proton.engine.Sasl; import org.apache.qpid.proton.message.MessageFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AmqpConnection extends AbstractAmqpResource<JmsConnectionInfo, Connection> { private static final Logger LOG = LoggerFactory.getLogger(AmqpConnection.class); private static final ProtonFactoryLoader<MessageFactory> protonFactoryLoader = new ProtonFactoryLoader<MessageFactory>(MessageFactory.class); private final URI remoteURI; private final Map<JmsSessionId, AmqpSession> sessions = new HashMap<JmsSessionId, AmqpSession>(); private final Map<JmsDestination, AmqpTemporaryDestination> tempDests = new HashMap<JmsDestination, AmqpTemporaryDestination>(); private final AmqpProvider provider; private boolean connected; private AmqpSaslAuthenticator authenticator; private final AmqpSession connectionSession; private final MessageFactory messageFactory = protonFactoryLoader.loadFactory(); private String queuePrefix; private String topicPrefix; private String tempQueuePrefix; private String tempTopicPrefix; public AmqpConnection(AmqpProvider provider, Connection protonConnection, Sasl sasl, JmsConnectionInfo info) { super(info, protonConnection); this.provider = provider; this.remoteURI = provider.getRemoteURI(); if (sasl != null) { this.authenticator = new AmqpSaslAuthenticator(sasl, info); } this.info.getConnectionId().setProviderHint(this); this.queuePrefix = info.getQueuePrefix(); this.topicPrefix = info.getTopicPrefix(); this.tempQueuePrefix = info.getTempQueuePrefix(); this.tempTopicPrefix = info.getTempTopicPrefix(); // Create a Session for this connection that is used for Temporary Destinations // and perhaps later on management and advisory monitoring. JmsSessionInfo sessionInfo = new JmsSessionInfo(this.info, -1); sessionInfo.setAcknowledgementMode(Session.AUTO_ACKNOWLEDGE); this.connectionSession = new AmqpSession(this, sessionInfo); } @Override protected void doOpen() { this.endpoint.setContainer(info.getClientId()); this.endpoint.setHostname(remoteURI.getHost()); } @Override protected void doClose() { } public AmqpSession createSession(JmsSessionInfo sessionInfo) { AmqpSession session = new AmqpSession(this, sessionInfo); return session; } public AmqpTemporaryDestination createTemporaryDestination(JmsDestination destination) { AmqpTemporaryDestination temporary = new AmqpTemporaryDestination(connectionSession, destination); return temporary; } /** * Called on receiving an event from Proton indicating a state change on the remote * side of the Connection. */ @Override public void processStateChange() { if (!connected && isOpen()) { connected = true; connectionSession.open(new AsyncResult<Void>() { @Override public boolean isComplete() { return connected; } @Override public void onSuccess(Void result) { LOG.debug("AMQP Connection Session opened: {}", result); opened(); } @Override public void onSuccess() { onSuccess(null); } @Override public void onFailure(Throwable result) { LOG.debug("AMQP Connection Session failed to open."); failed(IOExceptionSupport.create(result)); } }); } EndpointState localState = endpoint.getLocalState(); EndpointState remoteState = endpoint.getRemoteState(); // We are still active (connected or not) and something on the remote end has // closed us, signal an error if one was sent. if (localState == EndpointState.ACTIVE && remoteState != EndpointState.ACTIVE) { if (endpoint.getRemoteCondition().getCondition() != null) { LOG.info("Error condition detected on Connection open {}.", endpoint.getRemoteCondition().getCondition()); Exception remoteError = getRemoteError(); if (isAwaitingOpen()) { openRequest.onFailure(remoteError); } else { provider.fireProviderException(remoteError); } } } // Transition cleanly to closed state. if (localState == EndpointState.CLOSED && remoteState == EndpointState.CLOSED) { LOG.debug("{} has been closed successfully.", this); closed(); } } @Override public void processUpdates() { if (connected || authenticator == null) { return; } try { if (authenticator.authenticate()) { authenticator = null; } } catch (JMSSecurityException ex) { failed(ex); } } void addTemporaryDestination(AmqpTemporaryDestination destination) { tempDests.put(destination.getJmsDestination(), destination); } void removeTemporaryDestination(AmqpTemporaryDestination destination) { tempDests.remove(destination.getJmsDestination()); } void addSession(AmqpSession session) { this.sessions.put(session.getSessionId(), session); } void removeSession(AmqpSession session) { this.sessions.remove(session.getSessionId()); } public JmsConnectionInfo getConnectionInfo() { return this.info; } public Connection getProtonConnection() { return this.endpoint; } public URI getRemoteURI() { return this.remoteURI; } public String getUsername() { return this.info.getUsername(); } public String getPassword() { return this.info.getPassword(); } public AmqpProvider getProvider() { return this.provider; } public String getQueuePrefix() { return queuePrefix; } public void setQueuePrefix(String queuePrefix) { this.queuePrefix = queuePrefix; } public String getTopicPrefix() { return topicPrefix; } public void setTopicPrefix(String topicPrefix) { this.topicPrefix = topicPrefix; } public String getTempQueuePrefix() { return tempQueuePrefix; } public void setTempQueuePrefix(String tempQueuePrefix) { this.tempQueuePrefix = tempQueuePrefix; } public String getTempTopicPrefix() { return tempTopicPrefix; } public void setTempTopicPrefix(String tempTopicPrefix) { this.tempTopicPrefix = tempTopicPrefix; } /** * Retrieve the indicated Session instance from the list of active sessions. * * @param sessionId * The JmsSessionId that's associated with the target session. * * @return the AmqpSession associated with the given id. */ public AmqpSession getSession(JmsSessionId sessionId) { if (sessionId.getProviderHint() instanceof AmqpSession) { return (AmqpSession) sessionId.getProviderHint(); } return this.sessions.get(sessionId); } /** * @return the loaded Proton MessageFactory used to create message objects. */ public MessageFactory getMessageFactory() { return this.messageFactory; } /** * @return true if the provider has been configured for presettle operations. */ public boolean isPresettleConsumers() { return provider.isPresettleConsumers(); } /** * @return true if the provider has been configured for presettle operations. */ public boolean isPresettleProducers() { return provider.isPresettleProducers(); } @Override public String toString() { return "AmqpConnection { " + getConnectionInfo().getConnectionId() + " }"; } }