diff options
| author | Rafael H. Schloming <rhs@apache.org> | 2009-12-26 12:42:57 +0000 |
|---|---|---|
| committer | Rafael H. Schloming <rhs@apache.org> | 2009-12-26 12:42:57 +0000 |
| commit | 248f1fe188fe2307b9dcf2c87a83b653eaa1920c (patch) | |
| tree | d5d0959a70218946ff72e107a6c106e32479a398 /cpp/src/qpid/client | |
| parent | 3c83a0e3ec7cf4dc23e83a340b25f5fc1676f937 (diff) | |
| download | qpid-python-248f1fe188fe2307b9dcf2c87a83b653eaa1920c.tar.gz | |
synchronized with trunk except for ruby dir
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/qpid.rnr@893970 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src/qpid/client')
85 files changed, 8089 insertions, 1967 deletions
diff --git a/cpp/src/qpid/client/AckMode.h b/cpp/src/qpid/client/AckMode.h deleted file mode 100644 index b411c322d8..0000000000 --- a/cpp/src/qpid/client/AckMode.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef _client_AckMode_h -#define _client_AckMode_h - - -/* - * - * 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. - * - */ - -namespace qpid { -namespace client { - -/** - * DEPRECATED - * - * The available acknowledgement modes for Channel (now also deprecated). - */ -enum AckMode { - /** No acknowledgement will be sent, broker can - discard messages as soon as they are delivered - to a consumer using this mode. **/ - NO_ACK = 0, - /** Each message will be automatically - acknowledged as soon as it is delivered to the - application. **/ - AUTO_ACK = 1, - /** Acknowledgements will be sent automatically, - but not for each message. **/ - LAZY_ACK = 2, - /** The application is responsible for explicitly - acknowledging messages. **/ - CLIENT_ACK = 3 -}; - -}} // namespace qpid::client - -#endif diff --git a/cpp/src/qpid/client/Bounds.cpp b/cpp/src/qpid/client/Bounds.cpp index aac18022bc..abb983a62e 100644 --- a/cpp/src/qpid/client/Bounds.cpp +++ b/cpp/src/qpid/client/Bounds.cpp @@ -1,4 +1,24 @@ -#include "Bounds.h" +/* + * + * 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. + * + */ +#include "qpid/client/Bounds.h" #include "qpid/log/Statement.h" #include "qpid/sys/Waitable.h" @@ -28,8 +48,7 @@ void Bounds::reduce(size_t size) { if (current == 0) return; current -= std::min(size, current); if (current < max && lock.hasWaiters()) { - assert(lock.hasWaiters() == 1); - lock.notify(); + lock.notifyAll(); } } diff --git a/cpp/src/qpid/client/AckPolicy.cpp b/cpp/src/qpid/client/Completion.cpp index 7956ebad0f..a97c8c3534 100644 --- a/cpp/src/qpid/client/AckPolicy.cpp +++ b/cpp/src/qpid/client/Completion.cpp @@ -18,33 +18,23 @@ * under the License. * */ -#include "AckPolicy.h" + +#include "qpid/client/Completion.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" namespace qpid { namespace client { -AckPolicy::AckPolicy(size_t n) : interval(n), count(n) {} +typedef PrivateImplRef<Completion> PI; +Completion::Completion(CompletionImpl* p) { PI::ctor(*this, p); } +Completion::Completion(const Completion& c) : Handle<CompletionImpl>() { PI::copy(*this, c); } +Completion::~Completion() { PI::dtor(*this); } +Completion& Completion::operator=(const Completion& c) { return PI::assign(*this, c); } -void AckPolicy::ack(const Message& msg, AsyncSession session) -{ - accepted.add(msg.getId()); - if (interval && --count==0) { - session.markCompleted(msg.getId(), false, true); - session.messageAccept(accepted); - accepted.clear(); - count = interval; - } else { - session.markCompleted(msg.getId(), false, false); - } -} -void AckPolicy::ackOutstanding(AsyncSession session) -{ - if (!accepted.empty()) { - session.messageAccept(accepted); - accepted.clear(); - session.sendCompletion(); - } -} +void Completion::wait() { impl->wait(); } +bool Completion::isComplete() { return impl->isComplete(); } +std::string Completion::getResult() { return impl->getResult(); } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/Completion.h b/cpp/src/qpid/client/Completion.h deleted file mode 100644 index c4979d7934..0000000000 --- a/cpp/src/qpid/client/Completion.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef _Completion_ -#define _Completion_ - -#include <boost/shared_ptr.hpp> -#include "Future.h" -#include "SessionImpl.h" - -namespace qpid { -namespace client { - -/** - * Asynchronous commands that do not return a result will return a - * Completion. You can use the completion to wait for that specific - * command to complete. - * - *@see TypedResult - * - *\ingroup clientapi - */ -class Completion -{ -protected: - Future future; - shared_ptr<SessionImpl> session; - -public: - ///@internal - Completion() {} - - ///@internal - Completion(Future f, shared_ptr<SessionImpl> s) : future(f), session(s) {} - - /** Wait for the asynchronous command that returned this - *Completion to complete. - * - *@exception If the command returns an error, get() throws an exception. - */ - void wait() - { - future.wait(*session); - } - - bool isComplete() { - return future.isComplete(*session); - } -}; - -}} - -#endif diff --git a/cpp/src/qpid/client/FutureResult.h b/cpp/src/qpid/client/CompletionImpl.h index e97d80476d..f180708316 100644 --- a/cpp/src/qpid/client/FutureResult.h +++ b/cpp/src/qpid/client/CompletionImpl.h @@ -1,3 +1,6 @@ +#ifndef QPID_CLIENT_COMPLETIONIMPL_H +#define QPID_CLIENT_COMPLETIONIMPL_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -19,29 +22,30 @@ * */ -#ifndef _FutureResult_ -#define _FutureResult_ - -#include <string> -#include "qpid/framing/amqp_framing.h" -#include "FutureCompletion.h" +#include "qpid/RefCounted.h" +#include "qpid/client/Future.h" +#include <boost/shared_ptr.hpp> namespace qpid { namespace client { -class SessionImpl; - ///@internal -class FutureResult : public FutureCompletion +class CompletionImpl : public RefCounted { - std::string result; public: - const std::string& getResult(SessionImpl& session) const; - void received(const std::string& result); -}; + CompletionImpl() {} + CompletionImpl(Future f, boost::shared_ptr<SessionImpl> s) : future(f), session(s) {} + + bool isComplete() { return future.isComplete(*session); } + void wait() { future.wait(*session); } + std::string getResult() { return future.getResult(*session); } -}} +protected: + Future future; + boost::shared_ptr<SessionImpl> session; +}; +}} // namespace qpid::client -#endif +#endif /*!QPID_CLIENT_COMPLETIONIMPL_H*/ diff --git a/cpp/src/qpid/client/Connection.cpp b/cpp/src/qpid/client/Connection.cpp index 6572794516..32a01b2c40 100644 --- a/cpp/src/qpid/client/Connection.cpp +++ b/cpp/src/qpid/client/Connection.cpp @@ -18,15 +18,16 @@ * under the License. * */ -#include "Connection.h" -#include "ConnectionSettings.h" -#include "Message.h" -#include "SessionImpl.h" -#include "SessionBase_0_10Access.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/Message.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/Url.h" #include "qpid/log/Logger.h" #include "qpid/log/Options.h" #include "qpid/log/Statement.h" -#include "qpid/shared_ptr.h" #include "qpid/framing/AMQP_HighestVersion.h" #include <algorithm> @@ -35,6 +36,7 @@ #include <functional> #include <boost/format.hpp> #include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> using namespace qpid::framing; using namespace qpid::sys; @@ -43,9 +45,45 @@ using namespace qpid::sys; namespace qpid { namespace client { -Connection::Connection() : channelIdCounter(0), version(framing::highestProtocolVersion) {} +Connection::Connection() : version(framing::highestProtocolVersion) {} -Connection::~Connection(){ } +Connection::~Connection() {} + +void Connection::open( + const Url& url, + const std::string& uid, const std::string& pwd, + const std::string& vhost, + uint16_t maxFrameSize) +{ + ConnectionSettings settings; + settings.username = uid; + settings.password = pwd; + settings.virtualhost = vhost; + settings.maxFrameSize = maxFrameSize; + open(url, settings); +} + +void Connection::open(const Url& url, const ConnectionSettings& settings) { + if (url.empty()) + throw Exception(QPID_MSG("Attempt to open URL with no addresses.")); + Url::const_iterator i = url.begin(); + do { + const TcpAddress* tcp = i->get<TcpAddress>(); + i++; + if (tcp) { + try { + ConnectionSettings cs(settings); + cs.host = tcp->host; + cs.port = tcp->port; + open(cs); + break; + } + catch (const Exception& /*e*/) { + if (i == url.end()) throw; + } + } + } while (i != url.end()); +} void Connection::open( const std::string& host, int port, @@ -67,39 +105,55 @@ bool Connection::isOpen() const { return impl && impl->isOpen(); } +void +Connection::registerFailureCallback ( boost::function<void ()> fn ) { + failureCallback = fn; + if ( impl ) + impl->registerFailureCallback ( fn ); +} + + + void Connection::open(const ConnectionSettings& settings) { if (isOpen()) throw Exception(QPID_MSG("Connection::open() was already called")); - impl = shared_ptr<ConnectionImpl>(new ConnectionImpl(version, settings)); - impl->open(settings.host, settings.port); - max_frame_size = impl->getNegotiatedSettings().maxFrameSize; + impl = boost::shared_ptr<ConnectionImpl>(new ConnectionImpl(version, settings)); + impl->open(); + if ( failureCallback ) + impl->registerFailureCallback ( failureCallback ); +} + +const ConnectionSettings& Connection::getNegotiatedSettings() +{ + if (!isOpen()) + throw Exception(QPID_MSG("Connection is not open.")); + return impl->getNegotiatedSettings(); } -Session Connection::newSession(const std::string& name) { +Session Connection::newSession(const std::string& name, uint32_t timeout) { if (!isOpen()) throw Exception(QPID_MSG("Connection has not yet been opened")); - shared_ptr<SessionImpl> simpl( - new SessionImpl(name, impl, ++channelIdCounter, max_frame_size)); - impl->addSession(simpl); - simpl->open(0); Session s; - SessionBase_0_10Access(s).set(simpl); + SessionBase_0_10Access(s).set(impl->newSession(name, timeout)); return s; } void Connection::resume(Session& session) { if (!isOpen()) throw Exception(QPID_MSG("Connection is not open.")); - - session.impl->setChannel(++channelIdCounter); impl->addSession(session.impl); session.impl->resume(impl); } void Connection::close() { - impl->close(); + if ( impl ) + impl->close(); +} + +std::vector<Url> Connection::getInitialBrokers() { + return impl ? impl->getInitialBrokers() : std::vector<Url>(); } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/Connection.h b/cpp/src/qpid/client/Connection.h deleted file mode 100644 index ee543e20d2..0000000000 --- a/cpp/src/qpid/client/Connection.h +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef _client_Connection_ -#define _client_Connection_ - -/* - * - * 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. - * - */ -#include <map> -#include <string> -#include "qpid/client/Session.h" - -namespace qpid { -namespace client { - -class ConnectionSettings; - -/** - * Represents a connection to an AMQP broker. All communication is - * initiated by establishing a connection, then creating one or more - * Session objecst using the connection. @see newSession() - * - * \ingroup clientapi - */ -class Connection -{ - framing::ChannelId channelIdCounter; - framing::ProtocolVersion version; - uint16_t max_frame_size; - - protected: - boost::shared_ptr<ConnectionImpl> impl; - - public: - /** - * Creates a connection object, but does not open the connection. - * @see open() - */ - Connection(); - ~Connection(); - - /** - * Opens a connection to a broker. - * - * @param host the host on which the broker is running. - * - * @param port the port on the which the broker is listening. - * - * @param uid the userid to connect with. - * - * @param pwd the password to connect with (currently SASL - * PLAIN is the only authentication method supported so this - * is sent in clear text). - * - * @param virtualhost the AMQP virtual host to use (virtual - * hosts, where implemented(!), provide namespace partitioning - * within a single broker). - */ - void open(const std::string& host, int port = 5672, - const std::string& uid = "guest", - const std::string& pwd = "guest", - const std::string& virtualhost = "/", uint16_t maxFrameSize=65535); - - /** - * Opens a connection to a broker. - * - * @param the settings to use (host, port etc) @see ConnectionSettings - */ - void open(const ConnectionSettings& settings); - - /** - * Close the connection. - * - * Any further use of this connection (without reopening it) will - * not succeed. - */ - void close(); - - /** - * Create a new session on this connection. Sessions allow - * multiple streams of work to be multiplexed over the same - * connection. The returned Session provides functions to send - * commands to the broker. - * - * Session functions are synchronous. In other words, a Session - * function will send a command to the broker and does not return - * until it receives the broker's response confirming the command - * was executed. - * - * AsyncSession provides asynchronous versions of the same - * functions. These functions send a command to the broker but do - * not wait for a response. - * - * You can convert a Session s into an AsyncSession as follows: - * @code - * #include <qpid/client/AsyncSession.h> - * AsyncSession as = async(s); - * @endcode - * - * You can execute a single command asynchronously will a Session s - * like ths: - * @code - * async(s).messageTransfer(...); - * @endcode - * - * Using an AsyncSession is faster for sending large numbers of - * commands, since each command is sent as soon as possible - * without waiting for the previous command to be confirmed. - * - * However with AsyncSession you cannot assume that a command has - * completed until you explicitly synchronize. The simplest way to - * do this is to call Session::sync() or AsyncSession::sync(). - * Both of these functions wait for the broker to confirm all - * commands issued so far on the session. - * - *@param name: A name to identify the session. @see qpid::SessionId - * If the name is empty (the default) then a unique name will be - * chosen using a Universally-unique identifier (UUID) algorithm. - */ - Session newSession(const std::string& name=std::string()); - - /** - * Resume a suspended session. A session may be resumed - * on a different connection to the one that created it. - */ - void resume(Session& session); - - bool isOpen() const; -}; - -}} // namespace qpid::client - - -#endif diff --git a/cpp/src/qpid/client/Session.h b/cpp/src/qpid/client/ConnectionAccess.h index bdabd26c82..b662fd5d8b 100644 --- a/cpp/src/qpid/client/Session.h +++ b/cpp/src/qpid/client/ConnectionAccess.h @@ -1,5 +1,5 @@ -#ifndef QPID_CLIENT_SESSION_H -#define QPID_CLIENT_SESSION_H +#ifndef QPID_CLIENT_CONNECTIONACCESS_H +#define QPID_CLIENT_CONNECTIONACCESS_H /* * @@ -21,19 +21,21 @@ * under the License. * */ -#include "qpid/client/Session_0_10.h" + +#include "qpid/client/Connection.h" + +/**@file @internal Internal use only */ namespace qpid { namespace client { -/** - * Session is an alias for Session_0_10 - * - * \ingroup clientapi - */ -typedef Session_0_10 Session; +struct ConnectionAccess { + static void setVersion(Connection& c, const framing::ProtocolVersion& v) { c.version = v; } + static boost::shared_ptr<ConnectionImpl> getImpl(Connection& c) { return c.impl; } +}; + }} // namespace qpid::client -#endif /*!QPID_CLIENT_SESSION_H*/ +#endif /*!QPID_CLIENT_CONNECTIONACCESS_H*/ diff --git a/cpp/src/qpid/client/ConnectionHandler.cpp b/cpp/src/qpid/client/ConnectionHandler.cpp index 22ebec76bf..8f1cc7b03f 100644 --- a/cpp/src/qpid/client/ConnectionHandler.cpp +++ b/cpp/src/qpid/client/ConnectionHandler.cpp @@ -7,9 +7,9 @@ * 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 @@ -19,17 +19,28 @@ * */ -#include "ConnectionHandler.h" +#include "qpid/client/ConnectionHandler.h" -#include "qpid/log/Statement.h" +#include "qpid/client/SaslFactory.h" #include "qpid/framing/amqp_framing.h" #include "qpid/framing/all_method_bodies.h" #include "qpid/framing/ClientInvoker.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Helpers.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SystemInfo.h" using namespace qpid::client; using namespace qpid::framing; -using namespace boost; +using namespace qpid::framing::connection; +using qpid::sys::SecurityLayer; +using qpid::sys::Duration; +using qpid::sys::TimerTask; +using qpid::sys::Timer; +using qpid::sys::AbsTime; +using qpid::sys::TIME_SEC; +using qpid::sys::ScopedLock; +using qpid::sys::Mutex; namespace { const std::string OK("OK"); @@ -40,24 +51,54 @@ const std::string INVALID_STATE_START("start received in invalid state"); const std::string INVALID_STATE_TUNE("tune received in invalid state"); const std::string INVALID_STATE_OPEN_OK("open-ok received in invalid state"); const std::string INVALID_STATE_CLOSE_OK("close-ok received in invalid state"); + +const std::string SESSION_FLOW_CONTROL("qpid.session_flow"); +const std::string CLIENT_PROCESS_NAME("qpid.client_process"); +const std::string CLIENT_PID("qpid.client_pid"); +const std::string CLIENT_PPID("qpid.client_ppid"); +const int SESSION_FLOW_CONTROL_VER = 1; } -ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, framing::ProtocolVersion& v) - : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this), proxy(outHandler), errorCode(200), version(v) -{ +CloseCode ConnectionHandler::convert(uint16_t replyCode) +{ + switch (replyCode) { + case 200: return CLOSE_CODE_NORMAL; + case 320: return CLOSE_CODE_CONNECTION_FORCED; + case 402: return CLOSE_CODE_INVALID_PATH; + case 501: default: + return CLOSE_CODE_FRAMING_ERROR; + } +} + +ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, ProtocolVersion& v) + : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this), proxy(outHandler), + errorCode(CLOSE_CODE_NORMAL), version(v) +{ insist = true; ESTABLISHED.insert(FAILED); ESTABLISHED.insert(CLOSED); ESTABLISHED.insert(OPEN); -} + + FINISHED.insert(FAILED); + FINISHED.insert(CLOSED); + + properties.setInt(SESSION_FLOW_CONTROL, SESSION_FLOW_CONTROL_VER); + properties.setString(CLIENT_PROCESS_NAME, sys::SystemInfo::getProcessName()); + properties.setInt(CLIENT_PID, sys::SystemInfo::getProcessId()); + properties.setInt(CLIENT_PPID, sys::SystemInfo::getParentProcessId()); +} void ConnectionHandler::incoming(AMQFrame& frame) { if (getState() == CLOSED) { - throw Exception("Received frame on closed connection"); + throw Exception("Received frame on closed connection"); } + if (rcvTimeoutTask) { + // Received frame on connection so delay timeout + rcvTimeoutTask->restart(); + } AMQBody* body = frame.getBody(); try { @@ -67,26 +108,27 @@ void ConnectionHandler::incoming(AMQFrame& frame) in(frame); break; case CLOSING: - QPID_LOG(warning, "Ignoring frame while closing connection: " << frame); + QPID_LOG(warning, "Ignoring frame while closing connection: " << frame); break; default: throw Exception("Cannot receive frames on non-zero channel until connection is established."); } } }catch(std::exception& e){ - QPID_LOG(warning, "Closing connection due to " << e.what()); + QPID_LOG(warning, "Closing connection due to " << e.what()); setState(CLOSING); - proxy.close(501, e.what()); - if (onError) onError(501, e.what()); + errorCode = CLOSE_CODE_FRAMING_ERROR; + errorText = e.what(); + proxy.close(501, e.what()); } } void ConnectionHandler::outgoing(AMQFrame& frame) { - if (getState() == OPEN) + if (getState() == OPEN) out(frame); else - throw Exception(errorText.empty() ? "Connection is not open." : errorText); + throw TransportFailure(errorText.empty() ? "Connection is not open." : errorText); } void ConnectionHandler::waitForOpen() @@ -105,14 +147,29 @@ void ConnectionHandler::close() fail("Connection closed before it was established"); break; case OPEN: - setState(CLOSING); - proxy.close(200, OK); - waitFor(CLOSED); + if (setState(CLOSING, OPEN)) { + proxy.close(200, OK); + waitFor(FINISHED);//FINISHED = CLOSED or FAILED + } + //else, state was changed from open after we checked, can only + //change to failed or closed, so nothing to do break; - // Nothing to do for CLOSING, CLOSED, FAILED or NOT_STARTED + + // Nothing to do if already CLOSING, CLOSED, FAILED or if NOT_STARTED } } +void ConnectionHandler::heartbeat() +{ + // Do nothing - the purpose of heartbeats is just to make sure that there is some + // traffic on the connection within the heart beat interval, we check for the + // traffic and don't need to do anything in response to heartbeats + + // Although the above is still true we're now using a received heartbeat as a trigger + // to send out our own heartbeat + proxy.heartbeat(); +} + void ConnectionHandler::checkState(STATES s, const std::string& msg) { if (getState() != s) { @@ -122,45 +179,92 @@ void ConnectionHandler::checkState(STATES s, const std::string& msg) void ConnectionHandler::fail(const std::string& message) { - errorCode = 502; + errorCode = CLOSE_CODE_FRAMING_ERROR; errorText = message; QPID_LOG(warning, message); setState(FAILED); } -void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& /*mechanisms*/, const Array& /*locales*/) +namespace { +std::string SPACE(" "); +} + +void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& mechanisms, const Array& /*locales*/) { checkState(NOT_STARTED, INVALID_STATE_START); setState(NEGOTIATING); - //TODO: verify that desired mechanism and locale are supported - string response = ((char)0) + username + ((char)0) + password; - proxy.startOk(properties, mechanism, response, locale); + sasl = SaslFactory::getInstance().create(*this); + + std::string mechlist; + bool chosenMechanismSupported = mechanism.empty(); + for (Array::const_iterator i = mechanisms.begin(); i != mechanisms.end(); ++i) { + if (!mechanism.empty() && mechanism == (*i)->get<std::string>()) { + chosenMechanismSupported = true; + mechlist = (*i)->get<std::string>() + SPACE + mechlist; + } else { + if (i != mechanisms.begin()) mechlist += SPACE; + mechlist += (*i)->get<std::string>(); + } + } + + if (!chosenMechanismSupported) { + fail("Selected mechanism not supported: " + mechanism); + } + + if (sasl.get()) { + string response = sasl->start(mechanism.empty() ? mechlist : mechanism, + getSSF ? getSSF() : 0); + proxy.startOk(properties, sasl->getMechanism(), response, locale); + } else { + //TODO: verify that desired mechanism and locale are supported + string response = ((char)0) + username + ((char)0) + password; + proxy.startOk(properties, mechanism, response, locale); + } } -void ConnectionHandler::secure(const std::string& /*challenge*/) +void ConnectionHandler::secure(const std::string& challenge) { - throw NotImplementedException("Challenge-response cycle not yet implemented in client"); + if (sasl.get()) { + string response = sasl->step(challenge); + proxy.secureOk(response); + } else { + throw NotImplementedException("Challenge-response cycle not yet implemented in client"); + } } -void ConnectionHandler::tune(uint16_t maxChannelsProposed, uint16_t maxFrameSizeProposed, - uint16_t /*heartbeatMin*/, uint16_t /*heartbeatMax*/) +void ConnectionHandler::tune(uint16_t maxChannelsProposed, uint16_t maxFrameSizeProposed, + uint16_t heartbeatMin, uint16_t heartbeatMax) { checkState(NEGOTIATING, INVALID_STATE_TUNE); maxChannels = std::min(maxChannels, maxChannelsProposed); maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed); - //TODO: implement heartbeats and check desired value is in valid range + // Clip the requested heartbeat to the maximum/minimum offered + uint16_t heartbeat = ConnectionSettings::heartbeat; + heartbeat = heartbeat < heartbeatMin ? heartbeatMin : + heartbeat > heartbeatMax ? heartbeatMax : + heartbeat; + ConnectionSettings::heartbeat = heartbeat; proxy.tuneOk(maxChannels, maxFrameSize, heartbeat); setState(OPENING); proxy.open(virtualhost, capabilities, insist); } -void ConnectionHandler::openOk(const framing::Array& /*knownHosts*/) +void ConnectionHandler::openOk ( const Array& knownBrokers ) { checkState(OPENING, INVALID_STATE_OPEN_OK); - //TODO: store knownHosts for reconnection etc + knownBrokersUrls.clear(); + framing::Array::ValueVector::const_iterator i; + for ( i = knownBrokers.begin(); i != knownBrokers.end(); ++i ) + knownBrokersUrls.push_back(Url((*i)->get<std::string>())); + if (sasl.get()) { + securityLayer = sasl->getSecurityLayer(maxFrameSize); + operUserId = sasl->getUserId(); + } setState(OPEN); + QPID_LOG(debug, "Known-brokers for connection: " << log::formatList(knownBrokersUrls)); } + void ConnectionHandler::redirect(const std::string& /*host*/, const Array& /*knownHosts*/) { throw NotImplementedException("Redirection received from broker; not yet implemented in client"); @@ -169,7 +273,7 @@ void ConnectionHandler::redirect(const std::string& /*host*/, const Array& /*kno void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText) { proxy.closeOk(); - errorCode = replyCode; + errorCode = convert(replyCode); errorText = replyText; setState(CLOSED); QPID_LOG(warning, "Broker closed connection: " << replyCode << ", " << replyText); @@ -181,7 +285,9 @@ void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText) void ConnectionHandler::closeOk() { checkState(CLOSING, INVALID_STATE_CLOSE_OK); - if (onClose) { + if (onError && errorCode != CLOSE_CODE_NORMAL) { + onError(errorCode, errorText); + } else if (onClose) { onClose(); } setState(CLOSED); @@ -195,5 +301,17 @@ bool ConnectionHandler::isOpen() const bool ConnectionHandler::isClosed() const { int s = getState(); - return s == CLOSING || s == CLOSED || s == FAILED; + return s == CLOSED || s == FAILED; +} + +bool ConnectionHandler::isClosing() const { return getState() == CLOSING; } + +std::auto_ptr<qpid::sys::SecurityLayer> ConnectionHandler::getSecurityLayer() +{ + return securityLayer; +} + +void ConnectionHandler::setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask> t) +{ + rcvTimeoutTask = t; } diff --git a/cpp/src/qpid/client/ConnectionHandler.h b/cpp/src/qpid/client/ConnectionHandler.h index f8bd5e5d49..ed1e385dcf 100644 --- a/cpp/src/qpid/client/ConnectionHandler.h +++ b/cpp/src/qpid/client/ConnectionHandler.h @@ -21,17 +21,23 @@ #ifndef _ConnectionHandler_ #define _ConnectionHandler_ -#include "ChainableFrameHandler.h" -#include "ConnectionSettings.h" -#include "StateManager.h" +#include "qpid/client/ChainableFrameHandler.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/Sasl.h" +#include "qpid/client/StateManager.h" #include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/AMQP_HighestVersion.h" #include "qpid/framing/AMQP_ClientOperations.h" #include "qpid/framing/AMQP_ServerProxy.h" #include "qpid/framing/Array.h" +#include "qpid/framing/enum.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/InputHandler.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/Timer.h" +#include "qpid/Url.h" +#include <memory> namespace qpid { namespace client { @@ -44,7 +50,7 @@ class ConnectionHandler : private StateManager, { typedef framing::AMQP_ClientOperations::ConnectionHandler ConnectionOperations; enum STATES {NOT_STARTED, NEGOTIATING, OPENING, OPEN, CLOSING, CLOSED, FAILED}; - std::set<int> ESTABLISHED; + std::set<int> ESTABLISHED, FINISHED; class Adapter : public framing::FrameHandler { @@ -56,12 +62,16 @@ class ConnectionHandler : private StateManager, Adapter outHandler; framing::AMQP_ServerProxy::Connection proxy; - uint16_t errorCode; + framing::connection::CloseCode errorCode; std::string errorText; bool insist; framing::ProtocolVersion version; framing::Array capabilities; framing::FieldTable properties; + std::auto_ptr<Sasl> sasl; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + boost::intrusive_ptr<qpid::sys::TimerTask> rcvTimeoutTask; + std::string operUserId; void checkState(STATES s, const std::string& msg); @@ -79,11 +89,13 @@ class ConnectionHandler : private StateManager, const framing::Array& knownHosts); void close(uint16_t replyCode, const std::string& replyText); void closeOk(); + void heartbeat(); public: using InputHandler::handle; typedef boost::function<void()> CloseListener; - typedef boost::function<void(uint16_t, const std::string&)> ErrorListener; + typedef boost::function<void(uint16_t, const std::string&)> ErrorListener; + typedef boost::function<unsigned int()> GetConnSSF; ConnectionHandler(const ConnectionSettings&, framing::ProtocolVersion&); @@ -99,9 +111,19 @@ public: // Note that open and closed aren't related by open = !closed bool isOpen() const; bool isClosed() const; + bool isClosing() const; + + std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(); + void setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask>); CloseListener onClose; ErrorListener onError; + + std::vector<Url> knownBrokersUrls; + + static framing::connection::CloseCode convert(uint16_t replyCode); + const std::string& getUserId() const { return operUserId; } + GetConnSSF getSSF; /** query the connection for its security strength factor */ }; }} diff --git a/cpp/src/qpid/client/ConnectionImpl.cpp b/cpp/src/qpid/client/ConnectionImpl.cpp index 5e8596cacb..cede7f7310 100644 --- a/cpp/src/qpid/client/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/ConnectionImpl.cpp @@ -18,55 +18,97 @@ * under the License. * */ -#include "ConnectionImpl.h" -#include "Connector.h" -#include "ConnectionSettings.h" -#include "SessionImpl.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Connector.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/SessionImpl.h" #include "qpid/log/Statement.h" -#include "qpid/framing/constants.h" +#include "qpid/Url.h" +#include "qpid/framing/enum.h" #include "qpid/framing/reply_exceptions.h" #include <boost/bind.hpp> #include <boost/format.hpp> -using namespace qpid::client; +#include <limits> + +namespace qpid { +namespace client { + using namespace qpid::framing; +using namespace qpid::framing::connection; using namespace qpid::sys; - using namespace qpid::framing::connection;//for connection error codes +// Get timer singleton +Timer& theTimer() { + static Mutex timerInitLock; + ScopedLock<Mutex> l(timerInitLock); + + static qpid::sys::Timer t; + return t; +} + +class HeartbeatTask : public TimerTask { + TimeoutHandler& timeout; + + void fire() { + // If we ever get here then we have timed out + QPID_LOG(debug, "Traffic timeout"); + timeout.idleIn(); + } + +public: + HeartbeatTask(Duration p, TimeoutHandler& t) : + TimerTask(p), + timeout(t) + {} +}; + ConnectionImpl::ConnectionImpl(framing::ProtocolVersion v, const ConnectionSettings& settings) : Bounds(settings.maxFrameSize * settings.bounds), handler(settings, v), - connector(new Connector(v, settings, this)), - version(v) + version(v), + nextChannel(1) { - QPID_LOG(debug, "ConnectionImpl created for " << version); + QPID_LOG(debug, "ConnectionImpl created for " << version.toString()); handler.in = boost::bind(&ConnectionImpl::incoming, this, _1); handler.out = boost::bind(&Connector::send, boost::ref(connector), _1); handler.onClose = boost::bind(&ConnectionImpl::closed, this, - NORMAL, std::string()); - connector->setInputHandler(&handler); - connector->setShutdownHandler(this); - + CLOSE_CODE_NORMAL, std::string()); //only set error handler once open handler.onError = boost::bind(&ConnectionImpl::closed, this, _1, _2); + handler.getSSF = boost::bind(&Connector::getSSF, boost::ref(connector)); } +const uint16_t ConnectionImpl::NEXT_CHANNEL = std::numeric_limits<uint16_t>::max(); + ConnectionImpl::~ConnectionImpl() { // Important to close the connector first, to ensure the // connector thread does not call on us while the destructor // is running. - connector->close(); + if (connector) connector->close(); } -void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session) +void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session, uint16_t channel) { Mutex::ScopedLock l(lock); - boost::weak_ptr<SessionImpl>& s = sessions[session->getChannel()]; - if (s.lock()) throw SessionBusyException(); - s = session; + for (uint16_t i = 0; i < NEXT_CHANNEL; i++) { //will at most search through channels once + uint16_t c = channel == NEXT_CHANNEL ? nextChannel++ : channel; + boost::weak_ptr<SessionImpl>& s = sessions[c]; + boost::shared_ptr<SessionImpl> ss = s.lock(); + if (!ss) { + //channel is free, we can assign it to this session + session->setChannel(c); + s = session; + return; + } else if (channel != NEXT_CHANNEL) { + //channel is taken and was requested explicitly so don't look for another + throw SessionBusyException(QPID_MSG("Channel " << ss->getChannel() << " attached to " << ss->getId())); + } //else channel is busy, but we can keep looking for a free one + } + } void ConnectionImpl::handle(framing::AMQFrame& frame) @@ -81,9 +123,11 @@ void ConnectionImpl::incoming(framing::AMQFrame& frame) Mutex::ScopedLock l(lock); s = sessions[frame.getChannel()].lock(); } - if (!s) - throw NotAttachedException(QPID_MSG("Invalid channel: " << frame.getChannel())); - s->in(frame); + if (!s) { + QPID_LOG(info, "Dropping frame received on invalid channel: " << frame); + } else { + s->in(frame); + } } bool ConnectionImpl::isOpen() const @@ -92,57 +136,121 @@ bool ConnectionImpl::isOpen() const } -void ConnectionImpl::open(const std::string& host, int port) +void ConnectionImpl::open() { - QPID_LOG(info, "Connecting to " << host << ":" << port); + const std::string& protocol = handler.protocol; + const std::string& host = handler.host; + int port = handler.port; + QPID_LOG(info, "Connecting to " << protocol << ":" << host << ":" << port); + + connector.reset(Connector::create(protocol, version, handler, this)); + connector->setInputHandler(&handler); + connector->setShutdownHandler(this); connector->connect(host, port); connector->init(); - handler.waitForOpen(); + + // Enable heartbeat if requested + uint16_t heartbeat = static_cast<ConnectionSettings&>(handler).heartbeat; + if (heartbeat) { + // Set connection timeout to be 2x heart beat interval and setup timer + heartbeatTask = new HeartbeatTask(heartbeat * 2 * TIME_SEC, *this); + handler.setRcvTimeoutTask(heartbeatTask); + theTimer().add(heartbeatTask); + } + + try { + handler.waitForOpen(); + } catch (...) { + // Make sure the connector thread is joined. + connector->close(); + throw; + } + + // If the SASL layer has provided an "operational" userId for the connection, + // put it in the negotiated settings. + const std::string& userId(handler.getUserId()); + if (!userId.empty()) + handler.username = userId; + + //enable security layer if one has been negotiated: + std::auto_ptr<SecurityLayer> securityLayer = handler.getSecurityLayer(); + if (securityLayer.get()) { + QPID_LOG(debug, "Activating security layer"); + connector->activateSecurityLayer(securityLayer); + } else { + QPID_LOG(debug, "No security layer in place"); + } } void ConnectionImpl::idleIn() { - close(); + connector->abort(); } void ConnectionImpl::idleOut() { - AMQFrame frame(in_place<AMQHeartbeatBody>()); + AMQFrame frame((AMQHeartbeatBody())); connector->send(frame); } void ConnectionImpl::close() { - if (!handler.isOpen()) return; - handler.close(); - closed(NORMAL, "Closed by client"); + if (heartbeatTask) + heartbeatTask->cancel(); + // close() must be idempotent and no-throw as it will often be called in destructors. + if (handler.isOpen()) { + try { + handler.close(); + closed(CLOSE_CODE_NORMAL, "Closed by client"); + } catch (...) {} + } + assert(!handler.isOpen()); } template <class F> void ConnectionImpl::closeInternal(const F& f) { - connector->close(); - for (SessionMap::iterator i = sessions.begin(); i != sessions.end(); ++i) { + if (heartbeatTask) { + heartbeatTask->cancel(); + } + { + Mutex::ScopedUnlock u(lock); + connector->close(); + } + //notifying sessions of failure can result in those session being + //deleted which in turn results in a call to erase(); this can + //even happen on this thread, when 's' goes out of scope + //below. Using a copy prevents the map being modified as we + //iterate through. + SessionMap copy; + sessions.swap(copy); + for (SessionMap::iterator i = copy.begin(); i != copy.end(); ++i) { boost::shared_ptr<SessionImpl> s = i->second.lock(); if (s) f(s); } - sessions.clear(); } void ConnectionImpl::closed(uint16_t code, const std::string& text) { Mutex::ScopedLock l(lock); - setException(new ConnectionException(code, text)); + setException(new ConnectionException(ConnectionHandler::convert(code), text)); closeInternal(boost::bind(&SessionImpl::connectionClosed, _1, code, text)); } -static const std::string CONN_CLOSED("Connection closed by broker"); +static const std::string CONN_CLOSED("Connection closed"); void ConnectionImpl::shutdown() { - Mutex::ScopedLock l(lock); - // FIXME aconway 2008-06-06: exception use, connection-forced is incorrect here. - setException(new ConnectionException(CONNECTION_FORCED, CONN_CLOSED)); + if ( failureCallback ) + failureCallback(); + if (handler.isClosed()) return; - handler.fail(CONN_CLOSED); - closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, CONNECTION_FORCED, CONN_CLOSED)); + + // FIXME aconway 2008-06-06: exception use, amqp0-10 does not seem to have + // an appropriate close-code. connection-forced is not right. + bool isClosing = handler.isClosing(); + handler.fail(CONN_CLOSED);//ensure connection is marked as failed before notifying sessions + Mutex::ScopedLock l(lock); + if (!isClosing) + closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, CONN_CLOSED)); + setException(new TransportFailure(CONN_CLOSED)); } void ConnectionImpl::erase(uint16_t ch) { @@ -154,4 +262,16 @@ const ConnectionSettings& ConnectionImpl::getNegotiatedSettings() { return handler; } - + +std::vector<qpid::Url> ConnectionImpl::getInitialBrokers() { + return handler.knownBrokersUrls; +} + +boost::shared_ptr<SessionImpl> ConnectionImpl::newSession(const std::string& name, uint32_t timeout, uint16_t channel) { + boost::shared_ptr<SessionImpl> simpl(new SessionImpl(name, shared_from_this())); + addSession(simpl, channel); + simpl->open(timeout); + return simpl; +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/ConnectionImpl.h b/cpp/src/qpid/client/ConnectionImpl.h index cd0788d68b..2b32e1ccf0 100644 --- a/cpp/src/qpid/client/ConnectionImpl.h +++ b/cpp/src/qpid/client/ConnectionImpl.h @@ -22,8 +22,9 @@ #ifndef _ConnectionImpl_ #define _ConnectionImpl_ -#include "Bounds.h" -#include "ConnectionHandler.h" +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionHandler.h" + #include "qpid/framing/FrameHandler.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/ShutdownHandler.h" @@ -39,7 +40,7 @@ namespace qpid { namespace client { class Connector; -class ConnectionSettings; +struct ConnectionSettings; class SessionImpl; class ConnectionImpl : public Bounds, @@ -51,12 +52,17 @@ class ConnectionImpl : public Bounds, { typedef std::map<uint16_t, boost::weak_ptr<SessionImpl> > SessionMap; + static const uint16_t NEXT_CHANNEL; + SessionMap sessions; ConnectionHandler handler; boost::scoped_ptr<Connector> connector; framing::ProtocolVersion version; + uint16_t nextChannel; sys::Mutex lock; + boost::intrusive_ptr<qpid::sys::TimerTask> heartbeatTask; + template <class F> void closeInternal(const F&); void incoming(framing::AMQFrame& frame); @@ -65,20 +71,27 @@ class ConnectionImpl : public Bounds, void idleIn(); void shutdown(); + boost::function<void ()> failureCallback; + public: ConnectionImpl(framing::ProtocolVersion version, const ConnectionSettings& settings); ~ConnectionImpl(); - void open(const std::string& host, int port); + void open(); bool isOpen() const; - void addSession(const boost::shared_ptr<SessionImpl>&); + boost::shared_ptr<SessionImpl> newSession(const std::string& name, uint32_t timeout, uint16_t channel=NEXT_CHANNEL); + void addSession(const boost::shared_ptr<SessionImpl>&, uint16_t channel=NEXT_CHANNEL); void close(); void handle(framing::AMQFrame& frame); void erase(uint16_t channel); - const ConnectionSettings& getNegotiatedSettings(); + + std::vector<Url> getInitialBrokers(); + void registerFailureCallback ( boost::function<void ()> fn ) { failureCallback = fn; } + + framing::ProtocolVersion getVersion() { return version; } }; }} diff --git a/cpp/src/qpid/client/ConnectionSettings.cpp b/cpp/src/qpid/client/ConnectionSettings.cpp index 6bc220cd41..3ae4ee010a 100644 --- a/cpp/src/qpid/client/ConnectionSettings.cpp +++ b/cpp/src/qpid/client/ConnectionSettings.cpp @@ -18,27 +18,28 @@ * under the License. * */ -#include "ConnectionSettings.h" +#include "qpid/client/ConnectionSettings.h" #include "qpid/log/Logger.h" #include "qpid/sys/Socket.h" -#include <sys/socket.h> +#include "qpid/Version.h" namespace qpid { namespace client { ConnectionSettings::ConnectionSettings() : + protocol("tcp"), host("localhost"), port(TcpAddress::DEFAULT_PORT), - username("guest"), - password("guest"), - mechanism("PLAIN"), locale("en_US"), heartbeat(0), maxChannels(32767), maxFrameSize(65535), bounds(2), - tcpNoDelay(false) + tcpNoDelay(false), + service(qpid::saslName), + minSsf(0), + maxSsf(256) {} ConnectionSettings::~ConnectionSettings() {} @@ -46,7 +47,7 @@ ConnectionSettings::~ConnectionSettings() {} void ConnectionSettings::configureSocket(qpid::sys::Socket& socket) const { if (tcpNoDelay) { - socket.setTcpNoDelay(tcpNoDelay); + socket.setTcpNoDelay(); QPID_LOG(info, "Set TCP_NODELAY"); } } diff --git a/cpp/src/qpid/client/ConnectionSettings.h b/cpp/src/qpid/client/ConnectionSettings.h deleted file mode 100644 index 5e93b3103e..0000000000 --- a/cpp/src/qpid/client/ConnectionSettings.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef QPID_CLIENT_CONNECTIONSETTINGS_H -#define QPID_CLIENT_CONNECTIONSETTINGS_H - -/* - * - * 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. - * - */ - -#include "qpid/Options.h" -#include "qpid/log/Options.h" -#include "qpid/Url.h" - -#include <iostream> -#include <exception> - -namespace qpid { - -namespace sys { -class Socket; -} - -namespace client { - -/** - * Settings for a Connection. - */ -struct ConnectionSettings { - - ConnectionSettings(); - virtual ~ConnectionSettings(); - - /** - * Allows socket to be configured; default only sets tcp-nodelay - * based on the flag set. Can be overridden. - */ - virtual void configureSocket(qpid::sys::Socket&) const; - - /** - * The host (or ip address) to connect to (defaults to 'localhost'). - */ - std::string host; - /** - * The port to connect to (defaults to 5672). - */ - uint16_t port; - /** - * Allows an AMQP 'virtual host' to be specified for the - * connection. - */ - std::string virtualhost; - - /** - * The username to use when authenticating the connection. - */ - std::string username; - /** - * The password to use when authenticating the connection. - */ - std::string password; - /** - * The SASL mechanism to use when authenticating the connection; - * the options are currently PLAIN or ANONYMOUS. - */ - std::string mechanism; - /** - * Allows a locale to be specified for the connection. - */ - std::string locale; - /** - * Allows a heartbeat frequency to be specified (this feature is - * not yet implemented). - */ - uint16_t heartbeat; - /** - * The maximum number of channels that the client will request for - * use on this connection. - */ - uint16_t maxChannels; - /** - * The maximum frame size that the client will request for this - * connection. - */ - uint16_t maxFrameSize; - /** - * Allows the size of outgoing frames to be limited. The value - * should be a mutliple of the maximum buffer size in use (which - * is in turn set through the maxFrameSize setting above). - */ - uint bounds; - /** - * If true, TCP_NODELAY will be set for the connection. - */ - bool tcpNoDelay; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_CONNECTIONSETTINGS_H*/ diff --git a/cpp/src/qpid/client/Connector.cpp b/cpp/src/qpid/client/Connector.cpp index f4f414bc63..2c4feffdcf 100644 --- a/cpp/src/qpid/client/Connector.cpp +++ b/cpp/src/qpid/client/Connector.cpp @@ -18,20 +18,23 @@ * under the License. * */ -#include "Connector.h" -#include "Bounds.h" -#include "ConnectionImpl.h" -#include "ConnectionSettings.h" +#include "qpid/client/Connector.h" + +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" #include "qpid/log/Statement.h" +#include "qpid/sys/Codec.h" #include "qpid/sys/Time.h" #include "qpid/framing/AMQFrame.h" #include "qpid/sys/AsynchIO.h" #include "qpid/sys/Dispatcher.h" #include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" #include "qpid/Msg.h" #include <iostream> +#include <map> #include <boost/bind.hpp> #include <boost/format.hpp> @@ -43,227 +46,37 @@ using namespace qpid::framing; using boost::format; using boost::str; -Connector::Connector(ProtocolVersion ver, - const ConnectionSettings& settings, - ConnectionImpl* cimpl) - : maxFrameSize(settings.maxFrameSize), - version(ver), - initiated(false), - closed(true), - joined(true), - timeout(0), - idleIn(0), idleOut(0), - timeoutHandler(0), - shutdownHandler(0), - writer(maxFrameSize, cimpl), - aio(0), - impl(cimpl) -{ - QPID_LOG(debug, "Connector created for " << version); - settings.configureSocket(socket); -} - -Connector::~Connector() { - close(); -} - -void Connector::connect(const std::string& host, int port){ - Mutex::ScopedLock l(closedLock); - assert(closed); - socket.connect(host, port); - identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); - closed = false; - poller = Poller::shared_ptr(new Poller); - aio = new AsynchIO(socket, - boost::bind(&Connector::readbuff, this, _1, _2), - boost::bind(&Connector::eof, this, _1), - boost::bind(&Connector::eof, this, _1), - 0, // closed - 0, // nobuffs - boost::bind(&Connector::writebuff, this, _1)); - writer.init(identifier, aio); -} - -void Connector::init(){ - Mutex::ScopedLock l(closedLock); - assert(joined); - ProtocolInitiation init(version); - writeDataBlock(init); - joined = false; - receiver = Thread(this); -} - -bool Connector::closeInternal() { - Mutex::ScopedLock l(closedLock); - bool ret = !closed; - if (!closed) { - closed = true; - poller->shutdown(); - } - if (!joined && receiver.id() != Thread::current().id()) { - joined = true; - Mutex::ScopedUnlock u(closedLock); - receiver.join(); - } - return ret; -} - -void Connector::close() { - closeInternal(); -} - -void Connector::setInputHandler(InputHandler* handler){ - input = handler; -} - -void Connector::setShutdownHandler(ShutdownHandler* handler){ - shutdownHandler = handler; -} - -OutputHandler* Connector::getOutputHandler(){ - return this; -} +// Stuff for the registry of protocol connectors (maybe should be moved to its own file) +namespace { + typedef std::map<std::string, Connector::Factory*> ProtocolRegistry; -void Connector::send(AMQFrame& frame) { - writer.handle(frame); -} + ProtocolRegistry& theProtocolRegistry() { + static ProtocolRegistry protocolRegistry; -void Connector::handleClosed() { - if (closeInternal() && shutdownHandler) - shutdownHandler->shutdown(); + return protocolRegistry; + } } -struct Connector::Buff : public AsynchIO::BufferBase { - Buff(size_t size) : AsynchIO::BufferBase(new char[size], size) {} - ~Buff() { delete [] bytes;} -}; - -Connector::Writer::Writer(uint16_t s, Bounds* b) : maxFrameSize(s), aio(0), buffer(0), lastEof(0), bounds(b) +Connector* Connector::create(const std::string& proto, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { -} - -Connector::Writer::~Writer() { delete buffer; } - -void Connector::Writer::init(std::string id, sys::AsynchIO* a) { - Mutex::ScopedLock l(lock); - identifier = id; - aio = a; - newBuffer(l); -} -void Connector::Writer::handle(framing::AMQFrame& frame) { - Mutex::ScopedLock l(lock); - frames.push_back(frame); - if (frame.getEof()) {//or if we already have a buffers worth - lastEof = frames.size(); - aio->notifyPendingWrite(); - } - QPID_LOG(trace, "SENT " << identifier << ": " << frame); -} - -void Connector::Writer::writeOne(const Mutex::ScopedLock& l) { - assert(buffer); - framesEncoded = 0; - - buffer->dataStart = 0; - buffer->dataCount = encode.getPosition(); - aio->queueWrite(buffer); - newBuffer(l); -} - -void Connector::Writer::newBuffer(const Mutex::ScopedLock&) { - buffer = aio->getQueuedBuffer(); - if (!buffer) buffer = new Buff(maxFrameSize); - encode = framing::Buffer(buffer->bytes, buffer->byteCount); - framesEncoded = 0; -} - -// Called in IO thread. -void Connector::Writer::write(sys::AsynchIO&) { - Mutex::ScopedLock l(lock); - assert(buffer); - size_t bytesWritten(0); - for (size_t i = 0; i < lastEof; ++i) { - AMQFrame& frame = frames[i]; - uint32_t size = frame.size(); - if (size > encode.available()) writeOne(l); - assert(size <= encode.available()); - frame.encode(encode); - ++framesEncoded; - bytesWritten += size; + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i==theProtocolRegistry().end()) { + throw Exception(QPID_MSG("Unknown protocol: " << proto)); } - frames.erase(frames.begin(), frames.begin()+lastEof); - lastEof = 0; - if (bounds) bounds->reduce(bytesWritten); - if (encode.getPosition() > 0) writeOne(l); + return (i->second)(v, s, c); } -void Connector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) { - framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); - - if (!initiated) { - framing::ProtocolInitiation protocolInit; - if (protocolInit.decode(in)) { - //TODO: check the version is correct - QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); - } - initiated = true; - } - AMQFrame frame; - while(frame.decode(in)){ - QPID_LOG(trace, "RECV " << identifier << ": " << frame); - input->received(frame); - } - // TODO: unreading needs to go away, and when we can cope - // with multiple sub-buffers in the general buffer scheme, it will - if (in.available() != 0) { - // Adjust buffer for used bytes and then "unread them" - buff->dataStart += buff->dataCount-in.available(); - buff->dataCount = in.available(); - aio.unread(buff); - } else { - // Give whole buffer back to aio subsystem - aio.queueReadBuffer(buff); +void Connector::registerFactory(const std::string& proto, Factory* connectorFactory) +{ + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i!=theProtocolRegistry().end()) { + QPID_LOG(error, "Tried to register protocol: " << proto << " more than once"); } + theProtocolRegistry()[proto] = connectorFactory; } -void Connector::writebuff(AsynchIO& aio_) { - writer.write(aio_); -} - -void Connector::writeDataBlock(const AMQDataBlock& data) { - AsynchIO::BufferBase* buff = new Buff(maxFrameSize); - framing::Buffer out(buff->bytes, buff->byteCount); - data.encode(out); - buff->dataCount = data.size(); - aio->queueWrite(buff); -} - -void Connector::eof(AsynchIO&) { - handleClosed(); -} - -// TODO: astitcher 20070908 This version of the code can never time out, so the idle processing -// will never be called -void Connector::run(){ - // Keep the connection impl in memory until run() completes. - boost::shared_ptr<ConnectionImpl> protect = impl->shared_from_this(); - assert(protect); - try { - Dispatcher d(poller); - - for (int i = 0; i < 32; i++) { - aio->queueReadBuffer(new Buff(maxFrameSize)); - } - - aio->start(poller); - d.run(); - aio->queueForDeletion(); - socket.close(); - } catch (const std::exception& e) { - QPID_LOG(error, e.what()); - handleClosed(); - } +void Connector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>) +{ } - }} // namespace qpid::client diff --git a/cpp/src/qpid/client/Connector.h b/cpp/src/qpid/client/Connector.h index cde12f7b5b..3a49ae9012 100644 --- a/cpp/src/qpid/client/Connector.h +++ b/cpp/src/qpid/client/Connector.h @@ -42,107 +42,40 @@ namespace qpid { namespace sys { -class Poller; -class AsynchIO; -class AsynchIOBufferBase; +class SecurityLayer; } - + namespace client { -class Bounds; -class ConnectionSettings; +struct ConnectionSettings; class ConnectionImpl; ///@internal -class Connector : public framing::OutputHandler, - private sys::Runnable -{ - struct Buff; - - /** Batch up frames for writing to aio. */ - class Writer : public framing::FrameHandler { - typedef sys::AsynchIOBufferBase BufferBase; - typedef std::vector<framing::AMQFrame> Frames; - - const uint16_t maxFrameSize; - sys::Mutex lock; - sys::AsynchIO* aio; - BufferBase* buffer; - Frames frames; - size_t lastEof; // Position after last EOF in frames - framing::Buffer encode; - size_t framesEncoded; - std::string identifier; - Bounds* bounds; - - void writeOne(const sys::Mutex::ScopedLock&); - void newBuffer(const sys::Mutex::ScopedLock&); - - public: - - Writer(uint16_t maxFrameSize, Bounds*); - ~Writer(); - void init(std::string id, sys::AsynchIO*); - void handle(framing::AMQFrame&); - void write(sys::AsynchIO&); - }; - - const uint16_t maxFrameSize; - framing::ProtocolVersion version; - bool initiated; - - sys::Mutex closedLock; - bool closed; - bool joined; - - sys::AbsTime lastIn; - sys::AbsTime lastOut; - sys::Duration timeout; - sys::Duration idleIn; - sys::Duration idleOut; - - sys::TimeoutHandler* timeoutHandler; - sys::ShutdownHandler* shutdownHandler; - framing::InputHandler* input; - framing::InitiationHandler* initialiser; - framing::OutputHandler* output; - - Writer writer; - - sys::Thread receiver; - - sys::Socket socket; - - sys::AsynchIO* aio; - boost::shared_ptr<sys::Poller> poller; - - void run(); - void handleClosed(); - bool closeInternal(); - - void readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); - void writebuff(qpid::sys::AsynchIO&); - void writeDataBlock(const framing::AMQDataBlock& data); - void eof(qpid::sys::AsynchIO&); - - std::string identifier; - - ConnectionImpl* impl; - +class Connector : public framing::OutputHandler +{ public: - Connector(framing::ProtocolVersion pVersion, - const ConnectionSettings&, - ConnectionImpl*); - virtual ~Connector(); - virtual void connect(const std::string& host, int port); - virtual void init(); - virtual void close(); - virtual void setInputHandler(framing::InputHandler* handler); - virtual void setShutdownHandler(sys::ShutdownHandler* handler); - virtual sys::ShutdownHandler* getShutdownHandler() { return shutdownHandler; } - virtual framing::OutputHandler* getOutputHandler(); - virtual void send(framing::AMQFrame& frame); - const std::string& getIdentifier() const { return identifier; } + // Protocol connector factory related stuff (it might be better to separate this code from the TCP Connector in the future) + typedef Connector* Factory(framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static Connector* create(const std::string& proto, framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static void registerFactory(const std::string& proto, Factory* connectorFactory); + + virtual ~Connector() {}; + virtual void connect(const std::string& host, int port) = 0; + virtual void init() {}; + virtual void close() = 0; + virtual void send(framing::AMQFrame& frame) = 0; + virtual void abort() = 0; + + virtual void setInputHandler(framing::InputHandler* handler) = 0; + virtual void setShutdownHandler(sys::ShutdownHandler* handler) = 0; + virtual sys::ShutdownHandler* getShutdownHandler() const = 0; + virtual framing::OutputHandler* getOutputHandler() = 0; + virtual const std::string& getIdentifier() const = 0; + + virtual void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + virtual unsigned int getSSF() = 0; + }; }} diff --git a/cpp/src/qpid/client/Demux.cpp b/cpp/src/qpid/client/Demux.cpp index b774214ae4..abc23c75df 100644 --- a/cpp/src/qpid/client/Demux.cpp +++ b/cpp/src/qpid/client/Demux.cpp @@ -19,7 +19,7 @@ * */ -#include "Demux.h" +#include "qpid/client/Demux.h" #include "qpid/Exception.h" #include "qpid/framing/MessageTransferBody.h" diff --git a/cpp/src/qpid/client/Demux.h b/cpp/src/qpid/client/Demux.h index 7304e799bb..31dc3f9c06 100644 --- a/cpp/src/qpid/client/Demux.h +++ b/cpp/src/qpid/client/Demux.h @@ -25,6 +25,7 @@ #include "qpid/framing/FrameSet.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/BlockingQueue.h" +#include "qpid/client/ClientImportExport.h" #ifndef _Demux_ #define _Demux_ @@ -49,17 +50,17 @@ public: typedef sys::BlockingQueue<framing::FrameSet::shared_ptr> Queue; typedef boost::shared_ptr<Queue> QueuePtr; - Demux(); - ~Demux(); + QPID_CLIENT_EXTERN Demux(); + QPID_CLIENT_EXTERN ~Demux(); - void handle(framing::FrameSet::shared_ptr); - void close(const sys::ExceptionHolder& ex); - void open(); - - QueuePtr add(const std::string& name, Condition); - void remove(const std::string& name); - QueuePtr get(const std::string& name); - QueuePtr getDefault(); + QPID_CLIENT_EXTERN void handle(framing::FrameSet::shared_ptr); + QPID_CLIENT_EXTERN void close(const sys::ExceptionHolder& ex); + QPID_CLIENT_EXTERN void open(); + + QPID_CLIENT_EXTERN QueuePtr add(const std::string& name, Condition); + QPID_CLIENT_EXTERN void remove(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr get(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr getDefault(); private: struct Record diff --git a/cpp/src/qpid/client/Dispatcher.cpp b/cpp/src/qpid/client/Dispatcher.cpp index 5028d68405..a715c623bf 100644 --- a/cpp/src/qpid/client/Dispatcher.cpp +++ b/cpp/src/qpid/client/Dispatcher.cpp @@ -18,15 +18,25 @@ * under the License. * */ -#include "Dispatcher.h" +#include "qpid/client/Dispatcher.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/SessionImpl.h" #include "qpid/framing/FrameSet.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/log/Statement.h" #include "qpid/sys/BlockingQueue.h" -#include "Message.h" - -#include <boost/state_saver.hpp> +#include "qpid/client/Message.h" +#include "qpid/client/MessageImpl.h" + +#include <boost/version.hpp> +#if (BOOST_VERSION >= 104000) +# include <boost/serialization/state_saver.hpp> + using boost::serialization::state_saver; +#else +# include <boost/state_saver.hpp> + using boost::state_saver; +#endif /* BOOST_VERSION */ using qpid::framing::FrameSet; using qpid::framing::MessageTransferBody; @@ -37,44 +47,40 @@ using qpid::sys::Thread; namespace qpid { namespace client { -Subscriber::Subscriber(const Session& s, MessageListener* l, AckPolicy a) - : session(s), listener(l), autoAck(a) {} - -void Subscriber::received(Message& msg) -{ - if (listener) { - listener->received(msg); - autoAck.ack(msg, session); - } -} - Dispatcher::Dispatcher(const Session& s, const std::string& q) - : session(s), running(false), autoStop(true) + : session(s), + running(false), + autoStop(true), + failoverHandler(0) { - queue = q.empty() ? - session.getExecution().getDemux().getDefault() : - session.getExecution().getDemux().get(q); -} + Demux& demux = SessionBase_0_10Access(session).get()->getDemux(); + queue = q.empty() ? demux.getDefault() : demux.get(q); +} void Dispatcher::start() { worker = Thread(this); } +void Dispatcher::wait() +{ + worker.join(); +} + void Dispatcher::run() { Mutex::ScopedLock l(lock); if (running) throw Exception("Dispatcher is already running."); - boost::state_saver<bool> reset(running); // Reset to false on exit. + state_saver<bool> reset(running); // Reset to false on exit. running = true; try { while (!queue->isClosed()) { Mutex::ScopedUnlock u(lock); FrameSet::shared_ptr content = queue->pop(); if (content->isA<MessageTransferBody>()) { - Message msg(*content); - Subscriber::shared_ptr listener = find(msg.getDestination()); + Message msg(new MessageImpl(*content)); + boost::intrusive_ptr<SubscriptionImpl> listener = find(msg.getDestination()); if (!listener) { QPID_LOG(error, "No listener found for destination " << msg.getDestination()); } else { @@ -91,9 +97,21 @@ void Dispatcher::run() } session.sync(); // Make sure all our acks are received before returning. } - catch (const ClosedException&) {} //ignore it and return + catch (const ClosedException&) { + QPID_LOG(debug, QPID_MSG(session.getId() << ": closed by peer")); + } + catch (const TransportFailure&) { + QPID_LOG(info, QPID_MSG(session.getId() << ": transport failure")); + throw; + } catch (const std::exception& e) { - QPID_LOG(error, "Exception in client dispatch thread: " << e.what()); + if ( failoverHandler ) { + QPID_LOG(debug, QPID_MSG(session.getId() << " failover: " << e.what())); + failoverHandler(); + } else { + QPID_LOG(error, session.getId() << " error: " << e.what()); + throw; + } } } @@ -109,7 +127,7 @@ void Dispatcher::setAutoStop(bool b) autoStop = b; } -Subscriber::shared_ptr Dispatcher::find(const std::string& name) +boost::intrusive_ptr<SubscriptionImpl> Dispatcher::find(const std::string& name) { ScopedLock<Mutex> l(lock); Listeners::iterator i = listeners.find(name); @@ -119,27 +137,14 @@ Subscriber::shared_ptr Dispatcher::find(const std::string& name) return i->second; } -void Dispatcher::listen( - MessageListener* listener, AckPolicy autoAck -) -{ +void Dispatcher::listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription) { ScopedLock<Mutex> l(lock); - defaultListener = Subscriber::shared_ptr( - new Subscriber(session, listener, autoAck)); + listeners[subscription->getName()] = subscription; } -void Dispatcher::listen(const std::string& destination, MessageListener* listener, AckPolicy autoAck) -{ - ScopedLock<Mutex> l(lock); - listeners[destination] = Subscriber::shared_ptr( - new Subscriber(session, listener, autoAck)); -} - -void Dispatcher::cancel(const std::string& destination) -{ +void Dispatcher::cancel(const std::string& destination) { ScopedLock<Mutex> l(lock); - listeners.erase(destination); - if (autoStop && listeners.empty()) + if (listeners.erase(destination) && running && autoStop && listeners.empty()) queue->close(); } diff --git a/cpp/src/qpid/client/Dispatcher.h b/cpp/src/qpid/client/Dispatcher.h index 7d42bf8793..74fdb90103 100644 --- a/cpp/src/qpid/client/Dispatcher.h +++ b/cpp/src/qpid/client/Dispatcher.h @@ -26,28 +26,18 @@ #include <string> #include <boost/shared_ptr.hpp> #include "qpid/client/Session.h" +#include "qpid/client/SessionBase_0_10Access.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/Thread.h" -#include "MessageListener.h" -#include "AckPolicy.h" +#include "qpid/client/ClientImportExport.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/SubscriptionImpl.h" namespace qpid { namespace client { -///@internal -class Subscriber : public MessageListener -{ - AsyncSession session; - MessageListener* const listener; - AckPolicy autoAck; - -public: - typedef boost::shared_ptr<Subscriber> shared_ptr; - Subscriber(const Session& session, MessageListener* listener, AckPolicy); - void received(Message& msg); - -}; +class SubscriptionImpl; ///@internal typedef framing::Handler<framing::FrameSet> FrameSetHandler; @@ -55,7 +45,7 @@ typedef framing::Handler<framing::FrameSet> FrameSetHandler; ///@internal class Dispatcher : public sys::Runnable { - typedef std::map<std::string, Subscriber::shared_ptr> Listeners; + typedef std::map<std::string, boost::intrusive_ptr<SubscriptionImpl> >Listeners; sys::Mutex lock; sys::Thread worker; Session session; @@ -63,22 +53,32 @@ class Dispatcher : public sys::Runnable bool running; bool autoStop; Listeners listeners; - Subscriber::shared_ptr defaultListener; + boost::intrusive_ptr<SubscriptionImpl> defaultListener; std::auto_ptr<FrameSetHandler> handler; - Subscriber::shared_ptr find(const std::string& name); + boost::intrusive_ptr<SubscriptionImpl> find(const std::string& name); bool isStopped(); + boost::function<void ()> failoverHandler; + public: Dispatcher(const Session& session, const std::string& queue = ""); + ~Dispatcher() {} void start(); - void run(); + void wait(); + // As this class is marked 'internal', no extern should be made here; + // however, some test programs rely on it. + QPID_CLIENT_EXTERN void run(); void stop(); void setAutoStop(bool b); - void listen(MessageListener* listener, AckPolicy autoAck=AckPolicy()); - void listen(const std::string& destination, MessageListener* listener, AckPolicy autoAck=AckPolicy()); + void registerFailoverHandler ( boost::function<void ()> fh ) + { + failoverHandler = fh; + } + + void listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription); void cancel(const std::string& destination); }; diff --git a/cpp/src/qpid/client/Execution.h b/cpp/src/qpid/client/Execution.h index 10674afde0..ad622af9c1 100644 --- a/cpp/src/qpid/client/Execution.h +++ b/cpp/src/qpid/client/Execution.h @@ -22,7 +22,7 @@ #define _Execution_ #include "qpid/framing/SequenceNumber.h" -#include "Demux.h" +#include "qpid/client/Demux.h" namespace qpid { namespace client { diff --git a/cpp/src/qpid/client/FailoverListener.cpp b/cpp/src/qpid/client/FailoverListener.cpp new file mode 100644 index 0000000000..3396f5598c --- /dev/null +++ b/cpp/src/qpid/client/FailoverListener.cpp @@ -0,0 +1,89 @@ +/* + * + * 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. + * + */ +#include "qpid/client/FailoverListener.h" +#include "qpid/client/Session.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/log/Helpers.h" + +namespace qpid { +namespace client { + +const std::string FailoverListener::AMQ_FAILOVER("amq.failover"); + +FailoverListener::FailoverListener(Connection c) : + connection(c), + session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())), + subscriptions(session) +{ + knownBrokers = c.getInitialBrokers(); + if (session.exchangeQuery(arg::name=AMQ_FAILOVER).getNotFound()) { + session.close(); + return; + } + std::string qname=session.getId().getName(); + session.queueDeclare(arg::queue=qname, arg::exclusive=true, arg::autoDelete=true); + session.exchangeBind(arg::queue=qname, arg::exchange=AMQ_FAILOVER); + subscriptions.subscribe(*this, qname, SubscriptionSettings(FlowControl::unlimited(), + ACCEPT_MODE_NONE)); + thread = sys::Thread(*this); +} + +void FailoverListener::run() { + try { + subscriptions.run(); + } catch(...) {} +} + +FailoverListener::~FailoverListener() { + try { + subscriptions.stop(); + thread.join(); + if (connection.isOpen()) { + session.sync(); + session.close(); + } + } catch (...) {} +} + +void FailoverListener::received(Message& msg) { + sys::Mutex::ScopedLock l(lock); + knownBrokers = getKnownBrokers(msg); +} + +std::vector<Url> FailoverListener::getKnownBrokers() const { + sys::Mutex::ScopedLock l(lock); + return knownBrokers; +} + +std::vector<Url> FailoverListener::getKnownBrokers(const Message& msg) { + std::vector<Url> knownBrokers; + framing::Array urlArray; + msg.getHeaders().getArray("amq.failover", urlArray); + for (framing::Array::ValueVector::const_iterator i = urlArray.begin(); + i != urlArray.end(); + ++i ) + knownBrokers.push_back(Url((*i)->get<std::string>())); + return knownBrokers; +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/FailoverManager.cpp b/cpp/src/qpid/client/FailoverManager.cpp new file mode 100644 index 0000000000..81f71eb7df --- /dev/null +++ b/cpp/src/qpid/client/FailoverManager.cpp @@ -0,0 +1,131 @@ +/* + * + * 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. + * + */ +#include "qpid/client/FailoverManager.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" + + +namespace qpid { +namespace client { + +using qpid::sys::Monitor; +using qpid::sys::AbsTime; +using qpid::sys::Duration; + +FailoverManager::FailoverManager(const ConnectionSettings& s, + ReconnectionStrategy* rs) : settings(s), strategy(rs), state(IDLE) {} + +void FailoverManager::execute(Command& c) +{ + bool retry = false; + bool completed = false; + AbsTime failed; + while (!completed) { + try { + AsyncSession session = connect().newSession(); + if (retry) { + Duration failoverTime(failed, AbsTime::now()); + QPID_LOG(info, "Failed over for " << &c << " in " << (failoverTime/qpid::sys::TIME_MSEC) << " milliseconds"); + } + c.execute(session, retry); + session.sync();//TODO: shouldn't be required + session.close(); + completed = true; + } catch(const TransportFailure&) { + retry = true; + failed = AbsTime::now(); + } + } +} + +void FailoverManager::close() +{ + Monitor::ScopedLock l(lock); + connection.close(); +} + +Connection& FailoverManager::connect(std::vector<Url> brokers) +{ + Monitor::ScopedLock l(lock); + if (state == CANT_CONNECT) { + state = IDLE;//retry + } + while (!connection.isOpen()) { + if (state == CONNECTING) { + lock.wait(); + } else if (state == CANT_CONNECT) { + throw CannotConnectException("Cannot establish a connection"); + } else { + state = CONNECTING; + Connection c; + if (brokers.empty() && failoverListener.get()) + brokers = failoverListener->getKnownBrokers(); + attempt(c, settings, brokers); + if (c.isOpen()) state = IDLE; + else state = CANT_CONNECT; + connection = c; + lock.notifyAll(); + } + } + return connection; +} + +Connection& FailoverManager::getConnection() +{ + Monitor::ScopedLock l(lock); + return connection; +} + +void FailoverManager::attempt(Connection& c, ConnectionSettings s, std::vector<Url> urls) +{ + Monitor::ScopedUnlock u(lock); + if (strategy) strategy->editUrlList(urls); + if (urls.empty()) { + attempt(c, s); + } else { + for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end() && !c.isOpen(); ++i) { + for (Url::const_iterator j = i->begin(); j != i->end() && !c.isOpen(); ++j) { + const TcpAddress* tcp = j->get<TcpAddress>(); + if (tcp) { + s.host = tcp->host; + s.port = tcp->port; + attempt(c, s); + } + } + } + } +} + +void FailoverManager::attempt(Connection& c, ConnectionSettings s) +{ + try { + QPID_LOG(info, "Attempting to connect to " << s.host << " on " << s.port << "..."); + c.open(s); + failoverListener.reset(new FailoverListener(c)); + QPID_LOG(info, "Connected to " << s.host << " on " << s.port); + } catch (const Exception& e) { + QPID_LOG(info, "Could not connect to " << s.host << " on " << s.port << ": " << e.what()); + } +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/FlowControl.h b/cpp/src/qpid/client/FlowControl.h deleted file mode 100644 index a4ed9879f4..0000000000 --- a/cpp/src/qpid/client/FlowControl.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef QPID_CLIENT_FLOWCONTROL_H -#define QPID_CLIENT_FLOWCONTROL_H - -/* - * - * 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. - * - */ - -namespace qpid { -namespace client { - -/** - * Flow control works by associating a finite amount of "credit" - * associated with a subscription. - * - * Credit includes a message count and a byte count. Each message - * received decreases the message count by one, and the byte count by - * the size of the message. Either count can have the special value - * UNLIMITED which is never decreased. - * - * A subscription's credit is exhausted when the message count is 0 or - * the byte count is too small for the next available message. The - * subscription will not receive any further messages until is credit - * is renewed. - * - * In "window mode" credit is automatically renewed when a message is - * acknowledged (@see AckPolicy) In non-window mode credit is not - * automatically renewed, it must be explicitly re-set (@see - * SubscriptionManager) - */ -struct FlowControl { - static const uint32_t UNLIMITED=0xFFFFFFFF; - FlowControl(uint32_t messages_=0, uint32_t bytes_=0, bool window_=false) - : messages(messages_), bytes(bytes_), window(window_) {} - - static FlowControl messageCredit(uint32_t messages_) { return FlowControl(messages_,UNLIMITED,false); } - static FlowControl messageWindow(uint32_t messages_) { return FlowControl(messages_,UNLIMITED,true); } - static FlowControl byteCredit(uint32_t bytes_) { return FlowControl(UNLIMITED,bytes_,false); } - static FlowControl byteWindow(uint32_t bytes_) { return FlowControl(UNLIMITED,bytes_,true); } - static FlowControl unlimited() { return FlowControl(UNLIMITED, UNLIMITED, false); } - static FlowControl zero() { return FlowControl(0, 0, false); } - - /** Message credit: subscription can accept up to this many messages. */ - uint32_t messages; - /** Byte credit: subscription can accept up to this many bytes of message content. */ - uint32_t bytes; - /** Window mode. If true credit is automatically renewed as messages are acknowledged. */ - bool window; - - bool operator==(const FlowControl& x) { - return messages == x.messages && bytes == x.bytes && window == x.window; - }; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_FLOWCONTROL_H*/ diff --git a/cpp/src/qpid/client/Future.cpp b/cpp/src/qpid/client/Future.cpp index 6a0c78ae4b..740cd3df59 100644 --- a/cpp/src/qpid/client/Future.cpp +++ b/cpp/src/qpid/client/Future.cpp @@ -19,7 +19,8 @@ * */ -#include "Future.h" +#include "qpid/client/Future.h" +#include "qpid/client/SessionImpl.h" namespace qpid { namespace client { diff --git a/cpp/src/qpid/client/Future.h b/cpp/src/qpid/client/Future.h deleted file mode 100644 index 67f39cdf3f..0000000000 --- a/cpp/src/qpid/client/Future.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef _Future_ -#define _Future_ - -#include <boost/bind.hpp> -#include <boost/shared_ptr.hpp> -#include "qpid/Exception.h" -#include "qpid/framing/SequenceNumber.h" -#include "qpid/framing/StructHelper.h" -#include "FutureCompletion.h" -#include "FutureResult.h" -#include "SessionImpl.h" - -namespace qpid { -namespace client { - -/**@internal */ -class Future : private framing::StructHelper -{ - framing::SequenceNumber command; - boost::shared_ptr<FutureResult> result; - bool complete; - -public: - Future() : complete(false) {} - Future(const framing::SequenceNumber& id) : command(id), complete(false) {} - - template <class T> void decodeResult(T& value, SessionImpl& session) - { - if (result) { - decode(value, result->getResult(session)); - } else { - throw Exception("Result not expected"); - } - } - - void wait(SessionImpl& session); - bool isComplete(SessionImpl& session); - void setFutureResult(boost::shared_ptr<FutureResult> r); -}; - -}} - -#endif diff --git a/cpp/src/qpid/client/FutureCompletion.cpp b/cpp/src/qpid/client/FutureCompletion.cpp index 130c65d6aa..ccfb073855 100644 --- a/cpp/src/qpid/client/FutureCompletion.cpp +++ b/cpp/src/qpid/client/FutureCompletion.cpp @@ -19,7 +19,7 @@ * */ -#include "FutureCompletion.h" +#include "qpid/client/FutureCompletion.h" using namespace qpid::client; using namespace qpid::sys; diff --git a/cpp/src/qpid/client/FutureResult.cpp b/cpp/src/qpid/client/FutureResult.cpp index 007f278051..0237eb1464 100644 --- a/cpp/src/qpid/client/FutureResult.cpp +++ b/cpp/src/qpid/client/FutureResult.cpp @@ -19,9 +19,9 @@ * */ -#include "FutureResult.h" +#include "qpid/client/FutureResult.h" -#include "SessionImpl.h" +#include "qpid/client/SessionImpl.h" using namespace qpid::client; using namespace qpid::framing; diff --git a/cpp/src/qpid/client/LoadPlugins.cpp b/cpp/src/qpid/client/LoadPlugins.cpp new file mode 100644 index 0000000000..82cdedc7fe --- /dev/null +++ b/cpp/src/qpid/client/LoadPlugins.cpp @@ -0,0 +1,52 @@ +/* + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "qpid/Modules.h" +#include "qpid/sys/Shlib.h" +#include <string> +#include <vector> +using std::vector; +using std::string; + +namespace { + +struct LoadtimeInitialise { + LoadtimeInitialise() { + qpid::ModuleOptions moduleOptions(QPIDC_MODULE_DIR); + string defaultPath (moduleOptions.loadDir); + moduleOptions.parse (0, 0, QPIDC_CONF_FILE, true); + + for (vector<string>::iterator iter = moduleOptions.load.begin(); + iter != moduleOptions.load.end(); + iter++) + qpid::tryShlib (iter->data(), false); + + if (!moduleOptions.noLoad) { + bool isDefault = defaultPath == moduleOptions.loadDir; + qpid::loadModuleDir (moduleOptions.loadDir, isDefault); + } + } +} init; + +} // namespace diff --git a/cpp/src/qpid/client/LocalQueue.cpp b/cpp/src/qpid/client/LocalQueue.cpp index 99ab6f0133..0019adabaf 100644 --- a/cpp/src/qpid/client/LocalQueue.cpp +++ b/cpp/src/qpid/client/LocalQueue.cpp @@ -18,59 +18,35 @@ * under the License. * */ -#include "LocalQueue.h" +#include "qpid/client/LocalQueue.h" +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/MessageImpl.h" #include "qpid/Exception.h" #include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/SubscriptionImpl.h" namespace qpid { namespace client { using namespace framing; -LocalQueue::LocalQueue(AckPolicy a) : autoAck(a) {} -LocalQueue::~LocalQueue() {} +typedef PrivateImplRef<LocalQueue> PI; -Message LocalQueue::pop() { return get(); } +LocalQueue::LocalQueue() { PI::ctor(*this, new LocalQueueImpl()); } +LocalQueue::LocalQueue(const LocalQueue& x) : Handle<LocalQueueImpl>() { PI::copy(*this, x); } +LocalQueue::~LocalQueue() { PI::dtor(*this); } +LocalQueue& LocalQueue::operator=(const LocalQueue& x) { return PI::assign(*this, x); } -Message LocalQueue::get() { - Message result; - bool ok = get(result, sys::TIME_INFINITE); - assert(ok); (void) ok; - return result; -} +Message LocalQueue::pop(sys::Duration timeout) { return impl->pop(timeout); } -bool LocalQueue::get(Message& result, sys::Duration timeout) { - if (!queue) - throw ClosedException(); - FrameSet::shared_ptr content; - bool ok = queue->pop(content, timeout); - if (!ok) return false; - if (content->isA<MessageTransferBody>()) { - result = Message(*content); - autoAck.ack(result, session); - return true; - } - else - throw CommandInvalidException( - QPID_MSG("Unexpected method: " << content->getMethod())); -} +Message LocalQueue::get(sys::Duration timeout) { return impl->get(timeout); } -void LocalQueue::setAckPolicy(AckPolicy a) { autoAck=a; } -AckPolicy& LocalQueue::getAckPolicy() { return autoAck; } +bool LocalQueue::get(Message& result, sys::Duration timeout) { return impl->get(result, timeout); } -bool LocalQueue::empty() const -{ - if (!queue) - throw ClosedException(); - return queue->empty(); -} - -size_t LocalQueue::size() const -{ - if (!queue) - throw ClosedException(); - return queue->size(); -} +bool LocalQueue::empty() const { return impl->empty(); } +size_t LocalQueue::size() const { return impl->size(); } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/LocalQueue.h b/cpp/src/qpid/client/LocalQueue.h deleted file mode 100644 index f81065ef3c..0000000000 --- a/cpp/src/qpid/client/LocalQueue.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef QPID_CLIENT_LOCALQUEUE_H -#define QPID_CLIENT_LOCALQUEUE_H - -/* - * - * 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. - * - */ - -#include "qpid/client/Message.h" -#include "qpid/client/Demux.h" -#include "qpid/client/AckPolicy.h" -#include "qpid/sys/Time.h" - -namespace qpid { -namespace client { - -/** - * A local queue to collect messages retrieved from a remote broker - * queue. Create a queue and subscribe it using the SubscriptionManager. - * Messages from the remote queue on the broker will be stored in the - * local queue until you retrieve them. - * - * \ingroup clientapi - */ -class LocalQueue -{ - public: - /** Create a local queue. Subscribe the local queue to a remote broker - * queue with a SubscriptionManager. - * - * LocalQueue is an alternative to implementing a MessageListener. - * - *@param ackPolicy Policy for acknowledging messages. @see AckPolicy. - */ - LocalQueue(AckPolicy ackPolicy=AckPolicy()); - - ~LocalQueue(); - - /** Wait up to timeout for the next message from the local queue. - *@param result Set to the message from the queue. - *@param timeout wait up this timeout for a message to appear. - *@return true if result was set, false if queue was empty after timeout. - */ - bool get(Message& result, sys::Duration timeout=0); - - /** Get the next message off the local queue, or wait for a - * message from the broker queue. - *@exception ClosedException if subscription has been closed. - */ - Message get(); - - /** Synonym for get(). */ - Message pop(); - - /** Return true if local queue is empty. */ - bool empty() const; - - /** Number of messages on the local queue */ - size_t size() const; - - /** Set the message acknowledgement policy. @see AckPolicy. */ - void setAckPolicy(AckPolicy); - - /** Get the message acknowledgement policy. @see AckPolicy. */ - AckPolicy& getAckPolicy(); - - private: - Session session; - Demux::QueuePtr queue; - AckPolicy autoAck; - - friend class SubscriptionManager; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_LOCALQUEUE_H*/ diff --git a/cpp/src/qpid/client/LocalQueueImpl.cpp b/cpp/src/qpid/client/LocalQueueImpl.cpp new file mode 100644 index 0000000000..8b191728f4 --- /dev/null +++ b/cpp/src/qpid/client/LocalQueueImpl.cpp @@ -0,0 +1,78 @@ +/* + * + * 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. + * + */ +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/Exception.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/CompletionImpl.h" + +namespace qpid { +namespace client { + +using namespace framing; + +Message LocalQueueImpl::pop(sys::Duration timeout) { return get(timeout); } + +Message LocalQueueImpl::get(sys::Duration timeout) { + Message result; + bool ok = get(result, timeout); + if (!ok) throw Exception("Timed out waiting for a message"); + return result; +} + +bool LocalQueueImpl::get(Message& result, sys::Duration timeout) { + if (!queue) + throw ClosedException(); + FrameSet::shared_ptr content; + bool ok = queue->pop(content, timeout); + if (!ok) return false; + if (content->isA<MessageTransferBody>()) { + + *MessageImpl::get(result) = MessageImpl(*content); + boost::intrusive_ptr<SubscriptionImpl> si = PrivateImplRef<Subscription>::get(subscription); + assert(si); + if (si) si->received(result); + return true; + } + else + throw CommandInvalidException( + QPID_MSG("Unexpected method: " << content->getMethod())); +} + +bool LocalQueueImpl::empty() const +{ + if (!queue) + throw ClosedException(); + return queue->empty(); +} + +size_t LocalQueueImpl::size() const +{ + if (!queue) + throw ClosedException(); + return queue->size(); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/LocalQueueImpl.h b/cpp/src/qpid/client/LocalQueueImpl.h new file mode 100644 index 0000000000..75b62cf203 --- /dev/null +++ b/cpp/src/qpid/client/LocalQueueImpl.h @@ -0,0 +1,108 @@ +#ifndef QPID_CLIENT_LOCALQUEUEIMPL_H +#define QPID_CLIENT_LOCALQUEUEIMPL_H + +/* + * + * 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. + * + */ + +#include "qpid/client/ClientImportExport.h" +#include "qpid/client/Handle.h" +#include "qpid/client/Message.h" +#include "qpid/client/Subscription.h" +#include "qpid/client/Demux.h" +#include "qpid/sys/Time.h" +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { + +/** + * A local queue to collect messages retrieved from a remote broker + * queue. Create a queue and subscribe it using the SubscriptionManager. + * Messages from the remote queue on the broker will be stored in the + * local queue until you retrieve them. + * + * \ingroup clientapi + * + * \details Using a Local Queue + * + * <pre> + * LocalQueue local_queue; + * subscriptions.subscribe(local_queue, string("message_queue")); + * for (int i=0; i<10; i++) { + * Message message = local_queue.get(); + * std::cout << message.getData() << std::endl; + * } + * </pre> + * + * <h2>Getting Messages</h2> + * + * <ul><li> + * <p>get()</p> + * <pre>Message message = local_queue.get();</pre> + * <pre>// Specifying timeouts (TIME_SEC, TIME_MSEC, TIME_USEC, TIME_NSEC) + *#include <qpid/sys/Time.h> + *Message message; + *local_queue.get(message, 5*sys::TIME_SEC);</pre></li></ul> + * + * <h2>Checking size</h2> + * <ul><li> + * <p>empty()</p> + * <pre>if (local_queue.empty()) { ... }</pre></li> + * <li><p>size()</p> + * <pre>std::cout << local_queue.size();</pre></li> + * </ul> + */ + +class LocalQueueImpl : public RefCounted { + public: + /** Wait up to timeout for the next message from the local queue. + *@param result Set to the message from the queue. + *@param timeout wait up this timeout for a message to appear. + *@return true if result was set, false if queue was empty after timeout. + */ + bool get(Message& result, sys::Duration timeout=0); + + /** Get the next message off the local queue, or wait up to the timeout + * for message from the broker queue. + *@param timeout wait up this timeout for a message to appear. + *@return message from the queue. + *@throw ClosedException if subscription is closed or timeout exceeded. + */ + Message get(sys::Duration timeout=sys::TIME_INFINITE); + + /** Synonym for get() */ + Message pop(sys::Duration timeout=sys::TIME_INFINITE); + + /** Return true if local queue is empty. */ + bool empty() const; + + /** Number of messages on the local queue */ + size_t size() const; + + private: + Demux::QueuePtr queue; + Subscription subscription; + friend class SubscriptionManagerImpl; +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_LOCALQUEUEIMPL_H*/ diff --git a/cpp/src/qpid/client/Message.cpp b/cpp/src/qpid/client/Message.cpp index d5464594ee..00f911c57e 100644 --- a/cpp/src/qpid/client/Message.cpp +++ b/cpp/src/qpid/client/Message.cpp @@ -19,55 +19,44 @@ * */ -#include "Message.h" +#include "qpid/client/Message.h" +#include "qpid/client/MessageImpl.h" namespace qpid { namespace client { -Message::Message(const std::string& data_, - const std::string& routingKey, - const std::string& exchange) : TransferContent(data_, routingKey, exchange) {} +Message::Message(MessageImpl* mi) : impl(mi) {} -std::string Message::getDestination() const -{ - return method.getDestination(); -} +Message::Message(const std::string& data, const std::string& routingKey) + : impl(new MessageImpl(data, routingKey)) {} -bool Message::isRedelivered() const -{ - return hasDeliveryProperties() && getDeliveryProperties().getRedelivered(); -} +Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {} -void Message::setRedelivered(bool redelivered) -{ - getDeliveryProperties().setRedelivered(redelivered); -} +Message::~Message() { delete impl; } -framing::FieldTable& Message::getHeaders() -{ - return getMessageProperties().getApplicationHeaders(); -} +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } -const framing::FieldTable& Message::getHeaders() const -{ - return getMessageProperties().getApplicationHeaders(); -} +void Message::swap(Message& m) { std::swap(impl, m.impl); } -const framing::MessageTransferBody& Message::getMethod() const -{ - return method; -} +std::string Message::getDestination() const { return impl->getDestination(); } +bool Message::isRedelivered() const { return impl->isRedelivered(); } +void Message::setRedelivered(bool redelivered) { impl->setRedelivered(redelivered); } +framing::FieldTable& Message::getHeaders() { return impl->getHeaders(); } +const framing::FieldTable& Message::getHeaders() const { return impl->getHeaders(); } +const framing::SequenceNumber& Message::getId() const { return impl->getId(); } -const framing::SequenceNumber& Message::getId() const -{ - return id; -} +void Message::setData(const std::string& s) { impl->setData(s); } +const std::string& Message::getData() const { return impl->getData(); } +std::string& Message::getData() { return impl->getData(); } -/**@internal for incoming messages */ -Message::Message(const framing::FrameSet& frameset) : - method(*frameset.as<framing::MessageTransferBody>()), id(frameset.getId()) -{ - populate(frameset); -} +void Message::appendData(const std::string& s) { impl->appendData(s); } + +bool Message::hasMessageProperties() const { return impl->hasMessageProperties(); } +framing::MessageProperties& Message::getMessageProperties() { return impl->getMessageProperties(); } +const framing::MessageProperties& Message::getMessageProperties() const { return impl->getMessageProperties(); } + +bool Message::hasDeliveryProperties() const { return impl->hasDeliveryProperties(); } +framing::DeliveryProperties& Message::getDeliveryProperties() { return impl->getDeliveryProperties(); } +const framing::DeliveryProperties& Message::getDeliveryProperties() const { return impl->getDeliveryProperties(); } }} diff --git a/cpp/src/qpid/client/MessageImpl.cpp b/cpp/src/qpid/client/MessageImpl.cpp new file mode 100644 index 0000000000..865c462b15 --- /dev/null +++ b/cpp/src/qpid/client/MessageImpl.cpp @@ -0,0 +1,71 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/MessageImpl.h" + +namespace qpid { +namespace client { + +MessageImpl::MessageImpl(const std::string& data, const std::string& routingKey) : TransferContent(data, routingKey) {} + +std::string MessageImpl::getDestination() const +{ + return method.getDestination(); +} + +bool MessageImpl::isRedelivered() const +{ + return hasDeliveryProperties() && getDeliveryProperties().getRedelivered(); +} + +void MessageImpl::setRedelivered(bool redelivered) +{ + getDeliveryProperties().setRedelivered(redelivered); +} + +framing::FieldTable& MessageImpl::getHeaders() +{ + return getMessageProperties().getApplicationHeaders(); +} + +const framing::FieldTable& MessageImpl::getHeaders() const +{ + return getMessageProperties().getApplicationHeaders(); +} + +const framing::MessageTransferBody& MessageImpl::getMethod() const +{ + return method; +} + +const framing::SequenceNumber& MessageImpl::getId() const +{ + return id; +} + +/**@internal for incoming messages */ +MessageImpl::MessageImpl(const framing::FrameSet& frameset) : + method(*frameset.as<framing::MessageTransferBody>()), id(frameset.getId()) +{ + populate(frameset); +} + +}} diff --git a/cpp/src/qpid/client/Message.h b/cpp/src/qpid/client/MessageImpl.h index 4e6ed49bb4..a64ddd20d8 100644 --- a/cpp/src/qpid/client/Message.h +++ b/cpp/src/qpid/client/MessageImpl.h @@ -1,5 +1,5 @@ -#ifndef _client_Message_h -#define _client_Message_h +#ifndef QPID_CLIENT_MESSAGEIMPL_H +#define QPID_CLIENT_MESSAGEIMPL_H /* * @@ -21,30 +21,24 @@ * under the License. * */ -#include <string> +#include "qpid/client/Message.h" #include "qpid/client/Session.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/TransferContent.h" +#include <string> namespace qpid { namespace client { -/** - * A message sent to or received from the broker. - * - * \ingroup clientapi - */ -class Message : public framing::TransferContent +class MessageImpl : public framing::TransferContent { public: /** Create a Message. *@param data Data for the message body. *@param routingKey Passed to the exchange that routes the message. - *@param exchange Name of the exchange that should route the message. */ - Message(const std::string& data=std::string(), - const std::string& routingKey=std::string(), - const std::string& exchange=std::string()); + MessageImpl(const std::string& data=std::string(), + const std::string& routingKey=std::string()); /** The destination of messages sent to the broker is the exchange * name. The destination of messages received from the broker is @@ -70,7 +64,10 @@ public: const framing::SequenceNumber& getId() const; /**@internal for incoming messages */ - Message(const framing::FrameSet& frameset); + MessageImpl(const framing::FrameSet& frameset); + + static MessageImpl* get(Message& m) { return m.impl; } + static const MessageImpl* get(const Message& m) { return m.impl; } private: //method and id are only set for received messages: @@ -80,4 +77,4 @@ private: }} -#endif /*!_client_Message_h*/ +#endif /*!QPID_CLIENT_MESSAGEIMPL_H*/ diff --git a/cpp/src/qpid/client/MessageListener.cpp b/cpp/src/qpid/client/MessageListener.cpp index 68ebedeb0d..0f2a71287c 100644 --- a/cpp/src/qpid/client/MessageListener.cpp +++ b/cpp/src/qpid/client/MessageListener.cpp @@ -19,6 +19,6 @@ * */ -#include "MessageListener.h" +#include "qpid/client/MessageListener.h" qpid::client::MessageListener::~MessageListener() {} diff --git a/cpp/src/qpid/client/MessageReplayTracker.cpp b/cpp/src/qpid/client/MessageReplayTracker.cpp new file mode 100644 index 0000000000..079fb1167a --- /dev/null +++ b/cpp/src/qpid/client/MessageReplayTracker.cpp @@ -0,0 +1,78 @@ +/* + * + * 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. + * + */ +#include "qpid/client/MessageReplayTracker.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace client { + +MessageReplayTracker::MessageReplayTracker(uint f) : flushInterval(f), count(0) {} + +void MessageReplayTracker::send(const Message& message, const std::string& destination) +{ + buffer.push_back(ReplayRecord(message, destination)); + buffer.back().send(*this); + if (flushInterval && ++count >= flushInterval) { + checkCompletion(); + if (!buffer.empty()) session.flush(); + } +} +void MessageReplayTracker::init(AsyncSession s) +{ + session = s; +} + +void MessageReplayTracker::replay(AsyncSession s) +{ + session = s; + std::for_each(buffer.begin(), buffer.end(), boost::bind(&ReplayRecord::send, _1, boost::ref(*this))); + session.flush(); + count = 0; +} + +void MessageReplayTracker::setFlushInterval(uint f) +{ + flushInterval = f; +} + +uint MessageReplayTracker::getFlushInterval() +{ + return flushInterval; +} + +void MessageReplayTracker::checkCompletion() +{ + buffer.remove_if(boost::bind(&ReplayRecord::isComplete, _1)); +} + +MessageReplayTracker::ReplayRecord::ReplayRecord(const Message& m, const std::string& d) : message(m), destination(d) {} + +void MessageReplayTracker::ReplayRecord::send(MessageReplayTracker& tracker) +{ + status = tracker.session.messageTransfer(arg::destination=destination, arg::content=message); +} + +bool MessageReplayTracker::ReplayRecord::isComplete() +{ + return status.isComplete(); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/PrivateImplRef.h b/cpp/src/qpid/client/PrivateImplRef.h new file mode 100644 index 0000000000..503a383c31 --- /dev/null +++ b/cpp/src/qpid/client/PrivateImplRef.h @@ -0,0 +1,94 @@ +#ifndef QPID_CLIENT_PRIVATEIMPL_H +#define QPID_CLIENT_PRIVATEIMPL_H + +/* + * + * 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. + * + */ + +#include "qpid/client/ClientImportExport.h" +#include <boost/intrusive_ptr.hpp> +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { + +// FIXME aconway 2009-04-24: details! +/** @file + * + * Helper class to implement a class with a private, reference counted + * implementation and reference semantics. + * + * Such classes are used in the public API to hide implementation, they + * should. Example of use: + * + * === Foo.h + * + * template <class T> PrivateImplRef; + * class FooImpl; + * + * Foo : public Handle<FooImpl> { + * public: + * Foo(FooImpl* = 0); + * Foo(const Foo&); + * ~Foo(); + * Foo& operator=(const Foo&); + * + * int fooDo(); // and other Foo functions... + * + * private: + * typedef FooImpl Impl; + * Impl* impl; + * friend class PrivateImplRef<Foo>; + * + * === Foo.cpp + * + * typedef PrivateImplRef<Foo> PI; + * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); } + * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); } + * Foo::~Foo() { PI::dtor(*this); } + * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); } + * + * int foo::fooDo() { return impl->fooDo(); } + * + */ +template <class T> class PrivateImplRef { + public: + typedef typename T::Impl Impl; + typedef boost::intrusive_ptr<Impl> intrusive_ptr; + + static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); } + + static void set(T& t, const intrusive_ptr& p) { + if (t.impl == p) return; + if (t.impl) boost::intrusive_ptr_release(t.impl); + t.impl = p.get(); + if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + } + + // Helper functions to implement the ctor, dtor, copy, assign + static void ctor(T& t, Impl* p) { t.impl = p; if (p) boost::intrusive_ptr_add_ref(p); } + static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); } + static void dtor(T& t) { if(t.impl) boost::intrusive_ptr_release(t.impl); } + static T& assign(T& t, const T& x) { set(t, get(x)); return t;} +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_PRIVATEIMPL_H*/ diff --git a/cpp/src/qpid/client/QueueOptions.cpp b/cpp/src/qpid/client/QueueOptions.cpp new file mode 100644 index 0000000000..f4c1483859 --- /dev/null +++ b/cpp/src/qpid/client/QueueOptions.cpp @@ -0,0 +1,123 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/QueueOptions.h" + +namespace qpid { +namespace client { + +enum QueueEventGeneration {ENQUEUE_ONLY=1, ENQUEUE_AND_DEQUEUE=2}; + + +QueueOptions::QueueOptions() +{} + +const std::string QueueOptions::strMaxCountKey("qpid.max_count"); +const std::string QueueOptions::strMaxSizeKey("qpid.max_size"); +const std::string QueueOptions::strTypeKey("qpid.policy_type"); +const std::string QueueOptions::strREJECT("reject"); +const std::string QueueOptions::strFLOW_TO_DISK("flow_to_disk"); +const std::string QueueOptions::strRING("ring"); +const std::string QueueOptions::strRING_STRICT("ring_strict"); +const std::string QueueOptions::strLastValueQueue("qpid.last_value_queue"); +const std::string QueueOptions::strPersistLastNode("qpid.persist_last_node"); +const std::string QueueOptions::strLVQMatchProperty("qpid.LVQ_key"); +const std::string QueueOptions::strLastValueQueueNoBrowse("qpid.last_value_queue_no_browse"); +const std::string QueueOptions::strQueueEventMode("qpid.queue_event_generation"); + + +QueueOptions::~QueueOptions() +{} + +void QueueOptions::setSizePolicy(QueueSizePolicy sp, uint64_t maxSize, uint32_t maxCount) +{ + if (maxCount) setInt(strMaxCountKey, maxCount); + if (maxSize) setInt(strMaxSizeKey, maxSize); + if (maxSize || maxCount){ + switch (sp) + { + case REJECT: + setString(strTypeKey, strREJECT); + break; + case FLOW_TO_DISK: + setString(strTypeKey, strFLOW_TO_DISK); + break; + case RING: + setString(strTypeKey, strRING); + break; + case RING_STRICT: + setString(strTypeKey, strRING_STRICT); + break; + case NONE: + clearSizePolicy(); + break; + } + } +} + + +void QueueOptions::setPersistLastNode() +{ + setInt(strPersistLastNode, 1); +} + +void QueueOptions::setOrdering(QueueOrderingPolicy op) +{ + if (op == LVQ){ + setInt(strLastValueQueue, 1); + }else if (op == LVQ_NO_BROWSE){ + setInt(strLastValueQueueNoBrowse, 1); + }else { + clearOrdering(); + } +} + +void QueueOptions::getLVQKey(std::string& key) +{ + key.assign(strLVQMatchProperty); +} + +void QueueOptions::clearSizePolicy() +{ + erase(strMaxCountKey); + erase(strMaxSizeKey); + erase(strTypeKey); +} + +void QueueOptions::clearPersistLastNode() +{ + erase(strPersistLastNode); +} + +void QueueOptions::clearOrdering() +{ + erase(strLastValueQueue); +} + +void QueueOptions::enableQueueEvents(bool enqueueOnly) +{ + setInt(strQueueEventMode, enqueueOnly ? ENQUEUE_ONLY : ENQUEUE_AND_DEQUEUE); +} + +} +} + + diff --git a/cpp/src/qpid/client/RdmaConnector.cpp b/cpp/src/qpid/client/RdmaConnector.cpp new file mode 100644 index 0000000000..77169db3a6 --- /dev/null +++ b/cpp/src/qpid/client/RdmaConnector.cpp @@ -0,0 +1,387 @@ +/* + * + * 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. + * + */ +#include "qpid/client/Connector.h" + +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +// This stuff needs to abstracted out of here to a platform specific file +#include <netdb.h> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; +using boost::format; +using boost::str; + + class RdmaConnector : public Connector, public sys::Codec, private sys::Runnable +{ + struct Buff; + + typedef Rdma::Buffer BufferBase; + typedef std::deque<framing::AMQFrame> Frames; + + const uint16_t maxFrameSize; + sys::Mutex lock; + Frames frames; + size_t lastEof; // Position after last EOF in frames + uint64_t currentSize; + Bounds* bounds; + + + framing::ProtocolVersion version; + bool initiated; + + sys::Mutex pollingLock; + bool polling; + bool joined; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + sys::Thread receiver; + + Rdma::AsynchIO* aio; + sys::Poller::shared_ptr poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + ~RdmaConnector(); + + void run(); + void handleClosed(); + bool closeInternal(); + + // Callbacks + void connected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&); + void connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, Rdma::ErrorType); + void disconnected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&); + void rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&); + + void readbuff(Rdma::AsynchIO&, Rdma::Buffer*); + void writebuff(Rdma::AsynchIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(Rdma::AsynchIO&); + + std::string identifier; + + ConnectionImpl* impl; + + void connect(const std::string& host, int port); + void close(); + void send(framing::AMQFrame& frame); + void abort() {} // TODO: need to fix this for heartbeat timeouts to work + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +public: + RdmaConnector(framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + unsigned int getSSF() { return 0; } +}; + +// Static constructor which registers connector here +namespace { + Connector* create(framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new RdmaConnector(v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("rdma", &create); + Connector::registerFactory("ib", &create); + }; + } init; +} + + +RdmaConnector::RdmaConnector(ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + polling(false), + joined(true), + shutdownHandler(0), + aio(0), + impl(cimpl) +{ + QPID_LOG(debug, "RdmaConnector created for " << version); +} + +RdmaConnector::~RdmaConnector() { + close(); +} + +void RdmaConnector::connect(const std::string& host, int port){ + Mutex::ScopedLock l(pollingLock); + assert(!polling); + assert(joined); + poller = Poller::shared_ptr(new Poller); + + SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + Rdma::Connector* c = new Rdma::Connector( + sa, + Rdma::ConnectionParams(maxFrameSize, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&RdmaConnector::connected, this, poller, _1, _2), + boost::bind(&RdmaConnector::connectionError, this, poller, _1, _2), + boost::bind(&RdmaConnector::disconnected, this, poller, _1), + boost::bind(&RdmaConnector::rejected, this, poller, _1, _2)); + c->start(poller); + + polling = true; + joined = false; + receiver = Thread(this); +} + +// The following only gets run when connected +void RdmaConnector::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) { + Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair(); + + aio = new Rdma::AsynchIO(ci->getQueuePair(), + cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES, + boost::bind(&RdmaConnector::readbuff, this, _1, _2), + boost::bind(&RdmaConnector::writebuff, this, _1), + 0, // write buffers full + boost::bind(&RdmaConnector::eof, this, _1)); // data error - just close connection + aio->start(poller); + + identifier = str(format("[%1% %2%]") % ci->getLocalName() % ci->getPeerName()); + ProtocolInitiation init(version); + writeDataBlock(init); +} + +void RdmaConnector::connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, Rdma::ErrorType) { + QPID_LOG(trace, "Connection Error " << identifier); + eof(*aio); +} + +void RdmaConnector::disconnected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&) { + eof(*aio); +} + +void RdmaConnector::rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams& cp) { + QPID_LOG(trace, "Connection Rejected " << identifier << ": " << cp.maxRecvBufferSize); + eof(*aio); +} + +bool RdmaConnector::closeInternal() { + bool ret; + { + Mutex::ScopedLock l(pollingLock); + ret = polling; + if (polling) { + polling = false; + poller->shutdown(); + } + if (joined || receiver.id() == Thread::current().id()) { + return ret; + } + joined = true; + } + + receiver.join(); + return ret; +} + +void RdmaConnector::close() { + closeInternal(); +} + +void RdmaConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void RdmaConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* RdmaConnector::getOutputHandler(){ + return this; +} + +sys::ShutdownHandler* RdmaConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& RdmaConnector::getIdentifier() const { + return identifier; +} + +void RdmaConnector::send(AMQFrame& frame) { + bool notifyWrite = false; + { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + //only ask to write if this is the end of a frameset or if we + //already have a buffers worth of data + currentSize += frame.encodedSize(); + if (frame.getEof()) { + lastEof = frames.size(); + notifyWrite = true; + } else { + notifyWrite = (currentSize >= maxFrameSize); + } + } + if (notifyWrite) aio->notifyPendingWrite(); +} + +void RdmaConnector::handleClosed() { + if (closeInternal() && shutdownHandler) + shutdownHandler->shutdown(); +} + +// Called in IO thread. (write idle routine) +// This is NOT only called in response to previously calling notifyPendingWrite +void RdmaConnector::writebuff(Rdma::AsynchIO&) { + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (codec->canEncode()) { + std::auto_ptr<BufferBase> buffer = std::auto_ptr<BufferBase>(aio->getBuffer()); + size_t encoded = codec->encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer.release()); + } +} + +bool RdmaConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return aio->writable() && aio->bufferAvailable() && (lastEof || currentSize >= maxFrameSize); +} + +size_t RdmaConnector::encode(const char* buffer, size_t size) +{ + framing::Buffer out(const_cast<char*>(buffer), size); + size_t bytesWritten(0); + { + Mutex::ScopedLock l(lock); + while (!frames.empty() && out.available() >= frames.front().encodedSize() ) { + frames.front().encode(out); + QPID_LOG(trace, "SENT " << identifier << ": " << frames.front()); + frames.pop_front(); + if (lastEof) --lastEof; + } + bytesWritten = size - out.available(); + currentSize -= bytesWritten; + } + if (bounds) bounds->reduce(bytesWritten); + return bytesWritten; +} + +void RdmaConnector::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) { + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + codec->decode(buff->bytes+buff->dataStart, buff->dataCount); +} + +size_t RdmaConnector::decode(const char* buffer, size_t size) +{ + framing::Buffer in(const_cast<char*>(buffer), size); + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + //TODO: check the version is correct + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + return size - in.available(); +} + +void RdmaConnector::writeDataBlock(const AMQDataBlock& data) { + Rdma::Buffer* buff = aio->getBuffer(); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void RdmaConnector::eof(Rdma::AsynchIO&) { + handleClosed(); +} + +void RdmaConnector::run(){ + // Keep the connection impl in memory until run() completes. + //GRS: currently the ConnectionImpls destructor is where the Io thread is joined + //boost::shared_ptr<ConnectionImpl> protect = impl->shared_from_this(); + //assert(protect); + try { + Dispatcher d(poller); + + //aio->start(poller); + d.run(); + //aio->queueForDeletion(); + } catch (const std::exception& e) { + { + // We're no longer polling + Mutex::ScopedLock l(pollingLock); + polling = false; + } + QPID_LOG(error, e.what()); + handleClosed(); + } +} + +void RdmaConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/Results.cpp b/cpp/src/qpid/client/Results.cpp index c605af2878..0de3e8bd04 100644 --- a/cpp/src/qpid/client/Results.cpp +++ b/cpp/src/qpid/client/Results.cpp @@ -19,18 +19,21 @@ * */ -#include "Results.h" -#include "FutureResult.h" +#include "qpid/client/Results.h" +#include "qpid/client/FutureResult.h" #include "qpid/framing/SequenceSet.h" using namespace qpid::framing; -using namespace boost; namespace qpid { namespace client { Results::Results() {} +Results::~Results() { + try { close(); } catch (const std::exception& /*e*/) { assert(0); } +} + void Results::close() { for (Listeners::iterator i = listeners.begin(); i != listeners.end(); i++) { diff --git a/cpp/src/qpid/client/Results.h b/cpp/src/qpid/client/Results.h index 667f35089c..4c49f6b05b 100644 --- a/cpp/src/qpid/client/Results.h +++ b/cpp/src/qpid/client/Results.h @@ -38,6 +38,7 @@ public: typedef boost::shared_ptr<FutureResult> FutureResultPtr; Results(); + ~Results(); void completed(const framing::SequenceSet& set); void received(const framing::SequenceNumber& id, const std::string& result); FutureResultPtr listenForResult(const framing::SequenceNumber& point); diff --git a/cpp/src/qpid/client/Sasl.h b/cpp/src/qpid/client/Sasl.h new file mode 100644 index 0000000000..63da37fcb1 --- /dev/null +++ b/cpp/src/qpid/client/Sasl.h @@ -0,0 +1,70 @@ +#ifndef QPID_CLIENT_SASL_H +#define QPID_CLIENT_SASL_H + +/* + * + * 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. + * + */ + +#include <memory> +#include <string> +#include "qpid/sys/IntegerTypes.h" + +namespace qpid { + +namespace sys { +class SecurityLayer; +} + +namespace client { + +struct ConnectionSettings; + +/** + * Interface to SASL support. This class is implemented by platform-specific + * SASL providers. + */ +class Sasl +{ + public: + /** + * Start SASL negotiation with the broker. + * + * @param mechanisms Comma-separated list of the SASL mechanism the + * client supports. + * @param ssf Security Strength Factor (SSF). SSF is used to negotiate + * a SASL security layer on top of the connection should both + * parties require and support it. The value indicates the + * required level of security for communication. Possible + * values are: + * @li 0 No security + * @li 1 Integrity checking only + * @li >1 Integrity and confidentiality with the number + * giving the encryption key length. + */ + virtual std::string start(const std::string& mechanisms, unsigned int ssf) = 0; + virtual std::string step(const std::string& challenge) = 0; + virtual std::string getMechanism() = 0; + virtual std::string getUserId() = 0; + virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0; + virtual ~Sasl() {} +}; +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SASL_H*/ diff --git a/cpp/src/qpid/client/SaslFactory.cpp b/cpp/src/qpid/client/SaslFactory.cpp new file mode 100644 index 0000000000..5012b75c94 --- /dev/null +++ b/cpp/src/qpid/client/SaslFactory.cpp @@ -0,0 +1,379 @@ +/* + * + * 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. + * + */ +#include "qpid/client/SaslFactory.h" +#include "qpid/client/ConnectionSettings.h" +#include <map> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_SASL + +namespace qpid { +namespace client { + +//Null implementation + +SaslFactory::SaslFactory() {} + +SaslFactory::~SaslFactory() {} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const ConnectionSettings&) +{ + return std::auto_ptr<Sasl>(); +} + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +}} // namespace qpid::client + +#else + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/cyrus/CyrusSecurityLayer.h" +#include "qpid/log/Statement.h" +#include <sasl/sasl.h> +#include <strings.h> + +namespace qpid { +namespace client { + +using qpid::sys::SecurityLayer; +using qpid::sys::cyrus::CyrusSecurityLayer; +using qpid::framing::InternalErrorException; + +const size_t MAX_LOGIN_LENGTH = 50; + +class CyrusSasl : public Sasl +{ + public: + CyrusSasl(const ConnectionSettings&); + ~CyrusSasl(); + std::string start(const std::string& mechanisms, unsigned int ssf); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + sasl_conn_t* conn; + sasl_callback_t callbacks[5];//realm, user, authname, password, end-of-list + ConnectionSettings settings; + std::string input; + std::string mechanism; + char login[MAX_LOGIN_LENGTH]; + + void interact(sasl_interact_t* client_interact); +}; + +//sasl callback functions +int getUserFromSettings(void *context, int id, const char **result, unsigned *len); +int getPasswordFromSettings(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret); +typedef int CallbackProc(); + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +SaslFactory::SaslFactory() +{ + sasl_callback_t* callbacks = 0; + int result = sasl_client_init(callbacks); + if (result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errstring(result, 0, 0))); + } +} + +SaslFactory::~SaslFactory() +{ + sasl_done(); +} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const ConnectionSettings& settings) +{ + std::auto_ptr<Sasl> sasl(new CyrusSasl(settings)); + return sasl; +} + +CyrusSasl::CyrusSasl(const ConnectionSettings& s) : conn(0), settings(s) +{ + size_t i = 0; + + callbacks[i].id = SASL_CB_GETREALM; + callbacks[i].proc = 0; + callbacks[i++].context = 0; + + if (!settings.username.empty()) { + callbacks[i].id = SASL_CB_USER; + callbacks[i].proc = (CallbackProc*) &getUserFromSettings; + callbacks[i++].context = &settings; + + callbacks[i].id = SASL_CB_AUTHNAME; + callbacks[i].proc = (CallbackProc*) &getUserFromSettings; + callbacks[i++].context = &settings; + } + + callbacks[i].id = SASL_CB_PASS; + if (settings.password.empty()) { + callbacks[i].proc = 0; + callbacks[i++].context = 0; + } else { + callbacks[i].proc = (CallbackProc*) &getPasswordFromSettings; + callbacks[i++].context = &settings; + } + + callbacks[i].id = SASL_CB_LIST_END; + callbacks[i].proc = 0; + callbacks[i++].context = 0; +} + +CyrusSasl::~CyrusSasl() +{ + if (conn) { + sasl_dispose(&conn); + } +} + +namespace { + const std::string SSL("ssl"); +} + +std::string CyrusSasl::start(const std::string& mechanisms, unsigned int ssf) +{ + QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << ")"); + int result = sasl_client_new(settings.service.c_str(), + settings.host.c_str(), + 0, 0, /* Local and remote IP address strings */ + callbacks, + 0, /* security flags */ + &conn); + + if (result != SASL_OK) throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + + sasl_security_properties_t secprops; + + if (ssf) { + sasl_ssf_t external_ssf = (sasl_ssf_t) ssf; + if (external_ssf) { + int result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &external_ssf); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); + } + QPID_LOG(debug, "external SSF detected and set to " << ssf); + } + } + + secprops.min_ssf = settings.minSsf; + secprops.max_ssf = settings.maxSsf; + secprops.maxbufsize = 65535; + + QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << ", max_ssf: " << secprops.max_ssf); + + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0;//TODO: provide means for application to configure these + + result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); + } + + + sasl_interact_t* client_interact = 0; + const char *out = 0; + unsigned outlen = 0; + const char *chosenMechanism = 0; + + do { + result = sasl_client_start(conn, + mechanisms.c_str(), + &client_interact, + &out, + &outlen, + &chosenMechanism); + + if (result == SASL_INTERACT) { + interact(client_interact); + } + } while (result == SASL_INTERACT); + + if (result != SASL_CONTINUE && result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + } + + mechanism = std::string(chosenMechanism); + QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << "): selected " + << mechanism << " response: '" << std::string(out, outlen) << "'"); + return std::string(out, outlen); +} + +std::string CyrusSasl::step(const std::string& challenge) +{ + sasl_interact_t* client_interact = 0; + const char *out = 0; + unsigned outlen = 0; + int result = 0; + do { + result = sasl_client_step(conn, /* our context */ + challenge.data(), /* the data from the server */ + challenge.size(), /* it's length */ + &client_interact, /* this should be + unallocated and NULL */ + &out, /* filled in on success */ + &outlen); /* filled in on success */ + + if (result == SASL_INTERACT) { + interact(client_interact); + } + } while (result == SASL_INTERACT); + + std::string response; + if (result == SASL_CONTINUE || result == SASL_OK) response = std::string(out, outlen); + else if (result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + } + QPID_LOG(debug, "CyrusSasl::step(" << challenge << "): " << response); + return response; +} + +std::string CyrusSasl::getMechanism() +{ + return mechanism; +} + +std::string CyrusSasl::getUserId() +{ + int propResult; + const void* operName; + + propResult = sasl_getprop(conn, SASL_USERNAME, &operName); + if (propResult == SASL_OK) + return std::string((const char*) operName); + + return std::string(); +} + +void CyrusSasl::interact(sasl_interact_t* client_interact) +{ + + if (client_interact->id == SASL_CB_PASS) { + char* password = getpass(client_interact->prompt); + input = std::string(password); + client_interact->result = input.data(); + client_interact->len = input.size(); + } else { + std::cout << client_interact->prompt; + if (client_interact->defresult) std::cout << " (" << client_interact->defresult << ")"; + std::cout << ": "; + if (std::cin >> input) { + client_interact->result = input.data(); + client_interact->len = input.size(); + } + } + +} + +std::auto_ptr<SecurityLayer> CyrusSasl::getSecurityLayer(uint16_t maxFrameSize) +{ + const void* value(0); + int result = sasl_getprop(conn, SASL_SSF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); + } + uint ssf = *(reinterpret_cast<const unsigned*>(value)); + std::auto_ptr<SecurityLayer> securityLayer; + if (ssf) { + QPID_LOG(info, "Installing security layer, SSF: "<< ssf); + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize)); + } + return securityLayer; +} + +int getUserFromSettings(void* context, int /*id*/, const char** result, unsigned* /*len*/) +{ + if (context) { + *result = ((ConnectionSettings*) context)->username.c_str(); + QPID_LOG(debug, "getUserFromSettings(): " << (*result)); + return SASL_OK; + } else { + return SASL_FAIL; + } +} + +namespace { +// Global map of secrest allocated for SASL connections via callback +// to getPasswordFromSettings. Ensures secrets are freed. +class SecretsMap { + typedef std::map<sasl_conn_t*, void*> Map; + Map map; + public: + void keep(sasl_conn_t* conn, void* secret) { + Map::iterator i = map.find(conn); + if (i != map.end()) free(i->second); + map[conn] = secret; + } + + ~SecretsMap() { + for (Map::iterator i = map.begin(); i != map.end(); ++i) + free(i->second); + } +}; +SecretsMap getPasswordFromSettingsSecrets; +} + +int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_secret_t** psecret) +{ + if (context) { + size_t length = ((ConnectionSettings*) context)->password.size(); + sasl_secret_t* secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length); + getPasswordFromSettingsSecrets.keep(conn, secret); + secret->len = length; + memcpy(secret->data, ((ConnectionSettings*) context)->password.data(), length); + *psecret = secret; + return SASL_OK; + } else { + return SASL_FAIL; + } +} + +}} // namespace qpid::client + +#endif diff --git a/cpp/src/qpid/client/AsyncSession.h b/cpp/src/qpid/client/SaslFactory.h index 150aabe191..d012af06f7 100644 --- a/cpp/src/qpid/client/AsyncSession.h +++ b/cpp/src/qpid/client/SaslFactory.h @@ -1,5 +1,5 @@ -#ifndef QPID_CLIENT_ASYNCSESSION_H -#define QPID_CLIENT_ASYNCSESSION_H +#ifndef QPID_CLIENT_SASLFACTORY_H +#define QPID_CLIENT_SASLFACTORY_H /* * @@ -21,18 +21,28 @@ * under the License. * */ -#include "qpid/client/AsyncSession_0_10.h" +#include "qpid/client/Sasl.h" +#include "qpid/sys/Mutex.h" +#include <memory> namespace qpid { namespace client { /** - * AsyncSession is an alias for Session_0_10 - * - * \ingroup clientapi + * Factory for instances of the Sasl interface through which Sasl + * support is provided to a ConnectionHandler. */ -typedef AsyncSession_0_10 AsyncSession; - +class SaslFactory +{ + public: + std::auto_ptr<Sasl> create(const ConnectionSettings&); + static SaslFactory& getInstance(); + ~SaslFactory(); + private: + SaslFactory(); + static qpid::sys::Mutex lock; + static std::auto_ptr<SaslFactory> instance; +}; }} // namespace qpid::client -#endif /*!QPID_CLIENT_ASYNCSESSION_H*/ +#endif /*!QPID_CLIENT_SASLFACTORY_H*/ diff --git a/cpp/src/qpid/client/SessionBase_0_10.cpp b/cpp/src/qpid/client/SessionBase_0_10.cpp index 974acbfcf6..1a345d534e 100644 --- a/cpp/src/qpid/client/SessionBase_0_10.cpp +++ b/cpp/src/qpid/client/SessionBase_0_10.cpp @@ -18,21 +18,23 @@ * under the License. * */ -#include "SessionBase_0_10.h" +#include "qpid/client/SessionBase_0_10.h" +#include "qpid/client/Connection.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/Future.h" #include "qpid/framing/all_method_bodies.h" namespace qpid { namespace client { + using namespace framing; SessionBase_0_10::SessionBase_0_10() {} SessionBase_0_10::~SessionBase_0_10() {} -void SessionBase_0_10::close() { impl->close(); } - -Execution& SessionBase_0_10::getExecution() -{ - return *impl; +void SessionBase_0_10::close() +{ + if (impl) impl->close(); } void SessionBase_0_10::flush() @@ -47,6 +49,11 @@ void SessionBase_0_10::sync() impl->send(b).wait(*impl); } +void SessionBase_0_10::markCompleted(const framing::SequenceSet& ids, bool notifyPeer) +{ + impl->markCompleted(ids, notifyPeer); +} + void SessionBase_0_10::markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer) { impl->markCompleted(id, cumulative, notifyPeer); @@ -57,8 +64,14 @@ void SessionBase_0_10::sendCompletion() impl->sendCompletion(); } +uint16_t SessionBase_0_10::getChannel() const { return impl->getChannel(); } + +void SessionBase_0_10::suspend() { impl->suspend(); } +void SessionBase_0_10::resume(Connection c) { impl->resume(c.impl); } +uint32_t SessionBase_0_10::timeout(uint32_t seconds) { return impl->setTimeout(seconds); } + SessionId SessionBase_0_10::getId() const { return impl->getId(); } -framing::FrameSet::shared_ptr SessionBase_0_10::get() { return impl->get(); } +bool SessionBase_0_10::isValid() const { return impl; } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/SessionBase_0_10.h b/cpp/src/qpid/client/SessionBase_0_10.h deleted file mode 100644 index 8634164dd1..0000000000 --- a/cpp/src/qpid/client/SessionBase_0_10.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef QPID_CLIENT_SESSIONBASE_H -#define QPID_CLIENT_SESSIONBASE_H - -/* - * - * 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. - * - */ - -#include "qpid/SessionId.h" -#include "qpid/framing/amqp_structs.h" -#include "qpid/framing/ProtocolVersion.h" -#include "qpid/framing/MethodContent.h" -#include "qpid/framing/TransferContent.h" -#include "qpid/client/Completion.h" -#include "qpid/client/ConnectionImpl.h" -#include "qpid/client/Execution.h" -#include "qpid/client/SessionImpl.h" -#include "qpid/client/TypedResult.h" -#include "qpid/shared_ptr.h" -#include <string> - -namespace qpid { -namespace client { - -using std::string; -using framing::Content; -using framing::FieldTable; -using framing::MethodContent; -using framing::SequenceNumber; -using framing::SequenceSet; -using framing::SequenceNumberSet; -using qpid::SessionId; -using framing::Xid; - -/** Unit of message credit: messages or bytes */ -enum CreditUnit { MESSAGE_CREDIT=0, BYTE_CREDIT=1, UNLIMITED_CREDIT=0xFFFFFFFF }; - -/** - * Base class for handles to an AMQP session. - * - * Subclasses provide the AMQP commands for a given - * version of the protocol. - */ -class SessionBase_0_10 { - public: - - typedef framing::TransferContent DefaultContent; - - ///@internal - SessionBase_0_10(); - ~SessionBase_0_10(); - - /** Get the next message frame-set from the session. */ - framing::FrameSet::shared_ptr get(); - - /** Get the session ID */ - SessionId getId() const; - - /** Close the session. - * A session is automatically closed when all handles to it are destroyed. - */ - void close(); - - /** - * Synchronize the session: sync() waits until all commands issued - * on this session so far have been completed by the broker. - * - * Note sync() is always synchronous, even on an AsyncSession object - * because that's almost always what you want. You can call - * AsyncSession::executionSync() directly in the unusual event - * that you want to do an asynchronous sync. - */ - void sync(); - - /** Set the timeout for this session. */ - uint32_t timeout(uint32_t seconds); - - Execution& getExecution(); - void flush(); - void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer); - void sendCompletion(); - - protected: - boost::shared_ptr<SessionImpl> impl; - friend class SessionBase_0_10Access; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_SESSIONBASE_H*/ diff --git a/cpp/src/qpid/client/SessionBase_0_10Access.h b/cpp/src/qpid/client/SessionBase_0_10Access.h index e2189a53dd..4d08a7ceaf 100644 --- a/cpp/src/qpid/client/SessionBase_0_10Access.h +++ b/cpp/src/qpid/client/SessionBase_0_10Access.h @@ -33,7 +33,7 @@ class SessionBase_0_10Access { public: SessionBase_0_10Access(SessionBase_0_10& sb_) : sb(sb_) {} void set(const boost::shared_ptr<SessionImpl>& si) { sb.impl = si; } - boost::shared_ptr<SessionImpl> get() { return sb.impl; } + boost::shared_ptr<SessionImpl> get() const { return sb.impl; } private: SessionBase_0_10& sb; }; diff --git a/cpp/src/qpid/client/SessionImpl.cpp b/cpp/src/qpid/client/SessionImpl.cpp index 2075ed04f3..0f767c9f2e 100644 --- a/cpp/src/qpid/client/SessionImpl.cpp +++ b/cpp/src/qpid/client/SessionImpl.cpp @@ -19,21 +19,25 @@ * */ -#include "SessionImpl.h" +#include "qpid/client/SessionImpl.h" -#include "ConnectionImpl.h" -#include "Future.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Future.h" #include "qpid/framing/all_method_bodies.h" #include "qpid/framing/ClientInvoker.h" -#include "qpid/framing/constants.h" +#include "qpid/framing/enum.h" #include "qpid/framing/FrameSet.h" +#include "qpid/framing/AMQFrame.h" #include "qpid/framing/MethodContent.h" #include "qpid/framing/SequenceSet.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/DeliveryProperties.h" #include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" #include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> namespace { const std::string EMPTY; } @@ -41,29 +45,26 @@ namespace qpid { namespace client { using namespace qpid::framing; -using namespace qpid::framing::session;//for detach codes +using namespace qpid::framing::session; //for detach codes typedef sys::Monitor::ScopedLock Lock; typedef sys::Monitor::ScopedUnlock UnLock; typedef sys::ScopedLock<sys::Semaphore> Acquire; -SessionImpl::SessionImpl(const std::string& name, - shared_ptr<ConnectionImpl> conn, - uint16_t ch, uint64_t _maxFrameSize) - : error(OK), - code(NORMAL), - text(EMPTY), - state(INACTIVE), +SessionImpl::SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl> conn) + : state(INACTIVE), detachedLifetime(0), - maxFrameSize(_maxFrameSize), + maxFrameSize(conn->getNegotiatedSettings().maxFrameSize), id(conn->getNegotiatedSettings().username, name.empty() ? Uuid(true).str() : name), connection(conn), ioHandler(*this), - channel(ch), proxy(ioHandler), nextIn(0), - nextOut(0) + nextOut(0), + sendMsgCredit(0), + doClearDeliveryPropertiesExchange(true), + autoDetach(true) { channel.next = connection.get(); } @@ -71,12 +72,18 @@ SessionImpl::SessionImpl(const std::string& name, SessionImpl::~SessionImpl() { { Lock l(state); - if (state != DETACHED) { - QPID_LOG(error, "Session was not closed cleanly"); + if (state != DETACHED && state != DETACHING) { + QPID_LOG(warning, "Session was not closed cleanly: " << id); + try { + // Inform broker but don't wait for detached as that deadlocks. + // The detached will be ignored as the channel will be invalid. + if (autoDetach) detach(); + } catch (...) {} // ignore errors. setState(DETACHED); handleClosed(); state.waitWaiters(); } + delete sendMsgCredit; } connection->erase(channel); } @@ -102,7 +109,7 @@ void SessionImpl::open(uint32_t timeout) // user thread waitFor(ATTACHED); //TODO: timeout will not be set locally until get response to //confirm, should we wait for that? - proxy.requestTimeout(timeout); + setTimeout(timeout); proxy.commandPoint(nextOut, 0); } else { throw Exception("Open already called for this session"); @@ -112,16 +119,18 @@ void SessionImpl::open(uint32_t timeout) // user thread void SessionImpl::close() //user thread { Lock l(state); - if (detachedLifetime) { - proxy.requestTimeout(0); - //should we wait for the timeout response? - detachedLifetime = 0; + // close() must be idempotent and no-throw as it will often be called in destructors. + if (state != DETACHED && state != DETACHING) { + try { + if (detachedLifetime) setTimeout(0); + detach(); + waitFor(DETACHED); + } catch (...) {} + setState(DETACHED); } - detach(); - waitFor(DETACHED); } -void SessionImpl::resume(shared_ptr<ConnectionImpl>) // user thread +void SessionImpl::resume(boost::shared_ptr<ConnectionImpl>) // user thread { throw NotImplementedException("Resume not yet implemented by client!"); } @@ -135,8 +144,8 @@ void SessionImpl::suspend() //user thread void SessionImpl::detach() //call with lock held { if (state == ATTACHED) { - proxy.detach(id.getName()); setState(DETACHING); + proxy.detach(id.getName()); } } @@ -201,6 +210,16 @@ bool SessionImpl::isCompleteUpTo(const SequenceNumber& id) return f.result; } +framing::SequenceNumber SessionImpl::getCompleteUpTo() +{ + SequenceNumber firstIncomplete; + { + Lock l(state); + firstIncomplete = incompleteIn.front(); + } + return --firstIncomplete; +} + struct MarkCompleted { const SequenceNumber& id; @@ -219,6 +238,16 @@ struct MarkCompleted }; +void SessionImpl::markCompleted(const SequenceSet& ids, bool notifyPeer) +{ + Lock l(state); + incompleteIn.remove(ids); + completedIn.add(ids); + if (notifyPeer) { + sendCompletion(); + } +} + void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool notifyPeer) { Lock l(state); @@ -239,17 +268,22 @@ void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool } } +void SessionImpl::setException(const sys::ExceptionHolder& ex) { + Lock l(state); + setExceptionLH(ex); +} + +void SessionImpl::setExceptionLH(const sys::ExceptionHolder& ex) { // Call with lock held. + exceptionHolder = ex; + setState(DETACHED); +} + /** * Called by ConnectionImpl to notify active sessions when connection * is explictly closed */ -void SessionImpl::connectionClosed(uint16_t _code, const std::string& _text) -{ - Lock l(state); - error = CONNECTION_CLOSE; - code = _code; - text = _text; - setState(DETACHED); +void SessionImpl::connectionClosed(uint16_t code, const std::string& text) { + setException(createConnectionException(code, text)); handleClosed(); } @@ -257,9 +291,9 @@ void SessionImpl::connectionClosed(uint16_t _code, const std::string& _text) * Called by ConnectionImpl to notify active sessions when connection * is disconnected */ -void SessionImpl::connectionBroke(uint16_t _code, const std::string& _text) -{ - connectionClosed(_code, _text); +void SessionImpl::connectionBroke(const std::string& _text) { + setException(sys::ExceptionHolder(new TransportFailure(_text))); + handleClosed(); } Future SessionImpl::send(const AMQBody& command) @@ -272,8 +306,76 @@ Future SessionImpl::send(const AMQBody& command, const MethodContent& content) return sendCommand(command, &content); } +namespace { +// Functor for FrameSet::map to send header + content frames but, not method frames. +struct SendContentFn { + FrameHandler& handler; + void operator()(const AMQFrame& f) { + if (!f.getMethod()) + handler(const_cast<AMQFrame&>(f)); + } + SendContentFn(FrameHandler& h) : handler(h) {} +}; + +// Adaptor to make FrameSet look like MethodContent; used in cluster update client +struct MethodContentAdaptor : MethodContent +{ + AMQHeaderBody header; + const std::string content; + + MethodContentAdaptor(const FrameSet& f) : header(*f.getHeaders()), content(f.getContent()) {} + + AMQHeaderBody getHeader() const + { + return header; + } + const std::string& getData() const + { + return content; + } +}; + +} + +Future SessionImpl::send(const AMQBody& command, const FrameSet& content, bool reframe) { + Acquire a(sendLock); + SequenceNumber id = nextOut++; + { + Lock l(state); + checkOpen(); + incompleteOut.add(id); + } + Future f(id); + if (command.getMethod()->resultExpected()) { + Lock l(state); + //result listener must be set before the command is sent + f.setFutureResult(results.listenForResult(id)); + } + AMQFrame frame(command); + frame.setEof(false); + handleOut(frame); + + if (reframe) { + MethodContentAdaptor c(content); + sendContent(c); + } else { + SendContentFn send(out); + content.map(send); + } + return f; +} + +void SessionImpl::sendRawFrame(AMQFrame& frame) { + Acquire a(sendLock); + handleOut(frame); +} + Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* content) { + // Only message transfers have content + if (content && sendMsgCredit) { + sendMsgCredit->acquire(); + } Acquire a(sendLock); SequenceNumber id = nextOut++; { @@ -297,9 +399,21 @@ Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* con } return f; } + void SessionImpl::sendContent(const MethodContent& content) { AMQFrame header(content.getHeader()); + + // doClearDeliveryPropertiesExchange is set by cluster update client so + // it can send messages with delivery-properties.exchange set. + // + if (doClearDeliveryPropertiesExchange) { + // Normal client is not allowed to set the delivery-properties.exchange + // so clear it here. + AMQHeaderBody* headerp = static_cast<AMQHeaderBody*>(header.getBody()); + if (headerp && headerp->get<DeliveryProperties>()) + headerp->get<DeliveryProperties>(true)->clearExchangeFlag(); + } header.setFirstSegment(false); uint64_t data_length = content.getData().length(); if(data_length > 0){ @@ -309,7 +423,7 @@ void SessionImpl::sendContent(const MethodContent& content) const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead(); if(data_length < frag_size){ - AMQFrame frame(in_place<AMQContentBody>(content.getData())); + AMQFrame frame((AMQContentBody(content.getData()))); frame.setFirstSegment(false); handleOut(frame); }else{ @@ -318,7 +432,7 @@ void SessionImpl::sendContent(const MethodContent& content) while (remaining > 0) { uint32_t length = remaining > frag_size ? frag_size : remaining; string frag(content.getData().substr(offset, length)); - AMQFrame frame(in_place<AMQContentBody>(frag)); + AMQFrame frame((AMQContentBody(frag))); frame.setFirstSegment(false); frame.setLastSegment(true); if (offset > 0) { @@ -359,37 +473,45 @@ bool isContentFrame(AMQFrame& frame) void SessionImpl::handleIn(AMQFrame& frame) // network thread { try { - if (!invoke(static_cast<SessionHandler&>(*this), *frame.getBody())) { - if (invoke(static_cast<ExecutionHandler&>(*this), *frame.getBody())) { - //make sure the command id sequence and completion - //tracking takes account of execution commands - Lock l(state); - completedIn.add(nextIn++); - } else { - //if not handled by this class, its for the application: - deliver(frame); - } + if (invoke(static_cast<SessionHandler&>(*this), *frame.getBody())) { + ; + } else if (invoke(static_cast<ExecutionHandler&>(*this), *frame.getBody())) { + //make sure the command id sequence and completion + //tracking takes account of execution commands + Lock l(state); + completedIn.add(nextIn++); + } else if (invoke(static_cast<MessageHandler&>(*this), *frame.getBody())) { + ; + } else { + //if not handled by this class, its for the application: + deliver(frame); } - } catch (const SessionException& e) { - //TODO: proper 0-10 exception handling - QPID_LOG(error, "Session exception:" << e.what()); - Lock l(state); - error = EXCEPTION; - code = e.code; - text = e.what(); + } + catch (const SessionException& e) { + setException(createSessionException(e.code, e.getMessage())); + } + catch (const ChannelException& e) { + setException(createChannelException(e.code, e.getMessage())); } } void SessionImpl::handleOut(AMQFrame& frame) // user thread { - connection->expand(frame.size(), true); - channel.handle(frame); + sendFrame(frame, true); } void SessionImpl::proxyOut(AMQFrame& frame) // network thread { - connection->expand(frame.size(), false); + //Note: this case is treated slightly differently that command + //frames sent by application; session controls should not be + //blocked by bounds checking on the outgoing frame queue. + sendFrame(frame, false); +} + +void SessionImpl::sendFrame(AMQFrame& frame, bool canBlock) +{ channel.handle(frame); + connection->expand(frame.encodedSize(), canBlock); } void SessionImpl::deliver(AMQFrame& frame) // network thread @@ -435,24 +557,22 @@ void SessionImpl::detach(const std::string& _name) if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); setState(DETACHED); QPID_LOG(info, "Session detached by peer: " << id); + proxy.detached(_name, DETACH_CODE_NORMAL); } -void SessionImpl::detached(const std::string& _name, uint8_t _code) -{ +void SessionImpl::detached(const std::string& _name, uint8_t _code) { Lock l(state); if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); setState(DETACHED); if (_code) { //TODO: make sure this works with execution.exception - don't //want to overwrite the code from that - QPID_LOG(error, "Session detached by peer: " << id << " " << code); - error = SESSION_DETACH; - code = _code; - text = "Session detached by peer"; + setExceptionLH(createChannelException(_code, "Session detached by peer")); + QPID_LOG(error, exceptionHolder.what()); } if (detachedLifetime == 0) { handleClosed(); - } +} } void SessionImpl::requestTimeout(uint32_t t) @@ -478,7 +598,7 @@ void SessionImpl::commandPoint(const framing::SequenceNumber& id, uint64_t offse void SessionImpl::expected(const framing::SequenceSet& commands, const framing::Array& fragments) { - if (!commands.empty() || fragments.size()) { + if (!commands.empty() || fragments.encodedSize()) { throw NotImplementedException("Session resumption not yet supported"); } } @@ -492,7 +612,7 @@ void SessionImpl::completed(const framing::SequenceSet& commands, bool timelyRep { Lock l(state); incompleteOut.remove(commands); - state.notify();//notify any waiters of completion + state.notifyAll();//notify any waiters of completion completedOut.add(commands); //notify any waiting results of completion results.completed(commands); @@ -561,20 +681,78 @@ void SessionImpl::exception(uint16_t errorCode, const std::string& description, const framing::FieldTable& /*errorInfo*/) { - QPID_LOG(warning, "Exception received from peer: " << errorCode << ":" << description + Lock l(state); + setExceptionLH(createSessionException(errorCode, description)); + QPID_LOG(warning, "Exception received from broker: " << exceptionHolder.what() << " [caused by " << commandId << " " << classCode << ":" << commandCode << "]"); + if (detachedLifetime) + setTimeout(0); +} + +// Message methods: +void SessionImpl::accept(const qpid::framing::SequenceSet&) +{ +} + +void SessionImpl::reject(const qpid::framing::SequenceSet&, uint16_t, const std::string&) +{ +} + +void SessionImpl::release(const qpid::framing::SequenceSet&, bool) +{ +} + +MessageResumeResult SessionImpl::resume(const std::string&, const std::string&) +{ + throw NotImplementedException("resuming transfers not yet supported"); +} + +namespace { + const std::string QPID_SESSION_DEST = ""; + const uint8_t FLOW_MODE_CREDIT = 0; + const uint8_t CREDIT_MODE_MSG = 0; +} + +void SessionImpl::setFlowMode(const std::string& dest, uint8_t flowMode) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + + if ( flowMode != FLOW_MODE_CREDIT ) { + throw NotImplementedException("window flow control mode not supported by producer"); + } Lock l(state); - error = EXCEPTION; - code = errorCode; - text = description; - if (detachedLifetime) { - proxy.requestTimeout(0); - //should we wait for the timeout response? - detachedLifetime = 0; + sendMsgCredit = new sys::Semaphore(0); +} + +void SessionImpl::flow(const std::string& dest, uint8_t mode, uint32_t credit) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + + if ( mode != CREDIT_MODE_MSG ) { + return; + } + if (sendMsgCredit) { + sendMsgCredit->release(credit); } } +void SessionImpl::stop(const std::string& dest) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + if (sendMsgCredit) { + sendMsgCredit->forceLock(); + } +} //private utility methods: @@ -586,25 +764,21 @@ inline void SessionImpl::setState(State s) //call with lock held inline void SessionImpl::waitFor(State s) //call with lock held { // We can be DETACHED at any time - state.waitFor(States(s, DETACHED)); + if (s == DETACHED) state.waitFor(DETACHED); + else state.waitFor(States(s, DETACHED)); check(); } void SessionImpl::check() const //call with lock held. { - switch (error) { - case OK: break; - case CONNECTION_CLOSE: throw ConnectionException(code, text); - case SESSION_DETACH: throw ChannelException(code, text); - case EXCEPTION: throwExecutionException(code, text); - } + exceptionHolder.raise(); } void SessionImpl::checkOpen() const //call with lock held. { check(); if (state != ATTACHED) { - throw NotAttachedException("Session isn't attached"); + throw NotAttachedException(QPID_MSG("Session " << getId() << " isn't attached")); } } @@ -616,10 +790,28 @@ void SessionImpl::assertOpen() const void SessionImpl::handleClosed() { - // FIXME aconway 2008-06-12: needs to be set to the correct exception type. - // - demux.close(sys::ExceptionHolder(text.empty() ? new ClosedException() : new Exception(text))); + demux.close(exceptionHolder.empty() ? + sys::ExceptionHolder(new ClosedException()) : exceptionHolder); results.close(); } +uint32_t SessionImpl::setTimeout(uint32_t seconds) { + proxy.requestTimeout(seconds); + // FIXME aconway 2008-10-07: wait for timeout response from broker + // and use value retured by broker. + detachedLifetime = seconds; + return detachedLifetime; +} + +uint32_t SessionImpl::getTimeout() const { + return detachedLifetime; +} + +boost::shared_ptr<ConnectionImpl> SessionImpl::getConnection() +{ + return connection; +} + +void SessionImpl::disableAutoDetach() { autoDetach = false; } + }} diff --git a/cpp/src/qpid/client/SessionImpl.h b/cpp/src/qpid/client/SessionImpl.h index 55031a94ae..2f35032c4e 100644 --- a/cpp/src/qpid/client/SessionImpl.h +++ b/cpp/src/qpid/client/SessionImpl.h @@ -22,12 +22,13 @@ #ifndef _SessionImpl_ #define _SessionImpl_ -#include "Demux.h" -#include "Execution.h" -#include "Results.h" +#include "qpid/client/Demux.h" +#include "qpid/client/Execution.h" +#include "qpid/client/Results.h" +#include "qpid/client/ClientImportExport.h" #include "qpid/SessionId.h" -#include "qpid/shared_ptr.h" +#include "qpid/SessionState.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/ChannelHandler.h" #include "qpid/framing/SequenceNumber.h" @@ -35,7 +36,10 @@ #include "qpid/framing/AMQP_ServerProxy.h" #include "qpid/sys/Semaphore.h" #include "qpid/sys/StateMonitor.h" +#include "qpid/sys/ExceptionHolder.h" +#include <boost/weak_ptr.hpp> +#include <boost/shared_ptr.hpp> #include <boost/optional.hpp> namespace qpid { @@ -52,15 +56,17 @@ namespace client { class Future; class ConnectionImpl; +class SessionHandler; ///@internal class SessionImpl : public framing::FrameHandler::InOutHandler, public Execution, private framing::AMQP_ClientOperations::SessionHandler, - private framing::AMQP_ClientOperations::ExecutionHandler + private framing::AMQP_ClientOperations::ExecutionHandler, + private framing::AMQP_ClientOperations::MessageHandler { public: - SessionImpl(const std::string& name, shared_ptr<ConnectionImpl>, uint16_t channel, uint64_t maxFrameSize); + SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl>); ~SessionImpl(); @@ -74,33 +80,57 @@ public: void open(uint32_t detachedLifetime); void close(); - void resume(shared_ptr<ConnectionImpl>); + void resume(boost::shared_ptr<ConnectionImpl>); void suspend(); void assertOpen() const; Future send(const framing::AMQBody& command); Future send(const framing::AMQBody& command, const framing::MethodContent& content); + /** + * This method takes the content as a FrameSet; if reframe=false, + * the caller is resposnible for ensuring that the header and + * content frames in that set are correct for this connection + * (right flags, right fragmentation etc). If reframe=true, then + * the header and content from the frameset will be copied and + * reframed correctly for the connection. + */ + QPID_CLIENT_EXTERN Future send(const framing::AMQBody& command, const framing::FrameSet& content, bool reframe=false); + void sendRawFrame(framing::AMQFrame& frame); Demux& getDemux(); void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer); + void markCompleted(const framing::SequenceSet& ids, bool notifyPeer); bool isComplete(const framing::SequenceNumber& id); bool isCompleteUpTo(const framing::SequenceNumber& id); + framing::SequenceNumber getCompleteUpTo(); void waitForCompletion(const framing::SequenceNumber& id); void sendCompletion(); void sendFlush(); + void setException(const sys::ExceptionHolder&); + //NOTE: these are called by the network thread when the connection is closed or dies void connectionClosed(uint16_t code, const std::string& text); - void connectionBroke(uint16_t code, const std::string& text); + void connectionBroke(const std::string& text); + + /** Set timeout in seconds, returns actual timeout allowed by broker */ + uint32_t setTimeout(uint32_t requestedSeconds); + + /** Get timeout in seconds. */ + uint32_t getTimeout() const; + + /** + * get the Connection associated with this connection + */ + boost::shared_ptr<ConnectionImpl> getConnection(); + + void setDoClearDeliveryPropertiesExchange(bool b=true) { doClearDeliveryPropertiesExchange = b; } + + /** Suppress sending detach in destructor. Used by cluster to build session state */ + void disableAutoDetach(); private: - enum ErrorType { - OK, - CONNECTION_CLOSE, - SESSION_DETACH, - EXCEPTION - }; enum State { INACTIVE, ATTACHING, @@ -110,12 +140,14 @@ private: }; typedef framing::AMQP_ClientOperations::SessionHandler SessionHandler; typedef framing::AMQP_ClientOperations::ExecutionHandler ExecutionHandler; + typedef framing::AMQP_ClientOperations::MessageHandler MessageHandler; typedef sys::StateMonitor<State, DETACHED> StateMonitor; typedef StateMonitor::Set States; inline void setState(State s); inline void waitFor(State); + void setExceptionLH(const sys::ExceptionHolder&); // LH = lock held when called. void detach(); void check() const; @@ -124,13 +156,19 @@ private: void handleIn(framing::AMQFrame& frame); void handleOut(framing::AMQFrame& frame); + /** + * Sends session controls. This case is treated slightly + * differently than command frames sent by the application via + * handleOut(); session controlsare not subject to bounds checking + * on the outgoing frame queue. + */ void proxyOut(framing::AMQFrame& frame); + void sendFrame(framing::AMQFrame& frame, bool canBlock); void deliver(framing::AMQFrame& frame); Future sendCommand(const framing::AMQBody&, const framing::MethodContent* = 0); void sendContent(const framing::MethodContent&); void waitForCompletionImpl(const framing::SequenceNumber& id); - void requestTimeout(uint32_t timeout); void sendCompletionImpl(); @@ -139,7 +177,8 @@ private: void attach(const std::string& name, bool force); void attached(const std::string& name); void detach(const std::string& name); - void detached(const std::string& name, uint8_t detachCode); + void detached(const std::string& name, uint8_t detachCode); + void requestTimeout(uint32_t timeout); void timeout(uint32_t timeout); void commandPoint(const framing::SequenceNumber& commandId, uint64_t commandOffset); void expected(const framing::SequenceSet& commands, const framing::Array& fragments); @@ -160,17 +199,28 @@ private: uint8_t fieldIndex, const std::string& description, const framing::FieldTable& errorInfo); - - ErrorType error; - int code; // Error code - std::string text; // Error text + + // Note: Following methods are called by network thread in + // response to message commands from the broker + // EXCEPT Message.Transfer + void accept(const qpid::framing::SequenceSet&); + void reject(const qpid::framing::SequenceSet&, uint16_t, const std::string&); + void release(const qpid::framing::SequenceSet&, bool); + qpid::framing::MessageResumeResult resume(const std::string&, const std::string&); + void setFlowMode(const std::string&, uint8_t); + void flow(const std::string&, uint8_t, uint32_t); + void stop(const std::string&); + + + sys::ExceptionHolder exceptionHolder; mutable StateMonitor state; mutable sys::Semaphore sendLock; uint32_t detachedLifetime; const uint64_t maxFrameSize; const SessionId id; - shared_ptr<ConnectionImpl> connection; + boost::shared_ptr<ConnectionImpl> connection; + framing::FrameHandler::MemFunRef<SessionImpl, &SessionImpl::proxyOut> ioHandler; framing::ChannelHandler channel; framing::AMQP_ServerProxy::Session proxy; @@ -186,6 +236,16 @@ private: framing::SequenceNumber nextIn; framing::SequenceNumber nextOut; + SessionState sessionState; + + // Only keep track of message credit + sys::Semaphore* sendMsgCredit; + + bool doClearDeliveryPropertiesExchange; + + bool autoDetach; + + friend class client::SessionHandler; }; }} // namespace qpid::client diff --git a/cpp/src/qpid/client/SslConnector.cpp b/cpp/src/qpid/client/SslConnector.cpp new file mode 100644 index 0000000000..5cdaaa4615 --- /dev/null +++ b/cpp/src/qpid/client/SslConnector.cpp @@ -0,0 +1,400 @@ +/* + * + * 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. + * + */ +#include "qpid/client/Connector.h" + +#include "config.h" +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/Options.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/ssl/util.h" +#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <map> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::sys::ssl; +using namespace qpid::framing; +using boost::format; +using boost::str; + + +class SslConnector : public Connector, private sys::Runnable +{ + struct Buff; + + /** Batch up frames for writing to aio. */ + class Writer : public framing::FrameHandler { + typedef sys::ssl::SslIOBufferBase BufferBase; + typedef std::vector<framing::AMQFrame> Frames; + + const uint16_t maxFrameSize; + sys::Mutex lock; + sys::ssl::SslIO* aio; + BufferBase* buffer; + Frames frames; + size_t lastEof; // Position after last EOF in frames + framing::Buffer encode; + size_t framesEncoded; + std::string identifier; + Bounds* bounds; + + void writeOne(); + void newBuffer(); + + public: + + Writer(uint16_t maxFrameSize, Bounds*); + ~Writer(); + void init(std::string id, sys::ssl::SslIO*); + void handle(framing::AMQFrame&); + void write(sys::ssl::SslIO&); + }; + + const uint16_t maxFrameSize; + framing::ProtocolVersion version; + bool initiated; + + sys::Mutex closedLock; + bool closed; + bool joined; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + Writer writer; + + sys::Thread receiver; + + sys::ssl::SslSocket socket; + + sys::ssl::SslIO* aio; + boost::shared_ptr<sys::Poller> poller; + + ~SslConnector(); + + void run(); + void handleClosed(); + bool closeInternal(); + + void readbuff(qpid::sys::ssl::SslIO&, qpid::sys::ssl::SslIOBufferBase*); + void writebuff(qpid::sys::ssl::SslIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(qpid::sys::ssl::SslIO&); + + std::string identifier; + + ConnectionImpl* impl; + + void connect(const std::string& host, int port); + void init(); + void close(); + void send(framing::AMQFrame& frame); + void abort() {} // TODO: Need to fix for heartbeat timeouts to work + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + +public: + SslConnector(framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + unsigned int getSSF() { return socket.getKeyLen(); } +}; + +// Static constructor which registers connector here +namespace { + Connector* create(framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new SslConnector(v, s, c); + } + + struct StaticInit { + StaticInit() { + try { + SslOptions options; + options.parse (0, 0, QPIDC_CONF_FILE, true); + if (options.certDbPath.empty()) { + QPID_LOG(info, "SSL connector not enabled, you must set QPID_SSL_CERT_DB to enable it."); + } else { + initNSS(options); + Connector::registerFactory("ssl", &create); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL connector: " << e.what()); + } + }; + + ~StaticInit() { shutdownNSS(); } + } init; +} + +SslConnector::SslConnector(ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + version(ver), + initiated(false), + closed(true), + joined(true), + shutdownHandler(0), + writer(maxFrameSize, cimpl), + aio(0), + impl(cimpl) +{ + QPID_LOG(debug, "SslConnector created for " << version.toString()); + //TODO: how do we want to handle socket configuration with ssl? + //settings.configureSocket(socket); +} + +SslConnector::~SslConnector() { + close(); +} + +void SslConnector::connect(const std::string& host, int port){ + Mutex::ScopedLock l(closedLock); + assert(closed); + try { + socket.connect(host, port); + } catch (const std::exception& e) { + socket.close(); + throw; + } + + identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + closed = false; + poller = Poller::shared_ptr(new Poller); + aio = new SslIO(socket, + boost::bind(&SslConnector::readbuff, this, _1, _2), + boost::bind(&SslConnector::eof, this, _1), + boost::bind(&SslConnector::eof, this, _1), + 0, // closed + 0, // nobuffs + boost::bind(&SslConnector::writebuff, this, _1)); + writer.init(identifier, aio); +} + +void SslConnector::init(){ + Mutex::ScopedLock l(closedLock); + assert(joined); + ProtocolInitiation init(version); + writeDataBlock(init); + joined = false; + receiver = Thread(this); +} + +bool SslConnector::closeInternal() { + Mutex::ScopedLock l(closedLock); + bool ret = !closed; + if (!closed) { + closed = true; + aio->queueForDeletion(); + poller->shutdown(); + } + if (!joined && receiver.id() != Thread::current().id()) { + joined = true; + Mutex::ScopedUnlock u(closedLock); + receiver.join(); + } + return ret; +} + +void SslConnector::close() { + closeInternal(); +} + +void SslConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void SslConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* SslConnector::getOutputHandler() { + return this; +} + +sys::ShutdownHandler* SslConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& SslConnector::getIdentifier() const { + return identifier; +} + +void SslConnector::send(AMQFrame& frame) { + writer.handle(frame); +} + +void SslConnector::handleClosed() { + if (closeInternal() && shutdownHandler) + shutdownHandler->shutdown(); +} + +struct SslConnector::Buff : public SslIO::BufferBase { + Buff(size_t size) : SslIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +SslConnector::Writer::Writer(uint16_t s, Bounds* b) : maxFrameSize(s), aio(0), buffer(0), lastEof(0), bounds(b) +{ +} + +SslConnector::Writer::~Writer() { delete buffer; } + +void SslConnector::Writer::init(std::string id, sys::ssl::SslIO* a) { + Mutex::ScopedLock l(lock); + identifier = id; + aio = a; + newBuffer(); +} +void SslConnector::Writer::handle(framing::AMQFrame& frame) { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + if (frame.getEof() || (bounds && bounds->getCurrentSize() >= maxFrameSize)) { + lastEof = frames.size(); + aio->notifyPendingWrite(); + } + QPID_LOG(trace, "SENT " << identifier << ": " << frame); +} + +void SslConnector::Writer::writeOne() { + assert(buffer); + framesEncoded = 0; + + buffer->dataStart = 0; + buffer->dataCount = encode.getPosition(); + aio->queueWrite(buffer); + newBuffer(); +} + +void SslConnector::Writer::newBuffer() { + buffer = aio->getQueuedBuffer(); + if (!buffer) buffer = new Buff(maxFrameSize); + encode = framing::Buffer(buffer->bytes, buffer->byteCount); + framesEncoded = 0; +} + +// Called in IO thread. +void SslConnector::Writer::write(sys::ssl::SslIO&) { + Mutex::ScopedLock l(lock); + assert(buffer); + size_t bytesWritten(0); + for (size_t i = 0; i < lastEof; ++i) { + AMQFrame& frame = frames[i]; + uint32_t size = frame.encodedSize(); + if (size > encode.available()) writeOne(); + assert(size <= encode.available()); + frame.encode(encode); + ++framesEncoded; + bytesWritten += size; + } + frames.erase(frames.begin(), frames.begin()+lastEof); + lastEof = 0; + if (bounds) bounds->reduce(bytesWritten); + if (encode.getPosition() > 0) writeOne(); +} + +void SslConnector::readbuff(SslIO& aio, SslIO::BufferBase* buff) { + framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); + + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + //TODO: check the version is correct + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (in.available() != 0) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += buff->dataCount-in.available(); + buff->dataCount = in.available(); + aio.unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio.queueReadBuffer(buff); + } +} + +void SslConnector::writebuff(SslIO& aio_) { + writer.write(aio_); +} + +void SslConnector::writeDataBlock(const AMQDataBlock& data) { + SslIO::BufferBase* buff = new Buff(maxFrameSize); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void SslConnector::eof(SslIO&) { + handleClosed(); +} + +void SslConnector::run(){ + // Keep the connection impl in memory until run() completes. + boost::shared_ptr<ConnectionImpl> protect = impl->shared_from_this(); + assert(protect); + try { + Dispatcher d(poller); + + for (int i = 0; i < 32; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + + aio->start(poller); + d.run(); + socket.close(); + } catch (const std::exception& e) { + QPID_LOG(error, e.what()); + handleClosed(); + } +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/StateManager.cpp b/cpp/src/qpid/client/StateManager.cpp index 0cb3c6b9d4..5462e0fed4 100644 --- a/cpp/src/qpid/client/StateManager.cpp +++ b/cpp/src/qpid/client/StateManager.cpp @@ -19,7 +19,7 @@ * */ -#include "StateManager.h" +#include "qpid/client/StateManager.h" #include "qpid/framing/amqp_framing.h" using namespace qpid::client; @@ -60,6 +60,18 @@ void StateManager::setState(int s) stateLock.notifyAll(); } +bool StateManager::setState(int s, int expected) +{ + Monitor::ScopedLock l(stateLock); + if (state == expected) { + state = s; + stateLock.notifyAll(); + return true; + } else { + return false; + } +} + int StateManager::getState() const { Monitor::ScopedLock l(stateLock); diff --git a/cpp/src/qpid/client/StateManager.h b/cpp/src/qpid/client/StateManager.h index b01664a0c1..3c8412dfa7 100644 --- a/cpp/src/qpid/client/StateManager.h +++ b/cpp/src/qpid/client/StateManager.h @@ -36,6 +36,7 @@ class StateManager public: StateManager(int initial); void setState(int state); + bool setState(int state, int expected); int getState() const ; void waitForStateChange(int current); void waitFor(std::set<int> states); diff --git a/cpp/src/qpid/client/Subscription.cpp b/cpp/src/qpid/client/Subscription.cpp new file mode 100644 index 0000000000..988f372604 --- /dev/null +++ b/cpp/src/qpid/client/Subscription.cpp @@ -0,0 +1,55 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/Subscription.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<Subscription> PI; +Subscription::Subscription(SubscriptionImpl* p) { PI::ctor(*this, p); } +Subscription::~Subscription() { PI::dtor(*this); } +Subscription::Subscription(const Subscription& c) : Handle<SubscriptionImpl>() { PI::copy(*this, c); } +Subscription& Subscription::operator=(const Subscription& c) { return PI::assign(*this, c); } + + +std::string Subscription::getName() const { return impl->getName(); } +std::string Subscription::getQueue() const { return impl->getQueue(); } +const SubscriptionSettings& Subscription::getSettings() const { return impl->getSettings(); } +void Subscription::setFlowControl(const FlowControl& f) { impl->setFlowControl(f); } +void Subscription::setAutoAck(unsigned int n) { impl->setAutoAck(n); } +SequenceSet Subscription::getUnacquired() const { return impl->getUnacquired(); } +SequenceSet Subscription::getUnaccepted() const { return impl->getUnaccepted(); } +void Subscription::acquire(const SequenceSet& messageIds) { impl->acquire(messageIds); } +void Subscription::accept(const SequenceSet& messageIds) { impl->accept(messageIds); } +void Subscription::release(const SequenceSet& messageIds) { impl->release(messageIds); } +Session Subscription::getSession() const { return impl->getSession(); } +SubscriptionManager Subscription::getSubscriptionManager() { return impl->getSubscriptionManager(); } +void Subscription::cancel() { impl->cancel(); } +void Subscription::grantMessageCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_MESSAGE, value); } +void Subscription::grantByteCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_BYTE, value); } +}} // namespace qpid::client + + diff --git a/cpp/src/qpid/client/SubscriptionImpl.cpp b/cpp/src/qpid/client/SubscriptionImpl.cpp new file mode 100644 index 0000000000..a8a0b47d94 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionImpl.cpp @@ -0,0 +1,169 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionSettings.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +using sys::Mutex; +using framing::MessageAcquireResult; + +SubscriptionImpl::SubscriptionImpl(SubscriptionManager m, const std::string& q, const SubscriptionSettings& s, const std::string& n, MessageListener* l) + : manager(*PrivateImplRef<SubscriptionManager>::get(m)), name(n), queue(q), settings(s), listener(l) +{} + +void SubscriptionImpl::subscribe() +{ + async(manager.getSession()).messageSubscribe( + arg::queue=queue, + arg::destination=name, + arg::acceptMode=settings.acceptMode, + arg::acquireMode=settings.acquireMode, + arg::exclusive=settings.exclusive); + setFlowControl(settings.flowControl); +} + +std::string SubscriptionImpl::getName() const { return name; } + +std::string SubscriptionImpl::getQueue() const { return queue; } + +const SubscriptionSettings& SubscriptionImpl::getSettings() const { + Mutex::ScopedLock l(lock); + return settings; +} + +void SubscriptionImpl::setFlowControl(const FlowControl& f) { + Mutex::ScopedLock l(lock); + AsyncSession s=manager.getSession(); + if (&settings.flowControl != &f) settings.flowControl = f; + s.messageSetFlowMode(name, f.window); + s.messageFlow(name, CREDIT_UNIT_MESSAGE, f.messages); + s.messageFlow(name, CREDIT_UNIT_BYTE, f.bytes); + s.sync(); +} + +void SubscriptionImpl::grantCredit(framing::message::CreditUnit unit, uint32_t value) { + async(manager.getSession()).messageFlow(name, unit, value); +} + +void SubscriptionImpl::setAutoAck(size_t n) { + Mutex::ScopedLock l(lock); + settings.autoAck = n; +} + +SequenceSet SubscriptionImpl::getUnacquired() const { Mutex::ScopedLock l(lock); return unacquired; } +SequenceSet SubscriptionImpl::getUnaccepted() const { Mutex::ScopedLock l(lock); return unaccepted; } + +void SubscriptionImpl::acquire(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + MessageAcquireResult result = manager.getSession().messageAcquire(messageIds); + unacquired.remove(result.getTransfers()); + if (settings.acceptMode == ACCEPT_MODE_EXPLICIT) + unaccepted.add(result.getTransfers()); +} + +void SubscriptionImpl::accept(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + manager.getSession().messageAccept(messageIds); + unaccepted.remove(messageIds); + switch (settings.completionMode) { + case COMPLETE_ON_ACCEPT: + manager.getSession().markCompleted(messageIds, true); + break; + case COMPLETE_ON_DELIVERY: + manager.getSession().sendCompletion(); + break; + default://do nothing + break; + } +} + +void SubscriptionImpl::release(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + manager.getSession().messageRelease(messageIds); + if (settings.acceptMode == ACCEPT_MODE_EXPLICIT) + unaccepted.remove(messageIds); +} + +Session SubscriptionImpl::getSession() const { return manager.getSession(); } + +SubscriptionManager SubscriptionImpl::getSubscriptionManager() { return SubscriptionManager(&manager); } + +void SubscriptionImpl::cancel() { manager.cancel(name); } + +void SubscriptionImpl::received(Message& m) { + Mutex::ScopedLock l(lock); + MessageImpl& mi = *MessageImpl::get(m); + if (mi.getMethod().getAcquireMode() == ACQUIRE_MODE_NOT_ACQUIRED) + unacquired.add(m.getId()); + else if (mi.getMethod().getAcceptMode() == ACCEPT_MODE_EXPLICIT) + unaccepted.add(m.getId()); + + if (listener) { + Mutex::ScopedUnlock u(lock); + listener->received(m); + } + + if (settings.completionMode == COMPLETE_ON_DELIVERY) { + manager.getSession().markCompleted(m.getId(), false, false); + } + if (settings.autoAck) { + if (unaccepted.size() >= settings.autoAck) { + async(manager.getSession()).messageAccept(unaccepted); + switch (settings.completionMode) { + case COMPLETE_ON_ACCEPT: + manager.getSession().markCompleted(unaccepted, true); + break; + case COMPLETE_ON_DELIVERY: + manager.getSession().sendCompletion(); + break; + default://do nothing + break; + } + unaccepted.clear(); + } + } +} + +Demux::QueuePtr SubscriptionImpl::divert() +{ + Session session(manager.getSession()); + Demux& demux = SessionBase_0_10Access(session).get()->getDemux(); + demuxRule = std::auto_ptr<ScopedDivert>(new ScopedDivert(name, demux)); + return demuxRule->getQueue(); +} + +void SubscriptionImpl::cancelDiversion() { + demuxRule.reset(); +} + +}} // namespace qpid::client + diff --git a/cpp/src/qpid/client/SubscriptionImpl.h b/cpp/src/qpid/client/SubscriptionImpl.h new file mode 100644 index 0000000000..da77213423 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionImpl.h @@ -0,0 +1,125 @@ +#ifndef QPID_CLIENT_SUBSCRIPTIONIMPL_H +#define QPID_CLIENT_SUBSCRIPTIONIMPL_H + +/* + * + * 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. + * + */ + +#include "qpid/client/SubscriptionSettings.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Session.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/Demux.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/Mutex.h" +#include "qpid/RefCounted.h" +#include "qpid/client/ClientImportExport.h" +#include <memory> + +namespace qpid { +namespace client { + +class SubscriptionManager; +class SubscriptionManagerImpl; + +class SubscriptionImpl : public RefCounted, public MessageListener { + public: + QPID_CLIENT_EXTERN SubscriptionImpl(SubscriptionManager, const std::string& queue, + const SubscriptionSettings&, const std::string& name, MessageListener* =0); + + /** The name of the subsctription, used as the "destination" for messages from the broker. + * Usually the same as the queue name but can be set differently. + */ + QPID_CLIENT_EXTERN std::string getName() const; + + /** Name of the queue this subscription subscribes to */ + QPID_CLIENT_EXTERN std::string getQueue() const; + + /** Get the flow control and acknowledgement settings for this subscription */ + QPID_CLIENT_EXTERN const SubscriptionSettings& getSettings() const; + + /** Set the flow control parameters */ + QPID_CLIENT_EXTERN void setFlowControl(const FlowControl&); + + /** Automatically acknowledge (acquire and accept) batches of n messages. + * You can disable auto-acknowledgement by setting n=0, and use acquire() and accept() + * to manually acquire and accept messages. + */ + QPID_CLIENT_EXTERN void setAutoAck(size_t n); + + /** Get the set of ID's for messages received by this subscription but not yet acquired. + * This will always be empty if acquireMode=ACQUIRE_MODE_PRE_ACQUIRED + */ + QPID_CLIENT_EXTERN SequenceSet getUnacquired() const; + + /** Get the set of ID's for messages acquired by this subscription but not yet accepted. */ + QPID_CLIENT_EXTERN SequenceSet getUnaccepted() const; + + /** Acquire messageIds and remove them from the un-acquired set for the session. */ + QPID_CLIENT_EXTERN void acquire(const SequenceSet& messageIds); + + /** Accept messageIds and remove them from the un-accepted set for the session. */ + QPID_CLIENT_EXTERN void accept(const SequenceSet& messageIds); + + /** Release messageIds and remove them from the un-accepted set for the session. */ + QPID_CLIENT_EXTERN void release(const SequenceSet& messageIds); + + /** Get the session associated with this subscription */ + QPID_CLIENT_EXTERN Session getSession() const; + + /** Get the subscription manager associated with this subscription */ + QPID_CLIENT_EXTERN SubscriptionManager getSubscriptionManager(); + + /** Send subscription request and issue appropriate flow control commands. */ + QPID_CLIENT_EXTERN void subscribe(); + + /** Cancel the subscription. */ + QPID_CLIENT_EXTERN void cancel(); + + /** Grant specified credit for this subscription **/ + QPID_CLIENT_EXTERN void grantCredit(framing::message::CreditUnit unit, uint32_t value); + + QPID_CLIENT_EXTERN void received(Message&); + + /** + * Set up demux diversion for messages sent to this subscription + */ + Demux::QueuePtr divert(); + /** + * Cancel any demux diversion that may have been setup for this + * subscription + */ + QPID_CLIENT_EXTERN void cancelDiversion(); + + private: + + mutable sys::Mutex lock; + SubscriptionManagerImpl& manager; + std::string name, queue; + SubscriptionSettings settings; + framing::SequenceSet unacquired, unaccepted; + MessageListener* listener; + std::auto_ptr<ScopedDivert> demuxRule; +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SUBSCRIPTIONIMPL_H*/ diff --git a/cpp/src/qpid/client/SubscriptionManager.cpp b/cpp/src/qpid/client/SubscriptionManager.cpp index b4c48f7365..7eac3c541b 100644 --- a/cpp/src/qpid/client/SubscriptionManager.cpp +++ b/cpp/src/qpid/client/SubscriptionManager.cpp @@ -18,129 +18,85 @@ * under the License. * */ -#ifndef _Subscription_ -#define _Subscription_ -#include "SubscriptionManager.h" -#include <qpid/client/Dispatcher.h> -#include <qpid/client/Session.h> -#include <qpid/client/MessageListener.h> -#include <qpid/framing/Uuid.h> -#include <set> -#include <sstream> +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/PrivateImplRef.h" namespace qpid { namespace client { -SubscriptionManager::SubscriptionManager(const Session& s) - : dispatcher(s), session(s), - flowControl(UNLIMITED, UNLIMITED, false), - acceptMode(0), acquireMode(0), - autoStop(true) -{} - -void SubscriptionManager::subscribeInternal( - const std::string& q, const std::string& dest, const FlowControl& fc) -{ - session.messageSubscribe( - arg::queue=q, arg::destination=dest, - arg::acceptMode=acceptMode, arg::acquireMode=acquireMode); - if (fc.messages || fc.bytes) // No need to set if all 0. - setFlowControl(dest, fc); -} +typedef PrivateImplRef<SubscriptionManager> PI; -void SubscriptionManager::subscribe( - MessageListener& listener, const std::string& q, const std::string& d) -{ - subscribe(listener, q, getFlowControl(), d); -} +SubscriptionManager::SubscriptionManager(const Session& s) { PI::ctor(*this, new SubscriptionManagerImpl(s)); } +SubscriptionManager::SubscriptionManager(SubscriptionManagerImpl* i) { PI::ctor(*this, i); } +SubscriptionManager::SubscriptionManager(const SubscriptionManager& x) : Runnable(), Handle<SubscriptionManagerImpl>() { PI::copy(*this, x); } +SubscriptionManager::~SubscriptionManager() { PI::dtor(*this); } +SubscriptionManager& SubscriptionManager::operator=(const SubscriptionManager& x) { return PI::assign(*this, x); } -void SubscriptionManager::subscribe( - MessageListener& listener, const std::string& q, const FlowControl& fc, const std::string& d) -{ - std::string dest=d.empty() ? q:d; - dispatcher.listen(dest, &listener, autoAck); - return subscribeInternal(q, dest, fc); -} +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(listener, q, ss, n); } -void SubscriptionManager::subscribe( - LocalQueue& lq, const std::string& q, const std::string& d) -{ - subscribe(lq, q, getFlowControl(), d); -} +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(lq, q, ss, n); } -void SubscriptionManager::subscribe( - LocalQueue& lq, const std::string& q, const FlowControl& fc, const std::string& d) -{ - std::string dest=d.empty() ? q:d; - lq.session=session; - lq.queue=session.getExecution().getDemux().add(dest, ByTransferDest(dest)); - return subscribeInternal(q, dest, fc); -} -void SubscriptionManager::setFlowControl( - const std::string& dest, uint32_t messages, uint32_t bytes, bool window) -{ - session.messageSetFlowMode(dest, window); - session.messageFlow(dest, 0, messages); - session.messageFlow(dest, 1, bytes); - session.sync(); -} +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ return impl->subscribe(listener, q, n); } -void SubscriptionManager::setFlowControl(const std::string& dest, const FlowControl& fc) { - setFlowControl(dest, fc.messages, fc.bytes, fc.window); -} -void SubscriptionManager::setFlowControl(const FlowControl& fc) { flowControl=fc; } +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ return impl->subscribe(lq, q, n); } -void SubscriptionManager::setFlowControl( - uint32_t messages_, uint32_t bytes_, bool window_) -{ - setFlowControl(FlowControl(messages_, bytes_, window_)); -} +void SubscriptionManager::cancel(const std::string& dest) { return impl->cancel(dest); } -const FlowControl& SubscriptionManager::getFlowControl() const { return flowControl; } +void SubscriptionManager::setAutoStop(bool set) { impl->setAutoStop(set); } -void SubscriptionManager::setAcceptMode(bool c) { acceptMode=c; } +void SubscriptionManager::run() { impl->run(); } -void SubscriptionManager::setAcquireMode(bool a) { acquireMode=a; } +void SubscriptionManager::start() { impl->start(); } -void SubscriptionManager::setAckPolicy(const AckPolicy& a) { autoAck=a; } +void SubscriptionManager::wait() { impl->wait(); } -AckPolicy& SubscriptionManager::getAckPolicy() { return autoAck; } +void SubscriptionManager::stop() { impl->stop(); } -void SubscriptionManager::cancel(const std::string dest) -{ - sync(session).messageCancel(dest); - dispatcher.cancel(dest); +bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) { + return impl->get(result, queue, timeout); } -void SubscriptionManager::setAutoStop(bool set) { autoStop=set; } +Message SubscriptionManager::get(const std::string& queue, sys::Duration timeout) { + return impl->get(queue, timeout); +} + +Session SubscriptionManager::getSession() const { return impl->getSession(); } -void SubscriptionManager::run() -{ - dispatcher.setAutoStop(autoStop); - dispatcher.run(); +Subscription SubscriptionManager::getSubscription(const std::string& name) const { + return impl->getSubscription(name); +} +void SubscriptionManager::registerFailoverHandler (boost::function<void ()> fh) { + impl->registerFailoverHandler(fh); } -void SubscriptionManager::stop() -{ - dispatcher.stop(); +void SubscriptionManager::setFlowControl(const std::string& name, const FlowControl& flow) { + impl->setFlowControl(name, flow); } -bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) { - LocalQueue lq; - std::string unique = framing::Uuid(true).str(); - subscribe(lq, queue, FlowControl::messageCredit(1), unique); - AutoCancel ac(*this, unique); - //first wait for message to be delivered if a timeout has been specified - if (timeout && lq.get(result, timeout)) return true; - //make sure message is not on queue before final check - sync(session).messageFlush(unique); - return lq.get(result, 0); +void SubscriptionManager::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) { + impl->setFlowControl(name, FlowControl(messages, bytes, window)); } +void SubscriptionManager::setFlowControl(uint32_t messages, uint32_t bytes, bool window) { + impl->setFlowControl(messages, bytes, window); +} + +void SubscriptionManager::setAcceptMode(AcceptMode mode) { impl->setAcceptMode(mode); } +void SubscriptionManager::setAcquireMode(AcquireMode mode) { impl->setAcquireMode(mode); } + }} // namespace qpid::client -#endif + diff --git a/cpp/src/qpid/client/SubscriptionManager.h b/cpp/src/qpid/client/SubscriptionManager.h deleted file mode 100644 index 3dad15fd29..0000000000 --- a/cpp/src/qpid/client/SubscriptionManager.h +++ /dev/null @@ -1,210 +0,0 @@ -#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGER_H -#define QPID_CLIENT_SUBSCRIPTIONMANAGER_H - -/* - * - * 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. - * - */ -#include "qpid/sys/Mutex.h" -#include <qpid/client/Dispatcher.h> -#include <qpid/client/Completion.h> -#include <qpid/client/Session.h> -#include <qpid/client/MessageListener.h> -#include <qpid/client/LocalQueue.h> -#include <qpid/client/FlowControl.h> -#include <qpid/sys/Runnable.h> -#include <set> -#include <sstream> - -namespace qpid { -namespace client { - -/** - * A class to help create and manage subscriptions. - * - * Set up your subscriptions, then call run() to have messages - * delivered. - * - * \ingroup clientapi - */ -class SubscriptionManager : public sys::Runnable -{ - typedef sys::Mutex::ScopedLock Lock; - typedef sys::Mutex::ScopedUnlock Unlock; - - void subscribeInternal(const std::string& q, const std::string& dest, const FlowControl&); - - qpid::client::Dispatcher dispatcher; - qpid::client::AsyncSession session; - FlowControl flowControl; - AckPolicy autoAck; - bool acceptMode; - bool acquireMode; - bool autoStop; - - public: - /** Create a new SubscriptionManager associated with a session */ - SubscriptionManager(const Session& session); - - /** - * Subscribe a MessagesListener to receive messages from queue. - * - * Provide your own subclass of MessagesListener to process - * incoming messages. It will be called for each message received. - * - *@param listener Listener object to receive messages. - *@param queue Name of the queue to subscribe to. - *@param flow initial FlowControl for the subscription. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(MessageListener& listener, - const std::string& queue, - const FlowControl& flow, - const std::string& tag=std::string()); - - /** - * Subscribe a LocalQueue to receive messages from queue. - * - * Incoming messages are stored in the queue for you to retrieve. - * - *@param queue Name of the queue to subscribe to. - *@param flow initial FlowControl for the subscription. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(LocalQueue& localQueue, - const std::string& queue, - const FlowControl& flow, - const std::string& tag=std::string()); - - /** - * Subscribe a MessagesListener to receive messages from queue. - * - * Provide your own subclass of MessagesListener to process - * incoming messages. It will be called for each message received. - * - *@param listener Listener object to receive messages. - *@param queue Name of the queue to subscribe to. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(MessageListener& listener, - const std::string& queue, - const std::string& tag=std::string()); - - /** - * Subscribe a LocalQueue to receive messages from queue. - * - * Incoming messages are stored in the queue for you to retrieve. - * - *@param queue Name of the queue to subscribe to. - *@param tag Unique destination tag for the listener. - * If not specified, the queue name is used. - */ - void subscribe(LocalQueue& localQueue, - const std::string& queue, - const std::string& tag=std::string()); - - - /** Get a single message from a queue. - *@param result is set to the message from the queue. - *@ - *@param timeout wait up this timeout for a message to appear. - *@return true if result was set, false if no message available after timeout. - */ - bool get(Message& result, const std::string& queue, sys::Duration timeout=0); - - /** Cancel a subscription. */ - void cancel(const std::string tag); - - /** Deliver messages until stop() is called. */ - void run(); - - /** If set true, run() will stop when all subscriptions - * are cancelled. If false, run will only stop when stop() - * is called. True by default. - */ - void setAutoStop(bool set=true); - - /** Cause run() to return */ - void stop(); - - static const uint32_t UNLIMITED=0xFFFFFFFF; - - /** Set the flow control for destination. */ - void setFlowControl(const std::string& destintion, const FlowControl& flow); - - /** Set the default initial flow control for subscriptions that do not specify it. */ - void setFlowControl(const FlowControl& flow); - - /** Get the default flow control for new subscriptions that do not specify it. */ - const FlowControl& getFlowControl() const; - - /** Set the flow control for destination tag. - *@param tag: name of the destination. - *@param messages: message credit. - *@param bytes: byte credit. - *@param window: if true use window-based flow control. - */ - void setFlowControl(const std::string& tag, uint32_t messages, uint32_t bytes, bool window=true); - - /** Set the initial flow control settings to be applied to each new subscribtion. - *@param messages: message credit. - *@param bytes: byte credit. - *@param window: if true use window-based flow control. - */ - void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true); - - /** Set the accept-mode for new subscriptions. Defaults to true. - *@param required: if true messages must be confirmed by calling - *Message::acknowledge() or automatically, see setAckPolicy() - */ - void setAcceptMode(bool required); - - /** Set the acquire-mode for new subscriptions. Defaults to false. - *@param acquire: if false messages pre-acquired, if true - * messages are dequed on acknowledgement or on transfer - * depending on acceptMode. - */ - void setAcquireMode(bool acquire); - - /** Set the acknowledgement policy for new subscriptions. - * Default is to acknowledge every message automatically. - */ - void setAckPolicy(const AckPolicy& autoAck); - /** - * - */ - AckPolicy& getAckPolicy(); -}; - -/** AutoCancel cancels a subscription in its destructor */ -class AutoCancel { - public: - AutoCancel(SubscriptionManager& sm_, const std::string& tag_) : sm(sm_), tag(tag_) {} - ~AutoCancel() { sm.cancel(tag); } - private: - SubscriptionManager& sm; - std::string tag; -}; - -}} // namespace qpid::client - -#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGER_H*/ diff --git a/cpp/src/qpid/client/SubscriptionManagerImpl.cpp b/cpp/src/qpid/client/SubscriptionManagerImpl.cpp new file mode 100644 index 0000000000..a558d90be8 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionManagerImpl.cpp @@ -0,0 +1,162 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include <qpid/client/Dispatcher.h> +#include <qpid/client/Session.h> +#include <qpid/client/MessageListener.h> +#include <qpid/framing/Uuid.h> +#include <set> +#include <sstream> + + +namespace qpid { +namespace client { + +SubscriptionManagerImpl::SubscriptionManagerImpl(const Session& s) + : dispatcher(s), session(s), autoStop(true) +{} + +Subscription SubscriptionManagerImpl::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ + sys::Mutex::ScopedLock l(lock); + std::string name=n.empty() ? q:n; + boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, &listener); + dispatcher.listen(si); + //issue subscription request after listener is registered with dispatcher + si->subscribe(); + return subscriptions[name] = Subscription(si.get()); +} + +Subscription SubscriptionManagerImpl::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ + sys::Mutex::ScopedLock l(lock); + std::string name=n.empty() ? q:n; + boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, 0); + boost::intrusive_ptr<LocalQueueImpl> lqi = PrivateImplRef<LocalQueue>::get(lq); + lqi->queue=si->divert(); + si->subscribe(); + lqi->subscription = Subscription(si.get()); + return subscriptions[name] = lqi->subscription; +} + +Subscription SubscriptionManagerImpl::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ + return subscribe(listener, q, defaultSettings, n); +} + +Subscription SubscriptionManagerImpl::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ + return subscribe(lq, q, defaultSettings, n); +} + +void SubscriptionManagerImpl::cancel(const std::string& dest) +{ + sys::Mutex::ScopedLock l(lock); + std::map<std::string, Subscription>::iterator i = subscriptions.find(dest); + if (i != subscriptions.end()) { + sync(session).messageCancel(dest); + dispatcher.cancel(dest); + Subscription s = i->second; + if (s.isValid()) + PrivateImplRef<Subscription>::get(s)->cancelDiversion(); + subscriptions.erase(i); + } +} + +void SubscriptionManagerImpl::setAutoStop(bool set) { autoStop=set; } + +void SubscriptionManagerImpl::run() +{ + dispatcher.setAutoStop(autoStop); + dispatcher.run(); +} + +void SubscriptionManagerImpl::start() +{ + dispatcher.setAutoStop(autoStop); + dispatcher.start(); +} + +void SubscriptionManagerImpl::wait() +{ + dispatcher.wait(); +} + +void SubscriptionManagerImpl::stop() +{ + dispatcher.stop(); +} + +bool SubscriptionManagerImpl::get(Message& result, const std::string& queue, sys::Duration timeout) { + LocalQueue lq; + std::string unique = framing::Uuid(true).str(); + subscribe(lq, queue, SubscriptionSettings(FlowControl::messageCredit(1)), unique); + SubscriptionManager sm(this); + AutoCancel ac(sm, unique); + //first wait for message to be delivered if a timeout has been specified + if (timeout && lq.get(result, timeout)) + return true; + //make sure message is not on queue before final check + sync(session).messageFlush(unique); + return lq.get(result, 0); +} + +Message SubscriptionManagerImpl::get(const std::string& queue, sys::Duration timeout) { + Message result; + if (!get(result, queue, timeout)) + throw Exception("Timed out waiting for a message"); + return result; +} + +Session SubscriptionManagerImpl::getSession() const { return session; } + +Subscription SubscriptionManagerImpl::getSubscription(const std::string& name) const { + sys::Mutex::ScopedLock l(lock); + std::map<std::string, Subscription>::const_iterator i = subscriptions.find(name); + if (i == subscriptions.end()) + throw Exception(QPID_MSG("Subscription not found: " << name)); + return i->second; +} + +void SubscriptionManagerImpl::registerFailoverHandler (boost::function<void ()> fh) { + dispatcher.registerFailoverHandler(fh); +} + +void SubscriptionManagerImpl::setFlowControl(const std::string& name, const FlowControl& flow) { + getSubscription(name).setFlowControl(flow); +} + +void SubscriptionManagerImpl::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) { + setFlowControl(name, FlowControl(messages, bytes, window)); +} + +}} // namespace qpid::client + + diff --git a/cpp/src/qpid/client/SubscriptionManagerImpl.h b/cpp/src/qpid/client/SubscriptionManagerImpl.h new file mode 100644 index 0000000000..6376a05c45 --- /dev/null +++ b/cpp/src/qpid/client/SubscriptionManagerImpl.h @@ -0,0 +1,278 @@ +#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H +#define QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H + +/* + * + * 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. + * + */ +#include "qpid/sys/Mutex.h" +#include <qpid/client/Dispatcher.h> +#include <qpid/client/Completion.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/MessageListener.h> +#include <qpid/client/LocalQueue.h> +#include <qpid/client/Subscription.h> +#include <qpid/sys/Runnable.h> +#include <qpid/RefCounted.h> +#include <set> +#include <sstream> + +namespace qpid { +namespace client { + +/** + * A class to help create and manage subscriptions. + * + * Set up your subscriptions, then call run() to have messages + * delivered. + * + * \ingroup clientapi + * + * \details + * + * <h2>Subscribing and canceling subscriptions</h2> + * + * <ul> + * <li> + * <p>subscribe()</p> + * <pre> SubscriptionManager subscriptions(session); + * Listener listener(subscriptions); + * subscriptions.subscribe(listener, myQueue);</pre> + * <pre> SubscriptionManager subscriptions(session); + * LocalQueue local_queue; + * subscriptions.subscribe(local_queue, string("message_queue"));</pre></li> + * <li> + * <p>cancel()</p> + * <pre>subscriptions.cancel();</pre></li> + * </ul> + * + * <h2>Waiting for messages (and returning)</h2> + * + * <ul> + * <li> + * <p>run()</p> + * <pre> // Give up control to receive messages + * subscriptions.run();</pre></li> + * <li> + * <p>stop()</p> + * <pre>.// Use this code in a listener to return from run() + * subscriptions.stop();</pre></li> + * <li> + * <p>setAutoStop()</p> + * <pre>.// Return from subscriptions.run() when last subscription is cancelled + *.subscriptions.setAutoStop(true); + *.subscriptons.run(); + * </pre></li> + * <li> + * <p>Ending a subscription in a listener</p> + * <pre> + * void Listener::received(Message& message) { + * + * if (message.getData() == "That's all, folks!") { + * subscriptions.cancel(message.getDestination()); + * } + * } + * </pre> + * </li> + * </ul> + * + */ +class SubscriptionManagerImpl : public sys::Runnable, public RefCounted +{ + public: + /** Create a new SubscriptionManagerImpl associated with a session */ + SubscriptionManagerImpl(const Session& session); + + /** + * Subscribe a MessagesListener to receive messages from queue. + * + * Provide your own subclass of MessagesListener to process + * incoming messages. It will be called for each message received. + * + *@param listener Listener object to receive messages. + *@param queue Name of the queue to subscribe to. + *@param settings settings for the subscription. + *@param name unique destination name for the subscription, defaults to queue name. + */ + Subscription subscribe(MessageListener& listener, + const std::string& queue, + const SubscriptionSettings& settings, + const std::string& name=std::string()); + + /** + * Subscribe a LocalQueue to receive messages from queue. + * + * Incoming messages are stored in the queue for you to retrieve. + * + *@param queue Name of the queue to subscribe to. + *@param flow initial FlowControl for the subscription. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(LocalQueue& localQueue, + const std::string& queue, + const SubscriptionSettings& settings, + const std::string& name=std::string()); + + /** + * Subscribe a MessagesListener to receive messages from queue. + * + * Provide your own subclass of MessagesListener to process + * incoming messages. It will be called for each message received. + * + *@param listener Listener object to receive messages. + *@param queue Name of the queue to subscribe to. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(MessageListener& listener, + const std::string& queue, + const std::string& name=std::string()); + + /** + * Subscribe a LocalQueue to receive messages from queue. + * + * Incoming messages are stored in the queue for you to retrieve. + * + *@param queue Name of the queue to subscribe to. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(LocalQueue& localQueue, + const std::string& queue, + const std::string& name=std::string()); + + + /** Get a single message from a queue. + *@param result is set to the message from the queue. + *@param timeout wait up this timeout for a message to appear. + *@return true if result was set, false if no message available after timeout. + */ + bool get(Message& result, const std::string& queue, sys::Duration timeout=0); + + /** Get a single message from a queue. + *@param timeout wait up this timeout for a message to appear. + *@return message from the queue. + *@throw Exception if the timeout is exceeded. + */ + Message get(const std::string& queue, sys::Duration timeout=sys::TIME_INFINITE); + + /** Get a subscription by name. + *@throw Exception if not found. + */ + Subscription getSubscription(const std::string& name) const; + + /** Cancel a subscription. See also: Subscription.cancel() */ + void cancel(const std::string& name); + + /** Deliver messages in the current thread until stop() is called. + * Only one thread may be running in a SubscriptionManager at a time. + * @see run + */ + void run(); + + /** Start a new thread to deliver messages. + * Only one thread may be running in a SubscriptionManager at a time. + * @see start + */ + void start(); + + /** + * Wait for the thread started by a call to start() to complete. + */ + void wait(); + + /** If set true, run() will stop when all subscriptions + * are cancelled. If false, run will only stop when stop() + * is called. True by default. + */ + void setAutoStop(bool set=true); + + /** Stop delivery. Causes run() to return, or the thread started with start() to exit. */ + void stop(); + + static const uint32_t UNLIMITED=0xFFFFFFFF; + + /** Set the flow control for a subscription. */ + void setFlowControl(const std::string& name, const FlowControl& flow); + + /** Set the flow control for a subscription. + *@param name: name of the subscription. + *@param messages: message credit. + *@param bytes: byte credit. + *@param window: if true use window-based flow control. + */ + void setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window=true); + + /** Set the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + void setDefaultSettings(const SubscriptionSettings& s) { defaultSettings = s; } + + /** Get the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + const SubscriptionSettings& getDefaultSettings() const { return defaultSettings; } + + /** Get the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + SubscriptionSettings& getDefaultSettings() { return defaultSettings; } + + /** + * Set the default flow control settings for subscribe() calls + * that don't include a SubscriptionSettings parameter. + * + *@param messages: message credit. + *@param bytes: byte credit. + *@param window: if true use window-based flow control. + */ + void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true) { + defaultSettings.flowControl = FlowControl(messages, bytes, window); + } + + /** + *Set the default accept-mode for subscribe() calls that don't + *include a SubscriptionSettings parameter. + */ + void setAcceptMode(AcceptMode mode) { defaultSettings.acceptMode = mode; } + + /** + * Set the default acquire-mode subscribe()s that don't specify SubscriptionSettings. + */ + void setAcquireMode(AcquireMode mode) { defaultSettings.acquireMode = mode; } + + void registerFailoverHandler ( boost::function<void ()> fh ); + + Session getSession() const; + + private: + mutable sys::Mutex lock; + qpid::client::Dispatcher dispatcher; + qpid::client::AsyncSession session; + bool autoStop; + SubscriptionSettings defaultSettings; + std::map<std::string, Subscription> subscriptions; +}; + + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H*/ diff --git a/cpp/src/qpid/client/TCPConnector.cpp b/cpp/src/qpid/client/TCPConnector.cpp new file mode 100644 index 0000000000..1a6e51d54d --- /dev/null +++ b/cpp/src/qpid/client/TCPConnector.cpp @@ -0,0 +1,327 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/TCPConnector.h" + +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Codec.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; +using boost::format; +using boost::str; + +// Static constructor which registers connector here +namespace { + Connector* create(framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new TCPConnector(v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("tcp", &create); + }; + } init; +} + +struct TCPConnector::Buff : public AsynchIO::BufferBase { + Buff(size_t size) : AsynchIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +TCPConnector::TCPConnector(ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + closed(true), + joined(true), + shutdownHandler(0), + aio(0), + impl(cimpl->shared_from_this()) +{ + QPID_LOG(debug, "TCPConnector created for " << version.toString()); + settings.configureSocket(socket); +} + +TCPConnector::~TCPConnector() { + close(); +} + +void TCPConnector::connect(const std::string& host, int port) { + Mutex::ScopedLock l(lock); + assert(closed); + assert(joined); + poller = Poller::shared_ptr(new Poller); + AsynchConnector::create(socket, + poller, + host, port, + boost::bind(&TCPConnector::connected, this, _1), + boost::bind(&TCPConnector::connectFailed, this, _3)); + closed = false; + joined = false; + receiver = Thread(this); +} + +void TCPConnector::connected(const Socket&) { + aio = AsynchIO::create(socket, + boost::bind(&TCPConnector::readbuff, this, _1, _2), + boost::bind(&TCPConnector::eof, this, _1), + boost::bind(&TCPConnector::eof, this, _1), + 0, // closed + 0, // nobuffs + boost::bind(&TCPConnector::writebuff, this, _1)); + for (int i = 0; i < 32; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + aio->start(poller); + + identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + ProtocolInitiation init(version); + writeDataBlock(init); +} + +void TCPConnector::connectFailed(const std::string& msg) { + QPID_LOG(warning, "Connecting failed: " << msg); + closed = true; + poller->shutdown(); + closeInternal(); + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +bool TCPConnector::closeInternal() { + bool ret; + { + Mutex::ScopedLock l(lock); + ret = !closed; + if (!closed) { + closed = true; + aio->queueForDeletion(); + poller->shutdown(); + } + if (joined || receiver.id() == Thread::current().id()) { + return ret; + } + joined = true; + } + receiver.join(); + return ret; +} + +void TCPConnector::close() { + closeInternal(); +} + +void TCPConnector::abort() { + // Can't abort a closed connection + if (!closed) { + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&TCPConnector::eof, this, _1)); + } else { + // We're still connecting + connectFailed("Connection timedout"); + } + } +} + +void TCPConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void TCPConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* TCPConnector::getOutputHandler() { + return this; +} + +sys::ShutdownHandler* TCPConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& TCPConnector::getIdentifier() const { + return identifier; +} + +void TCPConnector::send(AMQFrame& frame) { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + //only ask to write if this is the end of a frameset or if we + //already have a buffers worth of data + currentSize += frame.encodedSize(); + bool notifyWrite = false; + if (frame.getEof()) { + lastEof = frames.size(); + notifyWrite = true; + } else { + notifyWrite = (currentSize >= maxFrameSize); + } + if (notifyWrite && !closed) aio->notifyPendingWrite(); +} + +void TCPConnector::handleClosed() { + if (closeInternal() && shutdownHandler) + shutdownHandler->shutdown(); +} + +void TCPConnector::writebuff(AsynchIO& /*aio*/) +{ + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (codec->canEncode()) { + std::auto_ptr<AsynchIO::BufferBase> buffer = std::auto_ptr<AsynchIO::BufferBase>(aio->getQueuedBuffer()); + if (!buffer.get()) buffer = std::auto_ptr<AsynchIO::BufferBase>(new Buff(maxFrameSize)); + + size_t encoded = codec->encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer.release()); + } +} + +// Called in IO thread. +bool TCPConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return lastEof || currentSize >= maxFrameSize; +} + +// Called in IO thread. +size_t TCPConnector::encode(const char* buffer, size_t size) +{ + framing::Buffer out(const_cast<char*>(buffer), size); + size_t bytesWritten(0); + { + Mutex::ScopedLock l(lock); + while (!frames.empty() && out.available() >= frames.front().encodedSize() ) { + frames.front().encode(out); + QPID_LOG(trace, "SENT " << identifier << ": " << frames.front()); + frames.pop_front(); + if (lastEof) --lastEof; + } + bytesWritten = size - out.available(); + currentSize -= bytesWritten; + } + if (bounds) bounds->reduce(bytesWritten); + return bytesWritten; +} + +bool TCPConnector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) +{ + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + int32_t decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (decoded < buff->dataCount) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += decoded; + buff->dataCount -= decoded; + aio.unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio.queueReadBuffer(buff); + } + return true; +} + +size_t TCPConnector::decode(const char* buffer, size_t size) +{ + framing::Buffer in(const_cast<char*>(buffer), size); + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + if(!(protocolInit==version)){ + throw Exception(QPID_MSG("Unsupported version: " << protocolInit + << " supported version " << version)); + } + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + return size - in.available(); +} + +void TCPConnector::writeDataBlock(const AMQDataBlock& data) { + AsynchIO::BufferBase* buff = new Buff(maxFrameSize); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void TCPConnector::eof(AsynchIO&) { + handleClosed(); +} + +void TCPConnector::run() { + // Keep the connection impl in memory until run() completes. + boost::shared_ptr<ConnectionImpl> protect = impl.lock(); + assert(protect); + try { + Dispatcher d(poller); + + d.run(); + } catch (const std::exception& e) { + QPID_LOG(error, QPID_MSG("FAIL " << identifier << ": " << e.what())); + handleClosed(); + } + try { + socket.close(); + } catch (const std::exception&) {} +} + +void TCPConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + + +}} // namespace qpid::client diff --git a/cpp/src/qpid/client/TCPConnector.h b/cpp/src/qpid/client/TCPConnector.h new file mode 100644 index 0000000000..6dc07d1f5d --- /dev/null +++ b/cpp/src/qpid/client/TCPConnector.h @@ -0,0 +1,117 @@ +/* + * + * 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. + * + */ + +#ifndef _TCPConnector_ +#define _TCPConnector_ + +#include "Connector.h" +#include "qpid/client/Bounds.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Codec.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Thread.h" + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <deque> +#include <string> + +namespace qpid { +namespace client { + +class TCPConnector : public Connector, public sys::Codec, private sys::Runnable +{ + typedef std::deque<framing::AMQFrame> Frames; + struct Buff; + + const uint16_t maxFrameSize; + + sys::Mutex lock; + Frames frames; // Outgoing frame queue + size_t lastEof; // Position after last EOF in frames + uint64_t currentSize; + Bounds* bounds; + + framing::ProtocolVersion version; + bool initiated; + bool closed; + bool joined; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + sys::Thread receiver; + + sys::Socket socket; + + sys::AsynchIO* aio; + std::string identifier; + boost::shared_ptr<sys::Poller> poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + ~TCPConnector(); + + void run(); + void handleClosed(); + bool closeInternal(); + + virtual void connected(const qpid::sys::Socket&); + void connectFailed(const std::string& msg); + bool readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void writebuff(qpid::sys::AsynchIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(qpid::sys::AsynchIO&); + + boost::weak_ptr<ConnectionImpl> impl; + + void connect(const std::string& host, int port); + void close(); + void send(framing::AMQFrame& frame); + void abort(); + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +public: + TCPConnector(framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + unsigned int getSSF() { return 0; } +}; + +}} // namespace qpid::client + +#endif /* _TCPConnector_ */ diff --git a/cpp/src/qpid/client/TypedResult.h b/cpp/src/qpid/client/TypedResult.h deleted file mode 100644 index 5306997d74..0000000000 --- a/cpp/src/qpid/client/TypedResult.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef _TypedResult_ -#define _TypedResult_ - -#include "Completion.h" - -namespace qpid { -namespace client { - -/** - * Returned by asynchronous commands that return a result. - * You can use get() to wait for completion and get the result value. - * \ingroup clientapi - */ -template <class T> class TypedResult : public Completion -{ - T result; - bool decoded; - -public: - ///@internal - TypedResult(Future f, shared_ptr<SessionImpl> s) : Completion(f, s), decoded(false) {} - - /** - * Wait for the asynchronous command that returned this TypedResult to complete - * and return its result. - * - *@return The result returned by the command. - *@exception If the command returns an error, get() throws an exception. - * - */ - T& get() - { - if (!decoded) { - future.decodeResult(result, *session); - decoded = true; - } - - return result; - } -}; - -}} - -#endif diff --git a/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp b/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp new file mode 100644 index 0000000000..80be5c56f3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp @@ -0,0 +1,111 @@ +/* + * + * 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. + * + */ +#include "AcceptTracker.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +void AcceptTracker::State::accept() +{ + unconfirmed.add(unaccepted); + unaccepted.clear(); +} + +void AcceptTracker::State::release() +{ + unaccepted.clear(); +} + +uint32_t AcceptTracker::State::acceptsPending() +{ + return unconfirmed.size(); +} + +void AcceptTracker::State::completed(qpid::framing::SequenceSet& set) +{ + unconfirmed.remove(set); +} + +void AcceptTracker::delivered(const std::string& destination, const qpid::framing::SequenceNumber& id) +{ + aggregateState.unaccepted.add(id); + destinationState[destination].unaccepted.add(id); +} + +void AcceptTracker::accept(qpid::client::AsyncSession& session) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.accept(); + } + Record record; + record.status = session.messageAccept(aggregateState.unaccepted); + record.accepted = aggregateState.unaccepted; + pending.push_back(record); + aggregateState.accept(); +} + +void AcceptTracker::release(qpid::client::AsyncSession& session) +{ + session.messageRelease(aggregateState.unaccepted); + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.release(); + } + aggregateState.release(); +} + +uint32_t AcceptTracker::acceptsPending() +{ + checkPending(); + return aggregateState.acceptsPending(); +} + +uint32_t AcceptTracker::acceptsPending(const std::string& destination) +{ + checkPending(); + return destinationState[destination].acceptsPending(); +} + +void AcceptTracker::reset() +{ + destinationState.clear(); + aggregateState.unaccepted.clear(); + aggregateState.unconfirmed.clear(); + pending.clear(); +} + +void AcceptTracker::checkPending() +{ + while (!pending.empty() && pending.front().status.isComplete()) { + completed(pending.front().accepted); + pending.pop_front(); + } +} + +void AcceptTracker::completed(qpid::framing::SequenceSet& set) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.completed(set); + } + aggregateState.completed(set); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/AcceptTracker.h b/cpp/src/qpid/client/amqp0_10/AcceptTracker.h new file mode 100644 index 0000000000..fb58a3a8c8 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AcceptTracker.h @@ -0,0 +1,85 @@ +#ifndef QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H +#define QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H + +/* + * + * 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. + * + */ +#include "qpid/client/AsyncSession.h" +#include "qpid/client/Completion.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceSet.h" +#include <deque> +#include <map> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Tracks the set of messages requiring acceptance, and those for + * which an accept has been issued but is yet to be confirmed + * complete. + */ +class AcceptTracker +{ + public: + void delivered(const std::string& destination, const qpid::framing::SequenceNumber& id); + void accept(qpid::client::AsyncSession&); + void release(qpid::client::AsyncSession&); + uint32_t acceptsPending(); + uint32_t acceptsPending(const std::string& destination); + void reset(); + private: + struct State + { + /** + * ids of messages that have been delivered but not yet + * accepted + */ + qpid::framing::SequenceSet unaccepted; + /** + * ids of messages for which an accpet has been issued but not + * yet confirmed as completed + */ + qpid::framing::SequenceSet unconfirmed; + + void accept(); + void release(); + uint32_t acceptsPending(); + void completed(qpid::framing::SequenceSet&); + }; + typedef std::map<std::string, State> StateMap; + struct Record + { + qpid::client::Completion status; + qpid::framing::SequenceSet accepted; + }; + typedef std::deque<Record> Records; + + State aggregateState; + StateMap destinationState; + Records pending; + + void checkPending(); + void completed(qpid::framing::SequenceSet&); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp new file mode 100644 index 0000000000..b70e67d12f --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -0,0 +1,819 @@ +/* + * + * 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. + * + */ +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/CodecsInternal.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/amqp0_10/OutgoingMessage.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Variant.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/ExchangeBoundResult.h" +#include "qpid/framing/ExchangeQueryResult.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/QueueQueryResult.h" +#include "qpid/framing/ReplyTo.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::Exception; +using qpid::messaging::Address; +using qpid::messaging::InvalidAddress; +using qpid::messaging::Variant; +using qpid::framing::ExchangeBoundResult; +using qpid::framing::ExchangeQueryResult; +using qpid::framing::FieldTable; +using qpid::framing::QueueQueryResult; +using qpid::framing::ReplyTo; +using qpid::framing::Uuid; +using namespace qpid::framing::message; +using namespace boost::assign; + + +namespace{ +const Variant EMPTY_VARIANT; +const FieldTable EMPTY_FIELD_TABLE; +const std::string EMPTY_STRING; + +//option names +const std::string BROWSE("browse"); +const std::string EXCLUSIVE("exclusive"); +const std::string NO_LOCAL("no-local"); +const std::string FILTER("filter"); +const std::string RELIABILITY("reliability"); +const std::string NAME("subscription-name"); +const std::string NODE_PROPERTIES("node-properties"); +const std::string X_PROPERTIES("x-properties"); + +//policy types +const std::string CREATE("create"); +const std::string ASSERT("assert"); +const std::string DELETE("delete"); +//policy values +const std::string ALWAYS("always"); +const std::string NEVER("never"); +const std::string RECEIVER("receiver"); +const std::string SENDER("sender"); + +const std::string QUEUE_ADDRESS("queue"); +const std::string TOPIC_ADDRESS("topic"); + +const std::string UNRELIABLE("unreliable"); +const std::string AT_MOST_ONCE("at-most-once"); +const std::string AT_LEAST_ONCE("at-least-once"); +const std::string EXACTLY_ONCE("exactly-once"); +const std::string DURABLE_SUBSCRIPTION("durable"); +const std::string DURABLE("durable"); + +const std::string TOPIC_EXCHANGE("topic"); +const std::string FANOUT_EXCHANGE("fanout"); +const std::string DIRECT_EXCHANGE("direct"); +const std::string HEADERS_EXCHANGE("headers"); +const std::string XML_EXCHANGE("xml"); +const std::string WILDCARD_ANY("*"); +} + +//some amqp 0-10 specific options +namespace xamqp{ +const std::string AUTO_DELETE("auto-delete"); +const std::string EXCHANGE_TYPE("type"); +const std::string EXCLUSIVE("exclusive"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string QUEUE_ARGUMENTS("x-queue-arguments"); +const std::string SUBSCRIBE_ARGUMENTS("x-subscribe-arguments"); +} + +class Node +{ + protected: + enum CheckMode {FOR_RECEIVER, FOR_SENDER}; + + Node(const Address& address); + + const std::string name; + Variant createPolicy; + Variant assertPolicy; + Variant deletePolicy; + + static bool enabled(const Variant& policy, CheckMode mode); + static bool createEnabled(const Address& address, CheckMode mode); + static void convert(const Variant& option, FieldTable& arguments); + static std::vector<std::string> RECEIVER_MODES; + static std::vector<std::string> SENDER_MODES; +}; + +class Queue : protected Node +{ + public: + Queue(const Address& address); + protected: + void checkCreate(qpid::client::AsyncSession&, CheckMode); + void checkAssert(qpid::client::AsyncSession&, CheckMode); + void checkDelete(qpid::client::AsyncSession&, CheckMode); + private: + bool durable; + bool autoDelete; + bool exclusive; + std::string alternateExchange; + FieldTable arguments; + + void configure(const Address&); +}; + +class Exchange : protected Node +{ + public: + Exchange(const Address& address); + protected: + void checkCreate(qpid::client::AsyncSession&, CheckMode); + void checkAssert(qpid::client::AsyncSession&, CheckMode); + void checkDelete(qpid::client::AsyncSession&, CheckMode); + const std::string& getDesiredExchangeType() { return type; } + + private: + std::string type; + bool typeSpecified; + bool durable; + bool autoDelete; + std::string alternateExchange; + FieldTable arguments; + + void configure(const Address&); +}; + +class QueueSource : public Queue, public MessageSource +{ + public: + QueueSource(const Address& address); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + const AcceptMode acceptMode; + const AcquireMode acquireMode; + bool exclusive; + FieldTable options; +}; + +class Subscription : public Exchange, public MessageSource +{ + public: + Subscription(const Address&, const std::string& exchangeType=""); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + struct Binding + { + Binding(const std::string& exchange, const std::string& key, const FieldTable& options = EMPTY_FIELD_TABLE); + + std::string exchange; + std::string key; + FieldTable options; + }; + + typedef std::vector<Binding> Bindings; + + const std::string queue; + const bool reliable; + const bool durable; + FieldTable queueOptions; + FieldTable subscriptionOptions; + Bindings bindings; + + void bindSpecial(const std::string& exchangeType); + void bind(const std::string& subject); + void bind(const std::string& subject, const Variant& filter); + void bind(const std::string& subject, const Variant::Map& filter); + void bind(const std::string& subject, const Variant::List& filter); + void add(const std::string& exchange, const std::string& key, const FieldTable& options = EMPTY_FIELD_TABLE); + static std::string getSubscriptionName(const std::string& base, const Variant& name); +}; + +class ExchangeSink : public Exchange, public MessageSink +{ + public: + ExchangeSink(const Address& name); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: +}; + +class QueueSink : public Queue, public MessageSink +{ + public: + QueueSink(const Address& name); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: +}; + + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address); +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address); + +bool in(const Variant& value, const std::vector<std::string>& choices) +{ + if (!value.isVoid()) { + for (std::vector<std::string>::const_iterator i = choices.begin(); i != choices.end(); ++i) { + if (value.asString() == *i) return true; + } + } + return false; +} + +bool getReceiverPolicy(const Address& address, const std::string& key) +{ + return in(address.getOption(key), list_of<std::string>(ALWAYS)(RECEIVER)); +} + +bool getSenderPolicy(const Address& address, const std::string& key) +{ + return in(address.getOption(key), list_of<std::string>(ALWAYS)(SENDER)); +} + +bool is_unreliable(const Address& address) +{ + return in(address.getOption(RELIABILITY), list_of<std::string>(UNRELIABLE)(AT_MOST_ONCE)); +} + +bool is_reliable(const Address& address) +{ + return in(address.getOption(RELIABILITY), list_of<std::string>(AT_LEAST_ONCE)(EXACTLY_ONCE)); +} + +std::string checkAddressType(qpid::client::Session session, const Address& address) +{ + std::string type = address.getType(); + if (type.empty()) { + ExchangeBoundResult result = session.exchangeBound(arg::exchange=address.getName(), arg::queue=address.getName()); + if (result.getQueueNotFound() && result.getExchangeNotFound()) { + //neither a queue nor an exchange exists with that name; treat it as a queue + type = QUEUE_ADDRESS; + } else if (result.getExchangeNotFound()) { + //name refers to a queue + type = QUEUE_ADDRESS; + } else if (result.getQueueNotFound()) { + //name refers to an exchange + type = TOPIC_ADDRESS; + } else { + //both a queue and exchange exist for that name + throw InvalidAddress("Ambiguous address, please specify queue or topic as node type"); + } + } + return type; +} + +std::auto_ptr<MessageSource> AddressResolution::resolveSource(qpid::client::Session session, + const Address& address) +{ + std::string type = checkAddressType(session, address); + if (type == TOPIC_ADDRESS) { + std::auto_ptr<MessageSource> source(new Subscription(address)); + QPID_LOG(debug, "treating source address as topic: " << address); + return source; + } else if (type == QUEUE_ADDRESS) { + std::auto_ptr<MessageSource> source(new QueueSource(address)); + QPID_LOG(debug, "treating source address as queue: " << address); + return source; + } else { + throw InvalidAddress("Unrecognised type: " + type); + } +} + + +std::auto_ptr<MessageSink> AddressResolution::resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address) +{ + std::string type = checkAddressType(session, address); + if (type == TOPIC_ADDRESS) { + std::auto_ptr<MessageSink> sink(new ExchangeSink(address)); + QPID_LOG(debug, "treating target address as topic: " << address); + return sink; + } else if (type == QUEUE_ADDRESS) { + std::auto_ptr<MessageSink> sink(new QueueSink(address)); + QPID_LOG(debug, "treating target address as queue: " << address); + return sink; + } else { + throw InvalidAddress("Unrecognised type: " + type); + } +} + +const Variant& getNestedOption(const Variant::Map& options, const std::vector<std::string>& keys, size_t index = 0) +{ + Variant::Map::const_iterator i = options.find(keys[index]); + if (i == options.end()) { + return EMPTY_VARIANT; + } else if (index+1 < keys.size()) { + return getNestedOption(i->second.asMap(), keys, index+1); + } else { + return i->second; + } +} + +QueueSource::QueueSource(const Address& address) : + Queue(address), + acceptMode(is_unreliable(address) ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT), + acquireMode(address.getOption(BROWSE).asBool() ? ACQUIRE_MODE_NOT_ACQUIRED : ACQUIRE_MODE_PRE_ACQUIRED), + exclusive(false) +{ + //extract subscription arguments from address options + const Variant& x = address.getOption(X_PROPERTIES); + if (!x.isVoid()) { + const Variant::Map& xProps = x.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::EXCLUSIVE) exclusive = i->second; + else passthrough[i->first] = i->second; + } + translate(passthrough, options); + } +} + +void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + session.messageSubscribe(arg::queue=name, + arg::destination=destination, + arg::acceptMode=acceptMode, + arg::acquireMode=acquireMode, + arg::exclusive=exclusive, + arg::arguments=options); +} + +void QueueSource::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageCancel(destination); + checkDelete(session, FOR_RECEIVER); +} + +std::string Subscription::getSubscriptionName(const std::string& base, const Variant& name) +{ + if (name.isVoid()) { + return (boost::format("%1%_%2%") % base % Uuid(true).str()).str(); + } else { + return (boost::format("%1%_%2%") % base % name.asString()).str(); + } +} + +Subscription::Subscription(const Address& address, const std::string& exchangeType) + : Exchange(address), + queue(getSubscriptionName(name, address.getOption(NAME))), + reliable(is_reliable(address)), + durable(address.getOption(DURABLE_SUBSCRIPTION).asBool()) +{ + if (address.getOption(NO_LOCAL).asBool()) queueOptions.setInt(NO_LOCAL, 1); + const Variant& x = address.getOption(X_PROPERTIES); + if (!x.isVoid()) { + const Variant::Map& xProps = x.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::QUEUE_ARGUMENTS) convert(i->second.asMap(), queueOptions); + else passthrough[i->first] = i->second; + } + translate(passthrough, subscriptionOptions); + } + + const Variant& filter = address.getOption(FILTER); + if (!filter.isVoid()) { + bind(address.getSubject(), filter); + } else if (address.hasSubject()) { + //Note: This will not work for headers- or xml- exchange; + //fanout exchange will do no filtering. + //TODO: for headers- or xml- exchange can construct a match + //for the subject in the application-headers + bind(address.getSubject()); + } else { + //Neither a subject nor a filter has been defined, treat this + //as wanting to match all messages (Note: direct exchange is + //currently unable to support this case). + if (!exchangeType.empty()) bindSpecial(exchangeType); + else if (!getDesiredExchangeType().empty()) bindSpecial(getDesiredExchangeType()); + } +} + +void Subscription::add(const std::string& exchange, const std::string& key, const FieldTable& options) +{ + bindings.push_back(Binding(exchange, key, options)); +} + +void Subscription::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + //create exchange if required and specified by policy: + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + + //create subscription queue: + session.queueDeclare(arg::queue=queue, arg::exclusive=true, + arg::autoDelete=!reliable, arg::durable=durable, arg::arguments=queueOptions); + //bind subscription queue to exchange: + for (Bindings::const_iterator i = bindings.begin(); i != bindings.end(); ++i) { + session.exchangeBind(arg::queue=queue, arg::exchange=i->exchange, arg::bindingKey=i->key, arg::arguments=i->options); + } + //subscribe to subscription queue: + AcceptMode accept = reliable ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE; + session.messageSubscribe(arg::queue=queue, arg::destination=destination, + arg::exclusive=true, arg::acceptMode=accept, arg::arguments=subscriptionOptions); +} + +void Subscription::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageCancel(destination); + session.queueDelete(arg::queue=queue); + checkDelete(session, FOR_RECEIVER); +} + +Subscription::Binding::Binding(const std::string& e, const std::string& k, const FieldTable& o): + exchange(e), key(k), options(o) {} + +ExchangeSink::ExchangeSink(const Address& address) : Exchange(address) {} + +void ExchangeSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + checkCreate(session, FOR_SENDER); + checkAssert(session, FOR_SENDER); +} + +void ExchangeSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m) +{ + m.message.getDeliveryProperties().setRoutingKey(m.getSubject()); + m.status = session.messageTransfer(arg::destination=name, arg::content=m.message); +} + +void ExchangeSink::cancel(qpid::client::AsyncSession& session, const std::string&) +{ + checkDelete(session, FOR_SENDER); +} + +QueueSink::QueueSink(const Address& address) : Queue(address) {} + +void QueueSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + checkCreate(session, FOR_SENDER); + checkAssert(session, FOR_SENDER); +} +void QueueSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m) +{ + m.message.getDeliveryProperties().setRoutingKey(name); + m.status = session.messageTransfer(arg::content=m.message); +} + +void QueueSink::cancel(qpid::client::AsyncSession& session, const std::string&) +{ + checkDelete(session, FOR_SENDER); +} + +Address AddressResolution::convert(const qpid::framing::ReplyTo& rt) +{ + Address address; + if (rt.getExchange().empty()) {//if default exchange, treat as queue + address.setName(rt.getRoutingKey()); + address.setType(QUEUE_ADDRESS); + } else { + address.setName(rt.getExchange()); + address.setSubject(rt.getRoutingKey()); + address.setType(TOPIC_ADDRESS); + } + return address; +} + +qpid::framing::ReplyTo AddressResolution::convert(const Address& address) +{ + if (address.getType() == QUEUE_ADDRESS || address.getType().empty()) { + return ReplyTo(EMPTY_STRING, address.getName()); + } else if (address.getType() == TOPIC_ADDRESS) { + return ReplyTo(address.getName(), address.getSubject()); + } else { + QPID_LOG(notice, "Unrecognised type for reply-to: " << address.getType()); + return ReplyTo(EMPTY_STRING, address.getName());//treat as queue + } +} + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address) +{ + return address.getType() == QUEUE_ADDRESS || + (address.getType().empty() && session.queueQuery(address.getName()).getQueue() == address.getName()); +} + +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address) +{ + if (address.getType().empty()) { + return !session.exchangeQuery(address.getName()).getNotFound(); + } else if (address.getType() == TOPIC_ADDRESS) { + return true; + } else { + return false; + } +} + +void Subscription::bind(const std::string& subject) +{ + add(name, subject); +} + +void Subscription::bind(const std::string& subject, const Variant& filter) +{ + switch (filter.getType()) { + case qpid::messaging::VAR_MAP: + bind(subject, filter.asMap()); + break; + case qpid::messaging::VAR_LIST: + bind(subject, filter.asList()); + break; + default: + //TODO: if both subject _and_ filter are specified, combine in + //some way; for now we just ignore the subject in that case. + add(name, filter.asString()); + break; + } +} + +void Subscription::bind(const std::string& subject, const Variant::Map& filter) +{ + qpid::framing::FieldTable arguments; + translate(filter, arguments); + add(name, subject.empty() ? queue : subject, arguments); +} + +void Subscription::bind(const std::string& subject, const Variant::List& filter) +{ + for (Variant::List::const_iterator i = filter.begin(); i != filter.end(); ++i) { + bind(subject, *i); + } +} + +void Subscription::bindSpecial(const std::string& exchangeType) +{ + if (exchangeType == TOPIC_EXCHANGE) { + add(name, WILDCARD_ANY); + } else if (exchangeType == FANOUT_EXCHANGE) { + add(name, queue); + } else if (exchangeType == HEADERS_EXCHANGE) { + //TODO: add special binding for headers exchange to match all messages + } else if (exchangeType == XML_EXCHANGE) { + //TODO: add special binding for xml exchange to match all messages + } else { //E.g. direct + throw qpid::Exception(QPID_MSG("Cannot create binding to match all messages for exchange of type " << exchangeType)); + } +} + +Node::Node(const Address& address) : name(address.getName()), + createPolicy(address.getOption(CREATE)), + assertPolicy(address.getOption(ASSERT)), + deletePolicy(address.getOption(DELETE)) {} + +Queue::Queue(const Address& a) : Node(a), + durable(false), + autoDelete(false), + exclusive(false) +{ + configure(a); +} + +void Queue::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + QPID_LOG(debug, "Auto-creating queue '" << name << "'"); + try { + sync(session).queueDeclare(arg::queue=name, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::exclusive=exclusive, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + } catch (const qpid::Exception& e) { + throw InvalidAddress((boost::format("Could not create queue %1%; %2%") % name % e.what()).str()); + } + } else { + try { + sync(session).queueDeclare(arg::queue=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw InvalidAddress((boost::format("Queue %1% does not exist") % name).str()); + } catch (const std::exception& e) { + throw InvalidAddress(e.what()); + } + } +} + +void Queue::checkDelete(qpid::client::AsyncSession& session, CheckMode mode) +{ + //Note: queue-delete will cause a session exception if the queue + //does not exist, the query here prevents obvious cases of this + //but there is a race whenever two deletions are made concurrently + //so careful use of the delete policy is recommended at present + if (enabled(deletePolicy, mode) && sync(session).queueQuery(name).getQueue() == name) { + QPID_LOG(debug, "Auto-deleting queue '" << name << "'"); + sync(session).queueDelete(arg::queue=name); + } +} + +void Queue::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(assertPolicy, mode)) { + QueueQueryResult result = sync(session).queueQuery(name); + if (result.getQueue() != name) { + throw InvalidAddress((boost::format("Queue not found: %1%") % name).str()); + } else { + if (durable && !result.getDurable()) { + throw InvalidAddress((boost::format("Queue not durable: %1%") % name).str()); + } + if (autoDelete && !result.getAutoDelete()) { + throw InvalidAddress((boost::format("Queue not set to auto-delete: %1%") % name).str()); + } + if (exclusive && !result.getExclusive()) { + throw InvalidAddress((boost::format("Queue not exclusive: %1%") % name).str()); + } + if (!alternateExchange.empty() && result.getAlternateExchange() != alternateExchange) { + throw InvalidAddress((boost::format("Alternate exchange does not match for %1%, expected %2%, got %3%") + % name % alternateExchange % result.getAlternateExchange()).str()); + } + for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) { + FieldTable::ValuePtr v = result.getArguments().get(i->first); + if (!v) { + throw InvalidAddress((boost::format("Option %1% not set for %2%") % i->first % name).str()); + } else if (*i->second != *v) { + throw InvalidAddress((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + } + } +} + +void Queue::configure(const Address& address) +{ + const Variant& v = address.getOption(NODE_PROPERTIES); + if (!v.isVoid()) { + Variant::Map nodeProps = v.asMap(); + durable = nodeProps[DURABLE]; + Variant::Map::const_iterator x = nodeProps.find(X_PROPERTIES); + if (x != nodeProps.end()) { + const Variant::Map& xProps = x->second.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::AUTO_DELETE) autoDelete = i->second; + else if (i->first == xamqp::EXCLUSIVE) exclusive = i->second; + else if (i->first == xamqp::ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + else passthrough[i->first] = i->second; + } + translate(passthrough, arguments); + } + } +} + +Exchange::Exchange(const Address& a) : Node(a), + type(TOPIC_EXCHANGE), + typeSpecified(false), + durable(false), + autoDelete(false) +{ + configure(a); +} + +void Exchange::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + try { + sync(session).exchangeDeclare(arg::exchange=name, + arg::type=type, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + } catch (const qpid::Exception& e) { + throw InvalidAddress((boost::format("Could not create exchange %1%; %2%") % name % e.what()).str()); + } + } else { + try { + sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw InvalidAddress((boost::format("Exchange %1% does not exist") % name).str()); + } catch (const std::exception& e) { + throw InvalidAddress(e.what()); + } + } +} + +void Exchange::checkDelete(qpid::client::AsyncSession& session, CheckMode mode) +{ + //Note: exchange-delete will cause a session exception if the + //exchange does not exist, the query here prevents obvious cases + //of this but there is a race whenever two deletions are made + //concurrently so careful use of the delete policy is recommended + //at present + if (enabled(deletePolicy, mode) && !sync(session).exchangeQuery(name).getNotFound()) { + sync(session).exchangeDelete(arg::exchange=name); + } +} + +void Exchange::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(assertPolicy, mode)) { + ExchangeQueryResult result = sync(session).exchangeQuery(name); + if (result.getNotFound()) { + throw InvalidAddress((boost::format("Exchange not found: %1%") % name).str()); + } else { + if (typeSpecified && result.getType() != type) { + throw InvalidAddress((boost::format("Exchange %1% is of incorrect type, expected %2% but got %3%") + % name % type % result.getType()).str()); + } + if (durable && !result.getDurable()) { + throw InvalidAddress((boost::format("Exchange not durable: %1%") % name).str()); + } + //Note: Can't check auto-delete or alternate-exchange via + //exchange-query-result as these are not returned + //TODO: could use a passive declare to check alternate-exchange + for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) { + FieldTable::ValuePtr v = result.getArguments().get(i->first); + if (!v) { + throw InvalidAddress((boost::format("Option %1% not set for %2%") % i->first % name).str()); + } else if (i->second != v) { + throw InvalidAddress((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + } + } +} + +void Exchange::configure(const Address& address) +{ + const Variant& v = address.getOption(NODE_PROPERTIES); + if (!v.isVoid()) { + Variant::Map nodeProps = v.asMap(); + durable = nodeProps[DURABLE]; + Variant::Map::const_iterator x = nodeProps.find(X_PROPERTIES); + if (x != nodeProps.end()) { + const Variant::Map& xProps = x->second.asMap(); + Variant::Map passthrough; + for (Variant::Map::const_iterator i = xProps.begin(); i != xProps.end(); ++i) { + if (i->first == xamqp::AUTO_DELETE) autoDelete = i->second; + else if (i->first == xamqp::EXCHANGE_TYPE) { type = i->second.asString(); typeSpecified = true; } + else if (i->first == xamqp::ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + else passthrough[i->first] = i->second; + } + translate(passthrough, arguments); + } + } +} + + +bool Node::enabled(const Variant& policy, CheckMode mode) +{ + bool result = false; + switch (mode) { + case FOR_RECEIVER: + result = in(policy, RECEIVER_MODES); + break; + case FOR_SENDER: + result = in(policy, SENDER_MODES); + break; + } + return result; +} + +bool Node::createEnabled(const Address& address, CheckMode mode) +{ + const Variant& policy = address.getOption(CREATE); + return enabled(policy, mode); +} + +void Node::convert(const Variant& options, FieldTable& arguments) +{ + if (!options.isVoid()) { + translate(options.asMap(), arguments); + } +} +std::vector<std::string> Node::RECEIVER_MODES = list_of<std::string>(ALWAYS) (RECEIVER); +std::vector<std::string> Node::SENDER_MODES = list_of<std::string>(ALWAYS) (SENDER); + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/AddressResolution.h b/cpp/src/qpid/client/amqp0_10/AddressResolution.h new file mode 100644 index 0000000000..01c8c51595 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/AddressResolution.h @@ -0,0 +1,64 @@ +#ifndef QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H +#define QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H + +/* + * + * 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. + * + */ +#include "qpid/messaging/Variant.h" +#include "qpid/client/Session.h" + +namespace qpid { + +namespace framing{ +class ReplyTo; +} + +namespace messaging { +class Address; +} + +namespace client { +namespace amqp0_10 { + +class MessageSource; +class MessageSink; + +/** + * Maps from a generic Address and optional Filter to an AMQP 0-10 + * MessageSource which will then be used by a ReceiverImpl instance + * created for the address. + */ +class AddressResolution +{ + public: + std::auto_ptr<MessageSource> resolveSource(qpid::client::Session session, + const qpid::messaging::Address& address); + + std::auto_ptr<MessageSink> resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address); + + static qpid::messaging::Address convert(const qpid::framing::ReplyTo&); + static qpid::framing::ReplyTo convert(const qpid::messaging::Address&); + + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/Codecs.cpp b/cpp/src/qpid/client/amqp0_10/Codecs.cpp new file mode 100644 index 0000000000..ff72dfbf4e --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/Codecs.cpp @@ -0,0 +1,299 @@ +/* + * + * 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. + * + */ +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" +#include <algorithm> +#include <functional> + +using namespace qpid::framing; +using namespace qpid::messaging; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +namespace { +const std::string iso885915("iso-8859-15"); +const std::string utf8("utf8"); +const std::string utf16("utf16"); +const std::string amqp0_10_binary("amqp0-10:binary"); +const std::string amqp0_10_bit("amqp0-10:bit"); +const std::string amqp0_10_datetime("amqp0-10:datetime"); +const std::string amqp0_10_struct("amqp0-10:struct"); +} + +template <class T, class U, class F> void convert(const T& from, U& to, F f) +{ + std::transform(from.begin(), from.end(), std::inserter(to, to.begin()), f); +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in); +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in); +Variant toVariant(boost::shared_ptr<FieldValue> in); +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in); + +template <class T, class U, class F> void translate(boost::shared_ptr<FieldValue> in, U& u, F f) +{ + T t; + getEncodedValue<T>(in, t); + convert(t, u, f); +} + +template <class T, class U, class F> T* toFieldValueCollection(const U& u, F f) +{ + typename T::ValueType t; + convert(u, t, f); + return new T(t); +} + +FieldTableValue* toFieldTableValue(const Variant::Map& map) +{ + FieldTable ft; + convert(map, ft, &toFieldTableEntry); + return new FieldTableValue(ft); +} + +ListValue* toListValue(const Variant::List& list) +{ + List l; + convert(list, l, &toFieldValue); + return new ListValue(l); +} + +void setEncodingFor(Variant& out, uint8_t code) +{ + switch(code){ + case 0x80: + case 0x90: + case 0xa0: + out.setEncoding(amqp0_10_binary); + break; + case 0x84: + case 0x94: + out.setEncoding(iso885915); + break; + case 0x85: + case 0x95: + out.setEncoding(utf8); + break; + case 0x86: + case 0x96: + out.setEncoding(utf16); + break; + case 0xab: + out.setEncoding(amqp0_10_struct); + break; + default: + //do nothing + break; + } +} + +Variant toVariant(boost::shared_ptr<FieldValue> in) +{ + Variant out; + //based on AMQP 0-10 typecode, pick most appropriate variant type + switch (in->getType()) { + //Fixed Width types: + case 0x01: out.setEncoding(amqp0_10_binary); + case 0x02: out = in->getIntegerValue<int8_t, 1>(); break; + case 0x03: out = in->getIntegerValue<uint8_t, 1>(); break; + case 0x04: break; //TODO: iso-8859-15 char + case 0x08: out = in->getIntegerValue<bool, 1>(); break; + case 0x010: out.setEncoding(amqp0_10_binary); + case 0x011: out = in->getIntegerValue<int16_t, 2>(); break; + case 0x012: out = in->getIntegerValue<uint16_t, 2>(); break; + case 0x020: out.setEncoding(amqp0_10_binary); + case 0x021: out = in->getIntegerValue<int32_t, 4>(); break; + case 0x022: out = in->getIntegerValue<uint32_t, 4>(); break; + case 0x023: out = in->get<float>(); break; + case 0x027: break; //TODO: utf-32 char + case 0x030: out.setEncoding(amqp0_10_binary); + case 0x031: out = in->getIntegerValue<int64_t, 8>(); break; + case 0x038: out.setEncoding(amqp0_10_datetime); //treat datetime as uint64_t, but set encoding + case 0x032: out = in->getIntegerValue<uint64_t, 8>(); break; + case 0x033:out = in->get<double>(); break; + + //TODO: figure out whether and how to map values with codes 0x40-0xd8 + + case 0xf0: break;//void, which is the default value for Variant + case 0xf1: out.setEncoding(amqp0_10_bit); break;//treat 'bit' as void, which is the default value for Variant + + //Variable Width types: + //strings: + case 0x80: + case 0x84: + case 0x85: + case 0x86: + case 0x90: + case 0x94: + case 0x95: + case 0x96: + case 0xa0: + case 0xab: + setEncodingFor(out, in->getType()); + out = in->get<std::string>(); + break; + + case 0xa8: + out = Variant::Map(); + translate<FieldTable>(in, out.asMap(), &toVariantMapEntry); + break; + + case 0xa9: + out = Variant::List(); + translate<List>(in, out.asList(), &toVariant); + break; + case 0xaa: //convert amqp0-10 array into variant list + out = Variant::List(); + translate<Array>(in, out.asList(), &toVariant); + break; + + default: + //error? + break; + } + return out; +} + +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in) +{ + boost::shared_ptr<FieldValue> out; + switch (in.getType()) { + case VAR_VOID: out = boost::shared_ptr<FieldValue>(new VoidValue()); break; + case VAR_BOOL: out = boost::shared_ptr<FieldValue>(new BoolValue(in.asBool())); break; + case VAR_UINT8: out = boost::shared_ptr<FieldValue>(new Unsigned8Value(in.asUint8())); break; + case VAR_UINT16: out = boost::shared_ptr<FieldValue>(new Unsigned16Value(in.asUint16())); break; + case VAR_UINT32: out = boost::shared_ptr<FieldValue>(new Unsigned32Value(in.asUint32())); break; + case VAR_UINT64: out = boost::shared_ptr<FieldValue>(new Unsigned64Value(in.asUint64())); break; + case VAR_INT8: out = boost::shared_ptr<FieldValue>(new Integer8Value(in.asInt8())); break; + case VAR_INT16: out = boost::shared_ptr<FieldValue>(new Integer16Value(in.asInt16())); break; + case VAR_INT32: out = boost::shared_ptr<FieldValue>(new Integer32Value(in.asInt32())); break; + case VAR_INT64: out = boost::shared_ptr<FieldValue>(new Integer64Value(in.asInt64())); break; + case VAR_FLOAT: out = boost::shared_ptr<FieldValue>(new FloatValue(in.asFloat())); break; + case VAR_DOUBLE: out = boost::shared_ptr<FieldValue>(new DoubleValue(in.asDouble())); break; + //TODO: check encoding (and length?) when deciding what AMQP type to treat string as + case VAR_STRING: out = boost::shared_ptr<FieldValue>(new Str16Value(in.asString())); break; + case VAR_MAP: + //out = boost::shared_ptr<FieldValue>(toFieldValueCollection<FieldTableValue>(in.asMap(), &toFieldTableEntry)); + out = boost::shared_ptr<FieldValue>(toFieldTableValue(in.asMap())); + break; + case VAR_LIST: + //out = boost::shared_ptr<FieldValue>(toFieldValueCollection<ListValue>(in.asList(), &toFieldValue)); + out = boost::shared_ptr<FieldValue>(toListValue(in.asList())); + break; + } + return out; +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in) +{ + return Variant::Map::value_type(in.first, toVariant(in.second)); +} + +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in) +{ + return FieldTable::value_type(in.first, toFieldValue(in.second)); +} + +struct EncodeBuffer +{ + char* data; + Buffer buffer; + + EncodeBuffer(size_t size) : data(new char[size]), buffer(data, size) {} + ~EncodeBuffer() { delete[] data; } + + template <class T> void encode(T& t) { t.encode(buffer); } + + void getData(std::string& s) { + s.assign(data, buffer.getSize()); + } +}; + +struct DecodeBuffer +{ + Buffer buffer; + + DecodeBuffer(const std::string& s) : buffer(const_cast<char*>(s.data()), s.size()) {} + + template <class T> void decode(T& t) { t.decode(buffer); } + +}; + +template <class T, class U, class F> void _encode(const U& value, std::string& data, F f) +{ + T t; + convert(value, t, f); + EncodeBuffer buffer(t.encodedSize()); + buffer.encode(t); + buffer.getData(data); +} + +template <class T, class U, class F> void _decode(const std::string& data, U& value, F f) +{ + T t; + DecodeBuffer buffer(data); + buffer.decode(t); + convert(t, value, f); +} + +void MapCodec::encode(const Variant& value, std::string& data) +{ + _encode<FieldTable>(value.asMap(), data, &toFieldTableEntry); +} + +void MapCodec::decode(const std::string& data, Variant& value) +{ + value = Variant::Map(); + _decode<FieldTable>(data, value.asMap(), &toVariantMapEntry); +} + +void ListCodec::encode(const Variant& value, std::string& data) +{ + _encode<List>(value.asList(), data, &toFieldValue); +} + +void ListCodec::decode(const std::string& data, Variant& value) +{ + value = Variant::List(); + _decode<List>(data, value.asList(), &toVariant); +} + +void translate(const Variant::Map& from, FieldTable& to) +{ + convert(from, to, &toFieldTableEntry); +} + +void translate(const FieldTable& from, Variant::Map& to) +{ + convert(from, to, &toVariantMapEntry); +} + +const std::string ListCodec::contentType("amqp/list"); +const std::string MapCodec::contentType("amqp/map"); + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/MessageQueue.h b/cpp/src/qpid/client/amqp0_10/CodecsInternal.h index ab6d351ba7..b5a561a9c3 100644 --- a/cpp/src/qpid/client/MessageQueue.h +++ b/cpp/src/qpid/client/amqp0_10/CodecsInternal.h @@ -1,3 +1,6 @@ +#ifndef QPID_CLIENT_AMQP0_10_CODECSINTERNAL_H +#define QPID_CLIENT_AMQP0_10_CODECSINTERNAL_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,33 +21,21 @@ * under the License. * */ - -#ifndef _MessageQueue_ -#define _MessageQueue_ -#include <iostream> -#include "qpid/sys/BlockingQueue.h" -#include "MessageListener.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/FieldTable.h" namespace qpid { namespace client { +namespace amqp0_10 { /** - * A MessageListener implementation that queues up - * messages. - * - * \ingroup clientapi + * Declarations of a couple of conversion functions implemented in + * Codecs.cpp but not exposed through API */ -class MessageQueue : public sys::BlockingQueue<Message>, public MessageListener -{ - public: - void received(Message& msg) - { - push(msg); - } -}; -} -} +void translate(const qpid::messaging::Variant::Map& from, qpid::framing::FieldTable& to); +void translate(const qpid::framing::FieldTable& from, qpid::messaging::Variant::Map& to); +}}} // namespace qpid::client::amqp0_10 -#endif +#endif /*!QPID_CLIENT_AMQP0_10_CODECSINTERNAL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp new file mode 100644 index 0000000000..cd5c0214e3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -0,0 +1,208 @@ +/* + * + * 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. + * + */ +#include "ConnectionImpl.h" +#include "SessionImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include <boost/intrusive_ptr.hpp> +#include <vector> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Variant; +using qpid::framing::Uuid; +using namespace qpid::sys; + +template <class T> void setIfFound(const Variant::Map& map, const std::string& key, T& value) +{ + Variant::Map::const_iterator i = map.find(key); + if (i != map.end()) { + value = (T) i->second; + } +} + +void convert(const Variant::Map& from, ConnectionSettings& to) +{ + setIfFound(from, "username", to.username); + setIfFound(from, "password", to.password); + setIfFound(from, "sasl-mechanism", to.mechanism); + setIfFound(from, "sasl-service", to.service); + setIfFound(from, "sasl-min-ssf", to.minSsf); + setIfFound(from, "sasl-max-ssf", to.maxSsf); + + setIfFound(from, "heartbeat", to.heartbeat); + setIfFound(from, "tcp-nodelay", to.tcpNoDelay); + + setIfFound(from, "locale", to.locale); + setIfFound(from, "max-channels", to.maxChannels); + setIfFound(from, "max-frame-size", to.maxFrameSize); + setIfFound(from, "bounds", to.bounds); +} + +ConnectionImpl::ConnectionImpl(const std::string& u, const Variant::Map& options) : + url(u), reconnectionEnabled(true), timeout(-1), + minRetryInterval(1), maxRetryInterval(30) +{ + QPID_LOG(debug, "Opening connection to " << url << " with " << options); + convert(options, settings); + setIfFound(options, "reconnection-enabled", reconnectionEnabled); + setIfFound(options, "reconnection-timeout", timeout); + setIfFound(options, "min-retry-interval", minRetryInterval); + setIfFound(options, "max-retry-interval", maxRetryInterval); + connection.open(url, settings); +} + +void ConnectionImpl::close() +{ + std::vector<std::string> names; + { + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::const_iterator i = sessions.begin(); i != sessions.end(); ++i) names.push_back(i->first); + } + for (std::vector<std::string>::const_iterator i = names.begin(); i != names.end(); ++i) { + getSession(*i).close(); + } + + qpid::sys::Mutex::ScopedLock l(lock); + connection.close(); +} + +boost::intrusive_ptr<SessionImpl> getImplPtr(qpid::messaging::Session& session) +{ + return boost::dynamic_pointer_cast<SessionImpl>( + qpid::client::PrivateImplRef<qpid::messaging::Session>::get(session) + ); +} + +void ConnectionImpl::closed(SessionImpl& s) +{ + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + if (getImplPtr(i->second).get() == &s) { + sessions.erase(i); + break; + } + } +} + +qpid::messaging::Session ConnectionImpl::getSession(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Sessions::const_iterator i = sessions.find(name); + if (i == sessions.end()) { + throw qpid::messaging::KeyError("No such session: " + name); + } else { + return i->second; + } +} + +qpid::messaging::Session ConnectionImpl::newSession(bool transactional, const std::string& n) +{ + std::string name = n.empty() ? Uuid(true).str() : n; + qpid::messaging::Session impl(new SessionImpl(*this, transactional)); + { + qpid::sys::Mutex::ScopedLock l(lock); + sessions[name] = impl; + } + try { + getImplPtr(impl)->setSession(connection.newSession(name)); + } catch (const TransportFailure&) { + reconnect(); + } + return impl; +} + +void ConnectionImpl::reconnect() +{ + AbsTime start = now(); + ScopedLock<Semaphore> l(semaphore); + if (!connection.isOpen()) connect(start); +} + +bool expired(const AbsTime& start, int timeout) +{ + if (timeout == 0) return true; + if (timeout < 0) return false; + Duration used(start, now()); + Duration allowed = timeout * TIME_SEC; + return allowed > used; +} + +void ConnectionImpl::connect(const AbsTime& started) +{ + for (int i = minRetryInterval; !tryConnect(); i = std::min(i * 2, maxRetryInterval)) { + if (expired(started, timeout)) throw TransportFailure(); + else qpid::sys::sleep(i); + } +} + +bool ConnectionImpl::tryConnect() +{ + if (tryConnect(url) || + (failoverListener.get() && tryConnect(failoverListener->getKnownBrokers()))) + { + return resetSessions(); + } else { + return false; + } +} + +bool ConnectionImpl::tryConnect(const Url& u) +{ + try { + QPID_LOG(info, "Trying to connect to " << url << "..."); + connection.open(u, settings); + failoverListener.reset(new FailoverListener(connection)); + return true; + } catch (const Exception& e) { + //TODO: need to fix timeout on open so that it throws TransportFailure + QPID_LOG(info, "Failed to connect to " << u << ": " << e.what()); + } + return false; +} + +bool ConnectionImpl::tryConnect(const std::vector<Url>& urls) +{ + for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end(); ++i) { + if (tryConnect(*i)) return true; + } + return false; +} + +bool ConnectionImpl::resetSessions() +{ + try { + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + getImplPtr(i->second)->setSession(connection.newSession(i->first)); + } + return true; + } catch (const TransportFailure&) { + QPID_LOG(debug, "Connection failed while re-inialising sessions"); + return false; + } +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h new file mode 100644 index 0000000000..979cc6c82a --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -0,0 +1,72 @@ +#ifndef QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H +#define QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H + +/* + * + * 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. + * + */ +#include "qpid/messaging/ConnectionImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/Url.h" +#include "qpid/client/Connection.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Semaphore.h" +#include <map> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class SessionImpl; + +class ConnectionImpl : public qpid::messaging::ConnectionImpl +{ + public: + ConnectionImpl(const std::string& url, const qpid::messaging::Variant::Map& options); + void close(); + qpid::messaging::Session newSession(bool transactional, const std::string& name); + qpid::messaging::Session getSession(const std::string& name) const; + void closed(SessionImpl&); + void reconnect(); + private: + typedef std::map<std::string, qpid::messaging::Session> Sessions; + + mutable qpid::sys::Mutex lock;//used to protect data structures + qpid::sys::Semaphore semaphore;//used to coordinate reconnection + Sessions sessions; + qpid::client::Connection connection; + std::auto_ptr<FailoverListener> failoverListener; + qpid::Url url; + qpid::client::ConnectionSettings settings; + bool reconnectionEnabled; + int timeout; + int minRetryInterval; + int maxRetryInterval; + + void connect(const qpid::sys::AbsTime& started); + bool tryConnect(); + bool tryConnect(const std::vector<Url>& urls); + bool tryConnect(const Url&); + bool resetSessions(); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp b/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp new file mode 100644 index 0000000000..e66dc5915c --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp @@ -0,0 +1,303 @@ +/* + * + * 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. + * + */ +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/CodecsInternal.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using namespace qpid::framing; +using namespace qpid::framing::message; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::messaging::MessageImplAccess; +using qpid::messaging::Variant; + +namespace { +const std::string EMPTY_STRING; + + +struct GetNone : IncomingMessages::Handler +{ + bool accept(IncomingMessages::MessageTransfer&) { return false; } +}; + +struct GetAny : IncomingMessages::Handler +{ + bool accept(IncomingMessages::MessageTransfer& transfer) + { + transfer.retrieve(0); + return true; + } +}; + +struct MatchAndTrack +{ + const std::string destination; + SequenceSet ids; + + MatchAndTrack(const std::string& d) : destination(d) {} + + bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command) + { + if (command->as<MessageTransferBody>()->getDestination() == destination) { + ids.add(command->getId()); + return true; + } else { + return false; + } + } +}; + +struct Match +{ + const std::string destination; + uint32_t matched; + + Match(const std::string& d) : destination(d), matched(0) {} + + bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command) + { + if (command->as<MessageTransferBody>()->getDestination() == destination) { + ++matched; + return true; + } else { + return false; + } + } +}; +} + +void IncomingMessages::setSession(qpid::client::AsyncSession s) +{ + session = s; + incoming = SessionBase_0_10Access(session).get()->getDemux().getDefault(); + acceptTracker.reset(); +} + +bool IncomingMessages::get(Handler& handler, Duration timeout) +{ + //search through received list for any transfer of interest: + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i++) + { + MessageTransfer transfer(*i, *this); + if (handler.accept(transfer)) { + received.erase(i); + return true; + } + } + //none found, check incoming: + return process(&handler, timeout); +} + +bool IncomingMessages::getNextDestination(std::string& destination, Duration timeout) +{ + //if there is not already a received message, we must wait for one + if (received.empty() && !wait(timeout)) return false; + //else we have a message in received; return the corresponding destination + destination = received.front()->as<MessageTransferBody>()->getDestination(); + return true; +} + +void IncomingMessages::accept() +{ + acceptTracker.accept(session); +} + +void IncomingMessages::releaseAll() +{ + //first process any received messages... + while (!received.empty()) { + retrieve(received.front(), 0); + received.pop_front(); + } + //then pump out any available messages from incoming queue... + GetAny handler; + while (process(&handler, 0)) ; + //now release all messages + acceptTracker.release(session); +} + +void IncomingMessages::releasePending(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) ; + + //now remove all messages for this destination from received list, recording their ids... + MatchAndTrack match(destination); + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i = match(*i) ? received.erase(i) : ++i) ; + //now release those messages + session.messageRelease(match.ids); +} + +/** + * Get a frameset that is accepted by the specified handler from + * session queue, waiting for up to the specified duration and + * returning true if this could be achieved, false otherwise. Messages + * that are not accepted by the handler are pushed onto received queue + * for later retrieval. + */ +bool IncomingMessages::process(Handler* handler, qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + MessageTransfer transfer(content, *this); + if (handler && handler->accept(transfer)) { + QPID_LOG(debug, "Delivered " << *content->getMethod()); + return true; + } else { + //received message for another destination, keep for later + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + received.push_back(content); + } + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + return false; +} + +bool IncomingMessages::wait(qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + received.push_back(content); + return true; + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + return false; +} + +uint32_t IncomingMessages::pendingAccept() +{ + return acceptTracker.acceptsPending(); +} +uint32_t IncomingMessages::pendingAccept(const std::string& destination) +{ + return acceptTracker.acceptsPending(destination); +} + +uint32_t IncomingMessages::available() +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) {} + //return the count of received messages + return received.size(); +} + +uint32_t IncomingMessages::available(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) {} + + //count all messages for this destination from received list + return std::for_each(received.begin(), received.end(), Match(destination)).matched; +} + +void populate(qpid::messaging::Message& message, FrameSet& command); + +/** + * Called when message is retrieved; records retrieval for subsequent + * acceptance, marks the command as completed and converts command to + * message if message is required + */ +void IncomingMessages::retrieve(FrameSetPtr command, qpid::messaging::Message* message) +{ + if (message) { + populate(*message, *command); + } + const MessageTransferBody* transfer = command->as<MessageTransferBody>(); + if (transfer->getAcquireMode() == ACQUIRE_MODE_PRE_ACQUIRED && transfer->getAcceptMode() == ACCEPT_MODE_EXPLICIT) { + acceptTracker.delivered(transfer->getDestination(), command->getId()); + } + session.markCompleted(command->getId(), false, false); +} + +IncomingMessages::MessageTransfer::MessageTransfer(FrameSetPtr c, IncomingMessages& p) : content(c), parent(p) {} + +const std::string& IncomingMessages::MessageTransfer::getDestination() +{ + return content->as<MessageTransferBody>()->getDestination(); +} +void IncomingMessages::MessageTransfer::retrieve(qpid::messaging::Message* message) +{ + parent.retrieve(content, message); +} + +void populateHeaders(qpid::messaging::Message& message, + const DeliveryProperties* deliveryProperties, + const MessageProperties* messageProperties) +{ + if (deliveryProperties) { + message.setSubject(deliveryProperties->getRoutingKey()); + //TODO: convert other delivery properties + } + if (messageProperties) { + message.setContentType(messageProperties->getContentType()); + if (messageProperties->hasReplyTo()) { + message.setReplyTo(AddressResolution::convert(messageProperties->getReplyTo())); + } + message.getHeaders().clear(); + translate(messageProperties->getApplicationHeaders(), message.getHeaders()); + //TODO: convert other message properties + } +} + +void populateHeaders(qpid::messaging::Message& message, const AMQHeaderBody* headers) +{ + populateHeaders(message, headers->get<DeliveryProperties>(), headers->get<MessageProperties>()); +} + +void populate(qpid::messaging::Message& message, FrameSet& command) +{ + //need to be able to link the message back to the transfer it was delivered by + //e.g. for rejecting. + MessageImplAccess::get(message).setInternalId(command.getId()); + + command.getContent(message.getContent()); + + populateHeaders(message, command.getHeaders()); +} + + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/IncomingMessages.h b/cpp/src/qpid/client/amqp0_10/IncomingMessages.h new file mode 100644 index 0000000000..2bc6dd49c4 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/IncomingMessages.h @@ -0,0 +1,98 @@ +#ifndef QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H +#define QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H + +/* + * + * 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. + * + */ +#include <string> +#include <boost/shared_ptr.hpp> +#include "qpid/client/AsyncSession.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/sys/Time.h" +#include "qpid/client/amqp0_10/AcceptTracker.h" + +namespace qpid { + +namespace framing{ +class FrameSet; +} + +namespace messaging { +class Message; +} + +namespace client { +namespace amqp0_10 { + +/** + * + */ +class IncomingMessages +{ + public: + typedef boost::shared_ptr<qpid::framing::FrameSet> FrameSetPtr; + class MessageTransfer + { + public: + const std::string& getDestination(); + void retrieve(qpid::messaging::Message* message); + private: + FrameSetPtr content; + IncomingMessages& parent; + + MessageTransfer(FrameSetPtr, IncomingMessages&); + friend class IncomingMessages; + }; + + struct Handler + { + virtual ~Handler() {} + virtual bool accept(MessageTransfer& transfer) = 0; + }; + + void setSession(qpid::client::AsyncSession session); + bool get(Handler& handler, qpid::sys::Duration timeout); + bool getNextDestination(std::string& destination, qpid::sys::Duration timeout); + void accept(); + void releaseAll(); + void releasePending(const std::string& destination); + + uint32_t pendingAccept(); + uint32_t pendingAccept(const std::string& destination); + + uint32_t available(); + uint32_t available(const std::string& destination); + private: + typedef std::deque<FrameSetPtr> FrameSetQueue; + + qpid::client::AsyncSession session; + boost::shared_ptr< sys::BlockingQueue<FrameSetPtr> > incoming; + FrameSetQueue received; + AcceptTracker acceptTracker; + + bool process(Handler*, qpid::sys::Duration); + bool wait(qpid::sys::Duration); + void retrieve(FrameSetPtr, qpid::messaging::Message*); + +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H*/ diff --git a/cpp/src/qpid/client/AckPolicy.h b/cpp/src/qpid/client/amqp0_10/MessageSink.h index b34f1d15d1..8d87a3c7bb 100644 --- a/cpp/src/qpid/client/AckPolicy.h +++ b/cpp/src/qpid/client/amqp0_10/MessageSink.h @@ -1,7 +1,8 @@ -#ifndef QPID_CLIENT_ACKPOLICY_H -#define QPID_CLIENT_ACKPOLICY_H +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESINK_H +#define QPID_CLIENT_AMQP0_10_MESSAGESINK_H /* + * * 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 @@ -9,9 +10,9 @@ * 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 @@ -20,39 +21,32 @@ * under the License. * */ - -#include "qpid/framing/SequenceSet.h" +#include <string> #include "qpid/client/AsyncSession.h" -#include "qpid/client/Message.h" namespace qpid { + +namespace messaging { +class Message; +} + namespace client { +namespace amqp0_10 { + +struct OutgoingMessage; /** - * Policy for automatic acknowledgement of messages. - * * - * \ingroup clientapi */ -class AckPolicy +class MessageSink { - framing::SequenceSet accepted; - size_t interval; - size_t count; - public: - /** - * Sends accepts and marks completion of received transfers. - * - *@param n: acknowledge every n messages. - *n==0 means no automatic acknowledgement. - */ - AckPolicy(size_t n=1); - void ack(const Message& msg, AsyncSession session); - void ackOutstanding(AsyncSession session);}; - -}} // namespace qpid::client - - - -#endif /*!QPID_CLIENT_ACKPOLICY_H*/ + virtual ~MessageSink() {} + virtual void declare(qpid::client::AsyncSession& session, const std::string& name) = 0; + virtual void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message) = 0; + virtual void cancel(qpid::client::AsyncSession& session, const std::string& name) = 0; + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESINK_H*/ diff --git a/cpp/src/qpid/client/MessageListener.h b/cpp/src/qpid/client/amqp0_10/MessageSource.h index 76e1d17445..74f2732f59 100644 --- a/cpp/src/qpid/client/MessageListener.h +++ b/cpp/src/qpid/client/amqp0_10/MessageSource.h @@ -1,3 +1,6 @@ +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H +#define QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -19,35 +22,26 @@ * */ #include <string> - -#ifndef _MessageListener_ -#define _MessageListener_ - -#include "Message.h" +#include "qpid/client/AsyncSession.h" namespace qpid { namespace client { +namespace amqp0_10 { - /** - * Implement a subclass of MessageListener and subscribe it using - * the SubscriptionManager to receive messages. - * - * Another way to receive messages is by using a LocalQueue. - * - * \ingroup clientapi - */ - class MessageListener{ - public: - virtual ~MessageListener(); - - /** Called for each message arriving from the broker. Override - * in your own subclass to process messages. - */ - virtual void received(Message& msg) = 0; - }; - -} -} - - -#endif +/** + * Abstraction behind which the AMQP 0-10 commands required to + * establish (and tear down) an incoming stream of messages from a + * given address are hidden. + */ +class MessageSource +{ + public: + virtual ~MessageSource() {} + virtual void subscribe(qpid::client::AsyncSession& session, const std::string& destination) = 0; + virtual void cancel(qpid::client::AsyncSession& session, const std::string& destination) = 0; + + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp new file mode 100644 index 0000000000..abd4f4c28c --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp @@ -0,0 +1,67 @@ +/* + * + * 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. + * + */ +#include "qpid/client/amqp0_10/OutgoingMessage.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/CodecsInternal.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Address; +using qpid::messaging::MessageImplAccess; + +void OutgoingMessage::convert(const qpid::messaging::Message& from) +{ + //TODO: need to avoid copying as much as possible + message.setData(from.getContent()); + message.getMessageProperties().setContentType(from.getContentType()); + const Address& address = from.getReplyTo(); + if (address) { + message.getMessageProperties().setReplyTo(AddressResolution::convert(address)); + } + translate(from.getHeaders(), message.getMessageProperties().getApplicationHeaders()); + //TODO: set other message properties + message.getDeliveryProperties().setRoutingKey(from.getSubject()); + //TODO: set other delivery properties +} + +namespace { +const std::string SUBJECT("subject"); +} + +void OutgoingMessage::setSubject(const std::string& subject) +{ + if (!subject.empty()) { + message.getMessageProperties().getApplicationHeaders().setString(SUBJECT, subject); + } +} + +std::string OutgoingMessage::getSubject() const +{ + return message.getMessageProperties().getApplicationHeaders().getAsString(SUBJECT); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/FutureCompletion.h b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h index 4248ddeab8..0cdd2a2336 100644 --- a/cpp/src/qpid/client/FutureCompletion.h +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h @@ -1,3 +1,6 @@ +#ifndef QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H +#define QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,32 +21,28 @@ * under the License. * */ - -#ifndef _FutureCompletion_ -#define _FutureCompletion_ - -#include "qpid/framing/amqp_framing.h" -#include "qpid/sys/Monitor.h" +#include "qpid/client/Completion.h" +#include "qpid/client/Message.h" namespace qpid { +namespace messaging { +class Message; +} namespace client { +namespace amqp0_10 { -///@internal -class FutureCompletion +struct OutgoingMessage { -protected: - mutable sys::Monitor lock; - bool complete; - -public: - FutureCompletion(); - virtual ~FutureCompletion(){} - bool isComplete() const; - void waitForCompletion() const; - void completed(); + qpid::client::Message message; + qpid::client::Completion status; + + void convert(const qpid::messaging::Message&); + void setSubject(const std::string& subject); + std::string getSubject() const; }; -}} -#endif +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp new file mode 100644 index 0000000000..bc5c53fde6 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp @@ -0,0 +1,194 @@ +/* + * + * 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. + * + */ +#include "ReceiverImpl.h" +#include "AddressResolution.h" +#include "MessageSource.h" +#include "SessionImpl.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Receiver; + +void ReceiverImpl::received(qpid::messaging::Message&) +{ + //TODO: should this be configurable + if (capacity && --window <= capacity/2) { + session.sendCompletion(); + window = capacity; + } +} + +qpid::messaging::Message ReceiverImpl::get(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!get(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +qpid::messaging::Message ReceiverImpl::fetch(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +bool ReceiverImpl::get(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + Get f(*this, message, timeout); + while (!parent.execute(f)) {} + return f.result; +} + +bool ReceiverImpl::fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + Fetch f(*this, message, timeout); + while (!parent.execute(f)) {} + return f.result; +} + +void ReceiverImpl::cancel() +{ + execute<Cancel>(); +} + +void ReceiverImpl::start() +{ + if (state == STOPPED) { + state = STARTED; + startFlow(); + } +} + +void ReceiverImpl::stop() +{ + state = STOPPED; + session.messageStop(destination); +} + +void ReceiverImpl::setCapacity(uint32_t c) +{ + execute1<SetCapacity>(c); +} + +void ReceiverImpl::startFlow() +{ + if (capacity > 0) { + session.messageSetFlowMode(destination, FLOW_MODE_WINDOW); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, capacity); + session.messageFlow(destination, CREDIT_UNIT_BYTE, byteCredit); + window = capacity; + } +} + +void ReceiverImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + + session = s; + if (state == UNRESOLVED) { + source = resolver.resolveSource(session, address); + state = STARTED; + } + if (state == CANCELLED) { + source->cancel(session, destination); + parent.receiverCancelled(destination); + } else { + source->subscribe(session, destination); + start(); + } +} + + +const std::string& ReceiverImpl::getName() const { return destination; } + +uint32_t ReceiverImpl::getCapacity() +{ + return capacity; +} + +uint32_t ReceiverImpl::available() +{ + return parent.available(destination); +} + +uint32_t ReceiverImpl::pendingAck() +{ + return parent.pendingAck(destination); +} + +ReceiverImpl::ReceiverImpl(SessionImpl& p, const std::string& name, + const qpid::messaging::Address& a) : + + parent(p), destination(name), address(a), byteCredit(0xFFFFFFFF), + state(UNRESOLVED), capacity(0), window(0) {} + +bool ReceiverImpl::getImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + return parent.get(*this, message, timeout); +} + +bool ReceiverImpl::fetchImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + if (state == CANCELLED) return false;//TODO: or should this be an error? + + if (capacity == 0 || state != STARTED) { + session.messageSetFlowMode(destination, FLOW_MODE_CREDIT); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, 1); + session.messageFlow(destination, CREDIT_UNIT_BYTE, 0xFFFFFFFF); + } + + if (getImpl(message, timeout)) { + return true; + } else { + sync(session).messageFlush(destination); + startFlow();//reallocate credit + return getImpl(message, 0); + } +} + +void ReceiverImpl::cancelImpl() +{ + if (state != CANCELLED) { + state = CANCELLED; + source->cancel(session, destination); + parent.receiverCancelled(destination); + } +} + +void ReceiverImpl::setCapacityImpl(uint32_t c) +{ + if (c != capacity) { + capacity = c; + if (state == STARTED) { + session.messageStop(destination); + startFlow(); + } + } +} +qpid::messaging::Session ReceiverImpl::getSession() const +{ + return qpid::messaging::Session(&parent); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h new file mode 100644 index 0000000000..d40aac4058 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -0,0 +1,148 @@ +#ifndef QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H +#define QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H + +/* + * + * 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. + * + */ +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include "qpid/sys/Time.h" +#include <memory> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class AddressResolution; +class MessageSource; + +/** + * A receiver implementation based on an AMQP 0-10 subscription. + */ +class ReceiverImpl : public qpid::messaging::ReceiverImpl +{ + public: + + enum State {UNRESOLVED, STOPPED, STARTED, CANCELLED}; + + ReceiverImpl(SessionImpl& parent, const std::string& name, + const qpid::messaging::Address& address); + + void init(qpid::client::AsyncSession session, AddressResolution& resolver); + bool get(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message get(qpid::sys::Duration timeout); + bool fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message fetch(qpid::sys::Duration timeout); + void cancel(); + void start(); + void stop(); + const std::string& getName() const; + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t available(); + uint32_t pendingAck(); + void received(qpid::messaging::Message& message); + qpid::messaging::Session getSession() const; + private: + SessionImpl& parent; + const std::string destination; + const qpid::messaging::Address address; + const uint32_t byteCredit; + State state; + + std::auto_ptr<MessageSource> source; + uint32_t capacity; + qpid::client::AsyncSession session; + qpid::messaging::MessageListener* listener; + uint32_t window; + + void startFlow(); + //implementation of public facing methods + bool fetchImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout); + bool getImpl(qpid::messaging::Message& message, qpid::sys::Duration timeout); + void cancelImpl(); + void setCapacityImpl(uint32_t); + + //functors for public facing methods (allows locking and retry + //logic to be centralised) + struct Command + { + ReceiverImpl& impl; + + Command(ReceiverImpl& i) : impl(i) {} + }; + + struct Get : Command + { + qpid::messaging::Message& message; + qpid::sys::Duration timeout; + bool result; + + Get(ReceiverImpl& i, qpid::messaging::Message& m, qpid::sys::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.getImpl(message, timeout); } + }; + + struct Fetch : Command + { + qpid::messaging::Message& message; + qpid::sys::Duration timeout; + bool result; + + Fetch(ReceiverImpl& i, qpid::messaging::Message& m, qpid::sys::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.fetchImpl(message, timeout); } + }; + + struct Cancel : Command + { + Cancel(ReceiverImpl& i) : Command(i) {} + void operator()() { impl.cancelImpl(); } + }; + + struct SetCapacity : Command + { + uint32_t capacity; + + SetCapacity(ReceiverImpl& i, uint32_t c) : Command(i), capacity(c) {} + void operator()() { impl.setCapacityImpl(capacity); } + }; + + //helper templates for some common patterns + template <class F> void execute() + { + F f(*this); + parent.execute(f); + } + + template <class F, class P> void execute1(P p) + { + F f(*this, p); + parent.execute(f); + } +}; + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp new file mode 100644 index 0000000000..4d6b9869e6 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -0,0 +1,144 @@ +/* + * + * 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. + * + */ +#include "SenderImpl.h" +#include "MessageSink.h" +#include "SessionImpl.h" +#include "AddressResolution.h" +#include "OutgoingMessage.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name, + const qpid::messaging::Address& _address) : + parent(_parent), name(_name), address(_address), state(UNRESOLVED), + capacity(50), window(0), flushed(false) {} + +void SenderImpl::send(const qpid::messaging::Message& message) +{ + Send f(*this, &message); + while (f.repeat) parent.execute(f); +} + +void SenderImpl::cancel() +{ + execute<Cancel>(); +} + +void SenderImpl::setCapacity(uint32_t c) +{ + bool flush = c < capacity; + capacity = c; + execute1<CheckPendingSends>(flush); +} +uint32_t SenderImpl::getCapacity() { return capacity; } +uint32_t SenderImpl::pending() +{ + CheckPendingSends f(*this, false); + parent.execute(f); + return f.pending; +} + +void SenderImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + session = s; + if (state == UNRESOLVED) { + sink = resolver.resolveSink(session, address); + state = ACTIVE; + } + if (state == CANCELLED) { + sink->cancel(session, name); + parent.senderCancelled(name); + } else { + sink->declare(session, name); + replay(); + } +} + +void SenderImpl::waitForCapacity() +{ + //TODO: add option to throw exception rather than blocking? + if (capacity <= (flushed ? checkPendingSends(false) : outgoing.size())) { + //Initial implementation is very basic. As outgoing is + //currently only reduced on receiving completions and we are + //blocking anyway we may as well sync(). If successful that + //should clear all outstanding sends. + session.sync(); + checkPendingSends(false); + } + //flush periodically and check for conmpleted sends + if (++window > (capacity / 4)) {//TODO: make this configurable? + checkPendingSends(true); + window = 0; + } +} + +void SenderImpl::sendImpl(const qpid::messaging::Message& m) +{ + //TODO: make recording for replay optional (would still want to track completion however) + std::auto_ptr<OutgoingMessage> msg(new OutgoingMessage()); + msg->convert(m); + msg->setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject()); + outgoing.push_back(msg.release()); + sink->send(session, name, outgoing.back()); +} + +void SenderImpl::replay() +{ + for (OutgoingMessages::iterator i = outgoing.begin(); i != outgoing.end(); ++i) { + sink->send(session, name, *i); + } +} + +uint32_t SenderImpl::checkPendingSends(bool flush) +{ + if (flush) { + session.flush(); + flushed = true; + } else { + flushed = false; + } + while (!outgoing.empty() && outgoing.front().status.isComplete()) { + outgoing.pop_front(); + } + return outgoing.size(); +} + +void SenderImpl::cancelImpl() +{ + state = CANCELLED; + sink->cancel(session, name); + parent.senderCancelled(name); +} + +const std::string& SenderImpl::getName() const +{ + return name; +} + +qpid::messaging::Session SenderImpl::getSession() const +{ + return qpid::messaging::Session(&parent); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/cpp/src/qpid/client/amqp0_10/SenderImpl.h new file mode 100644 index 0000000000..80d9843d9e --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -0,0 +1,140 @@ +#ifndef QPID_CLIENT_AMQP0_10_SENDERIMPL_H +#define QPID_CLIENT_AMQP0_10_SENDERIMPL_H + +/* + * + * 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. + * + */ +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include <memory> +#include <boost/ptr_container/ptr_deque.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class AddressResolution; +class MessageSink; +struct OutgoingMessage; + +/** + * + */ +class SenderImpl : public qpid::messaging::SenderImpl +{ + public: + enum State {UNRESOLVED, ACTIVE, CANCELLED}; + + SenderImpl(SessionImpl& parent, const std::string& name, + const qpid::messaging::Address& address); + void send(const qpid::messaging::Message&); + void cancel(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t pending(); + void init(qpid::client::AsyncSession, AddressResolution&); + const std::string& getName() const; + qpid::messaging::Session getSession() const; + + private: + SessionImpl& parent; + const std::string name; + const qpid::messaging::Address address; + State state; + std::auto_ptr<MessageSink> sink; + + qpid::client::AsyncSession session; + std::string destination; + std::string routingKey; + + typedef boost::ptr_deque<OutgoingMessage> OutgoingMessages; + OutgoingMessages outgoing; + uint32_t capacity; + uint32_t window; + bool flushed; + + uint32_t checkPendingSends(bool flush); + void replay(); + void waitForCapacity(); + + //logic for application visible methods: + void sendImpl(const qpid::messaging::Message&); + void cancelImpl(); + + + //functors for application visible methods (allowing locking and + //retry to be centralised): + struct Command + { + SenderImpl& impl; + + Command(SenderImpl& i) : impl(i) {} + }; + + struct Send : Command + { + const qpid::messaging::Message* message; + bool repeat; + + Send(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m), repeat(true) {} + void operator()() + { + impl.waitForCapacity(); + //from this point message will be recorded if there is any + //failure (and replayed) so need not repeat the call + repeat = false; + impl.sendImpl(*message); + } + }; + + struct Cancel : Command + { + Cancel(SenderImpl& i) : Command(i) {} + void operator()() { impl.cancelImpl(); } + }; + + struct CheckPendingSends : Command + { + bool flush; + uint32_t pending; + CheckPendingSends(SenderImpl& i, bool f) : Command(i), flush(f), pending(0) {} + void operator()() { pending = impl.checkPendingSends(flush); } + }; + + //helper templates for some common patterns + template <class F> void execute() + { + F f(*this); + parent.execute(f); + } + + template <class F, class P> bool execute1(P p) + { + F f(*this, p); + return parent.execute(f); + } +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SENDERIMPL_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp b/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp new file mode 100644 index 0000000000..c7dff05dd7 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp @@ -0,0 +1,433 @@ +/* + * + * 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. + * + */ +#include "qpid/client/amqp0_10/SessionImpl.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/client/amqp0_10/ReceiverImpl.h" +#include "qpid/client/amqp0_10/SenderImpl.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" +#include "qpid/framing/reply_exceptions.h" +#include <boost/format.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> + +using qpid::messaging::KeyError; +using qpid::messaging::MessageImplAccess; +using qpid::messaging::Sender; +using qpid::messaging::Receiver; +using qpid::messaging::VariantMap; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SessionImpl::SessionImpl(ConnectionImpl& c, bool t) : connection(c), transactional(t) {} + + +void SessionImpl::sync() +{ + retry<Sync>(); +} + +void SessionImpl::flush() +{ + retry<Flush>(); +} + +void SessionImpl::commit() +{ + if (!execute<Commit>()) { + throw Exception();//TODO: what type? + } +} + +void SessionImpl::rollback() +{ + //If the session fails during this operation, the transaction will + //be rolled back anyway. + execute<Rollback>(); +} + +void SessionImpl::acknowledge() +{ + //Should probably throw an exception on failure here, or indicate + //it through a return type at least. Failure means that the + //message may be redelivered; i.e. the application cannot delete + //any state necessary for preventing reprocessing of the message + execute<Acknowledge>(); +} + +void SessionImpl::reject(qpid::messaging::Message& m) +{ + //Possibly want to somehow indicate failure here as well. Less + //clear need as compared to acknowledge however. + execute1<Reject>(m); +} + +void SessionImpl::close() +{ + //cancel all the senders and receivers (get copy of names and then + //make the calls to avoid modifying maps while iterating over + //them): + std::vector<std::string> s; + std::vector<std::string> r; + { + qpid::sys::Mutex::ScopedLock l(lock); + for (Senders::const_iterator i = senders.begin(); i != senders.end(); ++i) s.push_back(i->first); + for (Receivers::const_iterator i = receivers.begin(); i != receivers.end(); ++i) r.push_back(i->first); + } + for (std::vector<std::string>::const_iterator i = s.begin(); i != s.end(); ++i) getSender(*i).cancel(); + for (std::vector<std::string>::const_iterator i = r.begin(); i != r.end(); ++i) getReceiver(*i).cancel(); + + + connection.closed(*this); + session.close(); +} + +template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t) +{ + return boost::dynamic_pointer_cast<S>(qpid::client::PrivateImplRef<T>::get(t)); +} + +template <class T> void getFreeKey(std::string& key, T& map) +{ + std::string name = key; + int count = 1; + for (typename T::const_iterator i = map.find(name); i != map.end(); i = map.find(name)) { + name = (boost::format("%1%_%2%") % key % ++count).str(); + } + key = name; +} + + +void SessionImpl::setSession(qpid::client::Session s) +{ + qpid::sys::Mutex::ScopedLock l(lock); + session = s; + incoming.setSession(session); + if (transactional) session.txSelect(); + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->init(session, resolver); + } + for (Senders::iterator i = senders.begin(); i != senders.end(); ++i) { + getImplPtr<Sender, SenderImpl>(i->second)->init(session, resolver); + } +} + +struct SessionImpl::CreateReceiver : Command +{ + qpid::messaging::Receiver result; + const qpid::messaging::Address& address; + + CreateReceiver(SessionImpl& i, const qpid::messaging::Address& a) : + Command(i), address(a) {} + void operator()() { result = impl.createReceiverImpl(address); } +}; + +Receiver SessionImpl::createReceiver(const qpid::messaging::Address& address) +{ + return get1<CreateReceiver, Receiver>(address); +} + +Receiver SessionImpl::createReceiverImpl(const qpid::messaging::Address& address) +{ + std::string name = address.getName(); + getFreeKey(name, receivers); + Receiver receiver(new ReceiverImpl(*this, name, address)); + getImplPtr<Receiver, ReceiverImpl>(receiver)->init(session, resolver); + receivers[name] = receiver; + return receiver; +} + +struct SessionImpl::CreateSender : Command +{ + qpid::messaging::Sender result; + const qpid::messaging::Address& address; + + CreateSender(SessionImpl& i, const qpid::messaging::Address& a) : + Command(i), address(a) {} + void operator()() { result = impl.createSenderImpl(address); } +}; + +Sender SessionImpl::createSender(const qpid::messaging::Address& address) +{ + return get1<CreateSender, Sender>(address); +} + +Sender SessionImpl::createSenderImpl(const qpid::messaging::Address& address) +{ + std::string name = address.getName(); + getFreeKey(name, senders); + Sender sender(new SenderImpl(*this, name, address)); + getImplPtr<Sender, SenderImpl>(sender)->init(session, resolver); + senders[name] = sender; + return sender; +} + +Sender SessionImpl::getSender(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Senders::const_iterator i = senders.find(name); + if (i == senders.end()) { + throw KeyError(name); + } else { + return i->second; + } +} + +Receiver SessionImpl::getReceiver(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Receivers::const_iterator i = receivers.find(name); + if (i == receivers.end()) { + throw KeyError(name); + } else { + return i->second; + } +} + +SessionImpl& SessionImpl::convert(qpid::messaging::Session& s) +{ + boost::intrusive_ptr<SessionImpl> impl = getImplPtr<qpid::messaging::Session, SessionImpl>(s); + if (!impl) { + throw qpid::Exception(QPID_MSG("Configuration error; require qpid::client::amqp0_10::SessionImpl")); + } + return *impl; +} + +namespace { + +struct IncomingMessageHandler : IncomingMessages::Handler +{ + typedef boost::function1<bool, IncomingMessages::MessageTransfer&> Callback; + Callback callback; + + IncomingMessageHandler(Callback c) : callback(c) {} + + bool accept(IncomingMessages::MessageTransfer& transfer) + { + return callback(transfer); + } +}; + +} + + +bool SessionImpl::getNextReceiver(Receiver* receiver, IncomingMessages::MessageTransfer& transfer) +{ + Receivers::const_iterator i = receivers.find(transfer.getDestination()); + if (i == receivers.end()) { + QPID_LOG(error, "Received message for unknown destination " << transfer.getDestination()); + return false; + } else { + *receiver = i->second; + return true; + } +} + +bool SessionImpl::accept(ReceiverImpl* receiver, + qpid::messaging::Message* message, + IncomingMessages::MessageTransfer& transfer) +{ + if (receiver->getName() == transfer.getDestination()) { + transfer.retrieve(message); + receiver->received(*message); + return true; + } else { + return false; + } +} + +bool SessionImpl::getIncoming(IncomingMessages::Handler& handler, qpid::sys::Duration timeout) +{ + return incoming.get(handler, timeout); +} + +bool SessionImpl::get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + IncomingMessageHandler handler(boost::bind(&SessionImpl::accept, this, &receiver, &message, _1)); + return getIncoming(handler, timeout); +} + +bool SessionImpl::nextReceiver(qpid::messaging::Receiver& receiver, qpid::sys::Duration timeout) +{ + qpid::sys::Mutex::ScopedLock l(lock); + while (true) { + try { + std::string destination; + if (incoming.getNextDestination(destination, timeout)) { + Receivers::const_iterator i = receivers.find(destination); + if (i == receivers.end()) { + throw qpid::Exception(QPID_MSG("Received message for unknown destination " << destination)); + } else { + receiver = i->second; + } + return true; + } else { + return false; + } + } catch (TransportFailure&) { + reconnect(); + } + } +} + +qpid::messaging::Receiver SessionImpl::nextReceiver(qpid::sys::Duration timeout) +{ + qpid::messaging::Receiver receiver; + if (!nextReceiver(receiver, timeout)) throw Receiver::NoMessageAvailable(); + if (!receiver) throw qpid::Exception("Bad receiver returned!"); + return receiver; +} + +uint32_t SessionImpl::available() +{ + return get1<Available, uint32_t>((const std::string*) 0); +} +uint32_t SessionImpl::available(const std::string& destination) +{ + return get1<Available, uint32_t>(&destination); +} + +struct SessionImpl::Available : Command +{ + const std::string* destination; + uint32_t result; + + Available(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.availableImpl(destination); } +}; + +uint32_t SessionImpl::availableImpl(const std::string* destination) +{ + if (destination) { + return incoming.available(*destination); + } else { + return incoming.available(); + } +} + +uint32_t SessionImpl::pendingAck() +{ + return get1<PendingAck, uint32_t>((const std::string*) 0); +} + +uint32_t SessionImpl::pendingAck(const std::string& destination) +{ + return get1<PendingAck, uint32_t>(&destination); +} + +struct SessionImpl::PendingAck : Command +{ + const std::string* destination; + uint32_t result; + + PendingAck(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.pendingAckImpl(destination); } +}; + +uint32_t SessionImpl::pendingAckImpl(const std::string* destination) +{ + if (destination) { + return incoming.pendingAccept(*destination); + } else { + return incoming.pendingAccept(); + } +} + +void SessionImpl::syncImpl() +{ + session.sync(); +} + +void SessionImpl::flushImpl() +{ + session.flush(); +} + + +void SessionImpl::commitImpl() +{ + incoming.accept(); + session.txCommit(); +} + +void SessionImpl::rollbackImpl() +{ + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->stop(); + } + //ensure that stop has been processed and all previously sent + //messages are available for release: + session.sync(); + incoming.releaseAll(); + session.txRollback(); + + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->start(); + } +} + +void SessionImpl::acknowledgeImpl() +{ + incoming.accept(); +} + +void SessionImpl::rejectImpl(qpid::messaging::Message& m) +{ + SequenceSet set; + set.add(MessageImplAccess::get(m).getInternalId()); + session.messageReject(set); +} + +void SessionImpl::receiverCancelled(const std::string& name) +{ + receivers.erase(name); + session.sync(); + incoming.releasePending(name); +} + +void SessionImpl::senderCancelled(const std::string& name) +{ + senders.erase(name); +} + +void SessionImpl::reconnect() +{ + connection.reconnect(); +} + +qpid::messaging::Connection SessionImpl::getConnection() const +{ + return qpid::messaging::Connection(&connection); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/SessionImpl.h b/cpp/src/qpid/client/amqp0_10/SessionImpl.h new file mode 100644 index 0000000000..96c7ca93a3 --- /dev/null +++ b/cpp/src/qpid/client/amqp0_10/SessionImpl.h @@ -0,0 +1,212 @@ +#ifndef QPID_CLIENT_AMQP0_10_SESSIONIMPL_H +#define QPID_CLIENT_AMQP0_10_SESSIONIMPL_H + +/* + * + * 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. + * + */ +#include "qpid/messaging/SessionImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/sys/Mutex.h" + +namespace qpid { + +namespace messaging { +class Address; +class Connection; +class Message; +class Receiver; +class Sender; +class Session; +} + +namespace client { +namespace amqp0_10 { + +class ConnectionImpl; +class ReceiverImpl; +class SenderImpl; + +/** + * Implementation of the protocol independent Session interface using + * AMQP 0-10. + */ +class SessionImpl : public qpid::messaging::SessionImpl +{ + public: + SessionImpl(ConnectionImpl&, bool transactional); + void commit(); + void rollback(); + void acknowledge(); + void reject(qpid::messaging::Message&); + void close(); + void sync(); + void flush(); + qpid::messaging::Sender createSender(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiver(const qpid::messaging::Address& address); + + qpid::messaging::Sender getSender(const std::string& name) const; + qpid::messaging::Receiver getReceiver(const std::string& name) const; + + bool nextReceiver(qpid::messaging::Receiver& receiver, qpid::sys::Duration timeout); + qpid::messaging::Receiver nextReceiver(qpid::sys::Duration timeout); + + qpid::messaging::Connection getConnection() const; + + bool get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::sys::Duration timeout); + + void receiverCancelled(const std::string& name); + void senderCancelled(const std::string& name); + + uint32_t available(); + uint32_t available(const std::string& destination); + + uint32_t pendingAck(); + uint32_t pendingAck(const std::string& destination); + + void setSession(qpid::client::Session); + + template <class T> bool execute(T& f) + { + try { + qpid::sys::Mutex::ScopedLock l(lock); + f(); + return true; + } catch (TransportFailure&) { + reconnect(); + return false; + } + } + + static SessionImpl& convert(qpid::messaging::Session&); + + private: + typedef std::map<std::string, qpid::messaging::Receiver> Receivers; + typedef std::map<std::string, qpid::messaging::Sender> Senders; + + mutable qpid::sys::Mutex lock; + ConnectionImpl& connection; + qpid::client::Session session; + AddressResolution resolver; + IncomingMessages incoming; + Receivers receivers; + Senders senders; + const bool transactional; + + bool accept(ReceiverImpl*, qpid::messaging::Message*, IncomingMessages::MessageTransfer&); + bool getIncoming(IncomingMessages::Handler& handler, qpid::sys::Duration timeout); + bool getNextReceiver(qpid::messaging::Receiver* receiver, IncomingMessages::MessageTransfer& transfer); + void reconnect(); + + void commitImpl(); + void rollbackImpl(); + void acknowledgeImpl(); + void rejectImpl(qpid::messaging::Message&); + void closeImpl(); + void syncImpl(); + void flushImpl(); + qpid::messaging::Sender createSenderImpl(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiverImpl(const qpid::messaging::Address& address); + uint32_t availableImpl(const std::string* destination); + uint32_t pendingAckImpl(const std::string* destination); + + //functors for public facing methods (allows locking and retry + //logic to be centralised) + struct Command + { + SessionImpl& impl; + + Command(SessionImpl& i) : impl(i) {} + }; + + struct Commit : Command + { + Commit(SessionImpl& i) : Command(i) {} + void operator()() { impl.commitImpl(); } + }; + + struct Rollback : Command + { + Rollback(SessionImpl& i) : Command(i) {} + void operator()() { impl.rollbackImpl(); } + }; + + struct Acknowledge : Command + { + Acknowledge(SessionImpl& i) : Command(i) {} + void operator()() { impl.acknowledgeImpl(); } + }; + + struct Sync : Command + { + Sync(SessionImpl& i) : Command(i) {} + void operator()() { impl.syncImpl(); } + }; + + struct Flush : Command + { + Flush(SessionImpl& i) : Command(i) {} + void operator()() { impl.flushImpl(); } + }; + + struct Reject : Command + { + qpid::messaging::Message& message; + + Reject(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.rejectImpl(message); } + }; + + struct CreateSender; + struct CreateReceiver; + struct PendingAck; + struct Available; + + //helper templates for some common patterns + template <class F> bool execute() + { + F f(*this); + return execute(f); + } + + template <class F> void retry() + { + while (!execute<F>()) {} + } + + template <class F, class P> bool execute1(P p) + { + F f(*this, p); + return execute(f); + } + + template <class F, class R, class P> R get1(P p) + { + F f(*this, p); + while (!execute(f)) {} + return f.result; + } +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SESSIONIMPL_H*/ diff --git a/cpp/src/qpid/client/windows/SaslFactory.cpp b/cpp/src/qpid/client/windows/SaslFactory.cpp new file mode 100644 index 0000000000..87df187ab2 --- /dev/null +++ b/cpp/src/qpid/client/windows/SaslFactory.cpp @@ -0,0 +1,146 @@ +/* + * + * 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. + * + */ + +#include "qpid/client/SaslFactory.h" +#include "qpid/client/ConnectionSettings.h" + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/log/Statement.h" + +#include "boost/tokenizer.hpp" + +namespace qpid { +namespace client { + +using qpid::sys::SecurityLayer; +using qpid::framing::InternalErrorException; + +class WindowsSasl : public Sasl +{ + public: + WindowsSasl(const ConnectionSettings&); + ~WindowsSasl(); + std::string start(const std::string& mechanisms, unsigned int ssf); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + ConnectionSettings settings; + std::string mechanism; +}; + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +SaslFactory::SaslFactory() +{ +} + +SaslFactory::~SaslFactory() +{ +} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const ConnectionSettings& settings) +{ + std::auto_ptr<Sasl> sasl(new WindowsSasl(settings)); + return sasl; +} + +namespace { + const std::string ANONYMOUS = "ANONYMOUS"; + const std::string PLAIN = "PLAIN"; +} + +WindowsSasl::WindowsSasl(const ConnectionSettings& s) + : settings(s) +{ +} + +WindowsSasl::~WindowsSasl() +{ +} + +std::string WindowsSasl::start(const std::string& mechanisms, + unsigned int /*ssf*/) +{ + QPID_LOG(debug, "WindowsSasl::start(" << mechanisms << ")"); + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" "); + bool havePlain = false; + bool haveAnon = false; + tokenizer mechs(mechanisms, sep); + for (tokenizer::iterator mech = mechs.begin(); + mech != mechs.end(); + ++mech) { + if (*mech == ANONYMOUS) + haveAnon = true; + else if (*mech == PLAIN) + havePlain = true; + } + if (!haveAnon && !havePlain) + throw InternalErrorException(QPID_MSG("Sasl error: no common mechanism")); + + std::string resp = ""; + if (havePlain) { + mechanism = PLAIN; + resp = ((char)0) + settings.username + ((char)0) + settings.password; + } + else { + mechanism = ANONYMOUS; + } + return resp; +} + +std::string WindowsSasl::step(const std::string& challenge) +{ + // Shouldn't get this for PLAIN... + throw InternalErrorException(QPID_MSG("Sasl step error")); +} + +std::string WindowsSasl::getMechanism() +{ + return mechanism; +} + +std::string WindowsSasl::getUserId() +{ + return std::string(); // TODO - when GSSAPI is supported, return userId for connection. +} + +std::auto_ptr<SecurityLayer> WindowsSasl::getSecurityLayer(uint16_t maxFrameSize) +{ + return std::auto_ptr<SecurityLayer>(0); +} + +}} // namespace qpid::client |
