diff options
Diffstat (limited to 'qpid/cpp/src/qpid/client')
76 files changed, 11460 insertions, 0 deletions
diff --git a/qpid/cpp/src/qpid/client/Bounds.cpp b/qpid/cpp/src/qpid/client/Bounds.cpp new file mode 100644 index 0000000000..cc2577d5fc --- /dev/null +++ b/qpid/cpp/src/qpid/client/Bounds.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/Bounds.h" + +#include "qpid/log/Statement.h" +#include "qpid/sys/Waitable.h" + +namespace qpid { +namespace client { + +using sys::Waitable; + +Bounds::Bounds(size_t maxSize) : max(maxSize), current(0) {} + +bool Bounds::expand(size_t sizeRequired, bool block) { + if (!max) return true; + Waitable::ScopedLock l(lock); + if (block) { + Waitable::ScopedWait w(lock); + while (current + sizeRequired > max) + lock.wait(); + } + current += sizeRequired; + return current <= max; +} + +void Bounds::reduce(size_t size) { + if (!max || size == 0) return; + Waitable::ScopedLock l(lock); + assert(current >= size); + current -= std::min(size, current); + if (current < max && lock.hasWaiters()) { + lock.notifyAll(); + } +} + +size_t Bounds::getCurrentSize() { + Waitable::ScopedLock l(lock); + return current; +} + +std::ostream& operator<<(std::ostream& out, const Bounds& bounds) { + out << "current=" << bounds.current << ", max=" << bounds.max << " [" << &bounds << "]"; + return out; +} + +void Bounds::setException(const sys::ExceptionHolder& e) { + Waitable::ScopedLock l(lock); + lock.setException(e); + lock.waitWaiters(); // Wait for waiting threads to exit. +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Bounds.h b/qpid/cpp/src/qpid/client/Bounds.h new file mode 100644 index 0000000000..838fcb8368 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Bounds.h @@ -0,0 +1,49 @@ +#ifndef QPID_CLIENT_BOUNDSCHECKING_H +#define QPID_CLIENT_BOUNDSCHECKING_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/Waitable.h" + +namespace qpid{ +namespace client{ + +class Bounds +{ + public: + Bounds(size_t maxSize); + bool expand(size_t, bool block); + void reduce(size_t); + size_t getCurrentSize(); + void setException(const sys::ExceptionHolder&); + + private: + friend std::ostream& operator<<(std::ostream&, const Bounds&); + sys::Waitable lock; + const size_t max; + size_t current; +}; + +std::ostream& operator<<(std::ostream&, const Bounds&); + + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/ChainableFrameHandler.h b/qpid/cpp/src/qpid/client/ChainableFrameHandler.h new file mode 100644 index 0000000000..29e16d53dc --- /dev/null +++ b/qpid/cpp/src/qpid/client/ChainableFrameHandler.h @@ -0,0 +1,47 @@ +/* + * + * 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 _ChainableFrameHandler_ +#define _ChainableFrameHandler_ + +#include <boost/function.hpp> +#include "qpid/framing/AMQFrame.h" + +namespace qpid { +namespace client { + +struct ChainableFrameHandler +{ + typedef boost::function<void(framing::AMQFrame&)> FrameDelegate; + + FrameDelegate in; + FrameDelegate out; + + ChainableFrameHandler() {} + ChainableFrameHandler(FrameDelegate i, FrameDelegate o): in(i), out(o) {} + virtual ~ChainableFrameHandler() {} +}; + +}} + + + +#endif diff --git a/qpid/cpp/src/qpid/client/Completion.cpp b/qpid/cpp/src/qpid/client/Completion.cpp new file mode 100644 index 0000000000..a97c8c3534 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Completion.cpp @@ -0,0 +1,40 @@ +/* + * + * 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/Completion.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +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 Completion::wait() { impl->wait(); } +bool Completion::isComplete() { return impl->isComplete(); } +std::string Completion::getResult() { return impl->getResult(); } + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/CompletionImpl.h b/qpid/cpp/src/qpid/client/CompletionImpl.h new file mode 100644 index 0000000000..f180708316 --- /dev/null +++ b/qpid/cpp/src/qpid/client/CompletionImpl.h @@ -0,0 +1,51 @@ +#ifndef QPID_CLIENT_COMPLETIONIMPL_H +#define QPID_CLIENT_COMPLETIONIMPL_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/RefCounted.h" +#include "qpid/client/Future.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace client { + +///@internal +class CompletionImpl : public RefCounted +{ +public: + 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 /*!QPID_CLIENT_COMPLETIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/Connection.cpp b/qpid/cpp/src/qpid/client/Connection.cpp new file mode 100644 index 0000000000..2882ef5d42 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Connection.cpp @@ -0,0 +1,161 @@ +/* + * + * 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/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/framing/AMQP_HighestVersion.h" + +#include <algorithm> +#include <iostream> +#include <sstream> +#include <functional> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +using namespace qpid::framing; +using namespace qpid::sys; + + +namespace qpid { +namespace client { + +Connection::Connection() : version(framing::highestProtocolVersion) +{ + ConnectionImpl::init(); +} + +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 Address& addr = *i; + i++; + try { + ConnectionSettings cs(settings); + cs.protocol = addr.protocol; + cs.host = addr.host; + cs.port = addr.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, + const std::string& uid, const std::string& pwd, + const std::string& vhost, + uint16_t maxFrameSize) +{ + ConnectionSettings settings; + settings.host = host; + settings.port = port; + settings.username = uid; + settings.password = pwd; + settings.virtualhost = vhost; + settings.maxFrameSize = maxFrameSize; + open(settings); +} + +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 = ConnectionImpl::create(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, uint32_t timeout) { + if (!isOpen()) + throw Exception(QPID_MSG("Connection has not yet been opened")); + Session s; + 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.")); + impl->addSession(session.impl); + session.impl->resume(impl); +} + +void Connection::close() { + if ( impl ) + impl->close(); +} + +std::vector<Url> Connection::getInitialBrokers() { + return impl ? impl->getInitialBrokers() : std::vector<Url>(); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/ConnectionAccess.h b/qpid/cpp/src/qpid/client/ConnectionAccess.h new file mode 100644 index 0000000000..3a763f692f --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionAccess.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLIENT_CONNECTIONACCESS_H +#define QPID_CLIENT_CONNECTIONACCESS_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/Connection.h" + +/**@file @internal Internal use only */ + +namespace qpid { +namespace client { + + + +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; } + static void setImpl(Connection& c, boost::shared_ptr<ConnectionImpl> i) { c.impl = i; } +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_CONNECTIONACCESS_H*/ diff --git a/qpid/cpp/src/qpid/client/ConnectionHandler.cpp b/qpid/cpp/src/qpid/client/ConnectionHandler.cpp new file mode 100644 index 0000000000..4fbf55aa60 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionHandler.cpp @@ -0,0 +1,356 @@ +/* + * + * 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/ConnectionHandler.h" + +#include "qpid/SaslFactory.h" +#include "qpid/StringUtils.h" +#include "qpid/client/Bounds.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 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"); +const std::string PLAIN("PLAIN"); +const std::string en_US("en_US"); + +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; +} + +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::Adapter::Adapter(ConnectionHandler& h, Bounds& b) : handler(h), bounds(b) {} +void ConnectionHandler::Adapter::handle(qpid::framing::AMQFrame& f) +{ + bounds.expand(f.encodedSize(), false); + handler.out(f); +} + +ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, ProtocolVersion& v, Bounds& b) + : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this, b), 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"); + } + + if (rcvTimeoutTask) { + // Received frame on connection so delay timeout + rcvTimeoutTask->restart(); + } + + AMQBody* body = frame.getBody(); + try { + if (frame.getChannel() != 0 || !invoke(static_cast<ConnectionOperations&>(*this), *body)) { + switch(getState()) { + case OPEN: + in(frame); + break; + case CLOSING: + 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()); + setState(CLOSING); + errorCode = CLOSE_CODE_FRAMING_ERROR; + errorText = e.what(); + proxy.close(501, e.what()); + } +} + +void ConnectionHandler::outgoing(AMQFrame& frame) +{ + if (getState() == OPEN) + out(frame); + else + throw TransportFailure(errorText.empty() ? "Connection is not open." : errorText); +} + +void ConnectionHandler::waitForOpen() +{ + waitFor(ESTABLISHED); + if (getState() == FAILED || getState() == CLOSED) { + throw ConnectionException(errorCode, errorText); + } +} + +void ConnectionHandler::close() +{ + switch (getState()) { + case NEGOTIATING: + case OPENING: + fail("Connection closed before it was established"); + break; + case OPEN: + if (setState(CLOSING, OPEN)) { + proxy.close(200, OK); + if (ConnectionSettings::heartbeat) { + //heartbeat timer is turned off at this stage, so don't wait indefinately + if (!waitFor(FINISHED, qpid::sys::Duration(ConnectionSettings::heartbeat * qpid::sys::TIME_SEC))) { + QPID_LOG(warning, "Connection close timed out"); + } + } else { + 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 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) { + throw CommandInvalidException(msg); + } +} + +void ConnectionHandler::fail(const std::string& message) +{ + errorCode = CLOSE_CODE_FRAMING_ERROR; + errorText = message; + QPID_LOG(warning, message); + setState(FAILED); +} + +namespace { +std::string SPACE(" "); + +std::string join(const std::vector<std::string>& in) +{ + std::string result; + for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) { + if (result.size()) result += SPACE; + result += *i; + } + return result; +} + +void intersection(const std::vector<std::string>& a, const std::vector<std::string>& b, std::vector<std::string>& results) +{ + for (std::vector<std::string>::const_iterator i = a.begin(); i != a.end(); ++i) { + if (std::find(b.begin(), b.end(), *i) != b.end()) results.push_back(*i); + } +} + +} + +void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& mechanisms, const Array& /*locales*/) +{ + checkState(NOT_STARTED, INVALID_STATE_START); + setState(NEGOTIATING); + sasl = SaslFactory::getInstance().create( username, + password, + service, + host, + minSsf, + maxSsf + ); + + std::vector<std::string> mechlist; + if (mechanism.empty()) { + //mechlist is simply what the server offers + mechanisms.collect(mechlist); + } else { + //mechlist is the intersection of those indicated by user and + //those supported by server, in the order listed by user + std::vector<std::string> allowed = split(mechanism, " "); + std::vector<std::string> supported; + mechanisms.collect(supported); + intersection(allowed, supported, mechlist); + if (mechlist.empty()) { + throw Exception(QPID_MSG("Desired mechanism(s) not valid: " << mechanism << " (supported: " << join(supported) << ")")); + } + } + + if (sasl.get()) { + string response = sasl->start(join(mechlist), getSecuritySettings ? getSecuritySettings() : 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) +{ + 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) +{ + checkState(NEGOTIATING, INVALID_STATE_TUNE); + maxChannels = std::min(maxChannels, maxChannelsProposed); + maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed); + // 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 Array& knownBrokers ) +{ + checkState(OPENING, INVALID_STATE_OPEN_OK); + 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"); +} + +void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText) +{ + proxy.closeOk(); + errorCode = convert(replyCode); + errorText = replyText; + setState(CLOSED); + QPID_LOG(warning, "Broker closed connection: " << replyCode << ", " << replyText); + if (onError) { + onError(replyCode, replyText); + } +} + +void ConnectionHandler::closeOk() +{ + checkState(CLOSING, INVALID_STATE_CLOSE_OK); + if (onError && errorCode != CLOSE_CODE_NORMAL) { + onError(errorCode, errorText); + } else if (onClose) { + onClose(); + } + setState(CLOSED); +} + +bool ConnectionHandler::isOpen() const +{ + return getState() == OPEN; +} + +bool ConnectionHandler::isClosed() const +{ + int s = getState(); + 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/qpid/cpp/src/qpid/client/ConnectionHandler.h b/qpid/cpp/src/qpid/client/ConnectionHandler.h new file mode 100644 index 0000000000..6af2e987fb --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionHandler.h @@ -0,0 +1,139 @@ +/* + * + * 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 _ConnectionHandler_ +#define _ConnectionHandler_ + +#include "qpid/client/ChainableFrameHandler.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/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 sys { +struct SecuritySettings; +} + +namespace client { + +class Bounds; + +class ConnectionHandler : private StateManager, + public ConnectionSettings, + public ChainableFrameHandler, + public framing::InputHandler, + private framing::AMQP_ClientOperations::ConnectionHandler +{ + typedef framing::AMQP_ClientOperations::ConnectionHandler ConnectionOperations; + enum STATES {NOT_STARTED, NEGOTIATING, OPENING, OPEN, CLOSING, CLOSED, FAILED}; + std::set<int> ESTABLISHED, FINISHED; + + class Adapter : public framing::FrameHandler + { + ConnectionHandler& handler; + Bounds& bounds; + public: + Adapter(ConnectionHandler& h, Bounds& bounds); + void handle(framing::AMQFrame& f); + }; + + Adapter outHandler; + framing::AMQP_ServerProxy::Connection proxy; + 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); + + //methods corresponding to connection controls: + void start(const framing::FieldTable& serverProperties, + const framing::Array& mechanisms, + const framing::Array& locales); + void secure(const std::string& challenge); + void tune(uint16_t channelMax, + uint16_t frameMax, + uint16_t heartbeatMin, + uint16_t heartbeatMax); + void openOk(const framing::Array& knownHosts); + void redirect(const std::string& host, + 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<const qpid::sys::SecuritySettings*()> GetSecuritySettings; + + ConnectionHandler(const ConnectionSettings&, framing::ProtocolVersion&, Bounds&); + + void received(framing::AMQFrame& f) { incoming(f); } + + void incoming(framing::AMQFrame& frame); + void outgoing(framing::AMQFrame& frame); + + void waitForOpen(); + void close(); + void fail(const std::string& message); + + // 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; } + GetSecuritySettings getSecuritySettings; /** query the transport for its security details */ +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/ConnectionImpl.cpp b/qpid/cpp/src/qpid/client/ConnectionImpl.cpp new file mode 100644 index 0000000000..4b7aa07065 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionImpl.cpp @@ -0,0 +1,451 @@ +/* + * + * 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/ConnectionImpl.h" + +#include "qpid/client/LoadPlugins.h" +#include "qpid/client/Connector.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/SessionImpl.h" + +#include "qpid/log/Statement.h" +#include "qpid/Url.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/Options.h" + +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> + +#include <limits> +#include <vector> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +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 + +namespace { +// Maybe should amalgamate the singletons into a single client singleton + +// Get timer singleton +Timer& theTimer() { + static Mutex timerInitLock; + ScopedLock<Mutex> l(timerInitLock); + + static qpid::sys::Timer t; + return t; +} + +struct IOThreadOptions : public qpid::Options { + int maxIOThreads; + + IOThreadOptions(int c) : + Options("IO threading options"), + maxIOThreads(c) + { + addOptions() + ("max-iothreads", optValue(maxIOThreads, "N"), "Maximum number of io threads to use"); + } +}; + +// IO threads +class IOThread { + int maxIOThreads; + int ioThreads; + int connections; + Mutex threadLock; + std::vector<Thread> t; + Poller::shared_ptr poller_; + +public: + void add() { + ScopedLock<Mutex> l(threadLock); + ++connections; + if (!poller_) + poller_.reset(new Poller); + if (ioThreads < connections && ioThreads < maxIOThreads) { + QPID_LOG(debug, "Created IO thread: " << ioThreads); + ++ioThreads; + t.push_back( Thread(poller_.get()) ); + } + } + + void sub() { + ScopedLock<Mutex> l(threadLock); + --connections; + } + + Poller::shared_ptr poller() const { + assert(poller_); + return poller_; + } + + // Here is where the maximum number of threads is set + IOThread(int c) : + ioThreads(0), + connections(0) + { + IOThreadOptions options(c); + options.parse(0, 0, QPIDC_CONF_FILE, true); + maxIOThreads = (options.maxIOThreads != -1) ? + options.maxIOThreads : 1; + } + + // We can't destroy threads one-by-one as the only + // control we have is to shutdown the whole lot + // and we can't do that before we're unloaded as we can't + // restart the Poller after shutting it down + ~IOThread() { + std::vector<Thread> threads; + { + ScopedLock<Mutex> l(threadLock); + if (poller_) + poller_->shutdown(); + t.swap(threads); + } + for (std::vector<Thread>::iterator i = threads.begin(); i != threads.end(); ++i) { + i->join(); + } + } +}; + +IOThread& theIO() { + static IOThread io(SystemInfo::concurrency()); + return io; +} + +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,"Heartbeat"), + timeout(t) + {} +}; + +} + +void ConnectionImpl::init() { + // Ensure that the plugin modules have been loaded + // This will make sure that any plugin protocols are available + theModuleLoader(); + + // Ensure the IO threads exist: + // This needs to be called in the Connection constructor + // so that they will still exist at last connection destruction + (void) theIO(); +} + +boost::shared_ptr<ConnectionImpl> ConnectionImpl::create(framing::ProtocolVersion version, const ConnectionSettings& settings) +{ + boost::shared_ptr<ConnectionImpl> instance(new ConnectionImpl(version, settings), boost::bind(&ConnectionImpl::release, _1)); + return instance; +} + +ConnectionImpl::ConnectionImpl(framing::ProtocolVersion v, const ConnectionSettings& settings) + : Bounds(settings.maxFrameSize * settings.bounds), + handler(settings, v, *this), + version(v), + nextChannel(1), + shutdownComplete(false), + released(false) +{ + 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, + CLOSE_CODE_NORMAL, std::string()); + //only set error handler once open + handler.onError = boost::bind(&ConnectionImpl::closed, this, _1, _2); + handler.getSecuritySettings = boost::bind(&Connector::getSecuritySettings, boost::ref(connector)); +} + +const uint16_t ConnectionImpl::NEXT_CHANNEL = std::numeric_limits<uint16_t>::max(); + +ConnectionImpl::~ConnectionImpl() { + if (heartbeatTask) heartbeatTask->cancel(); + theIO().sub(); +} + +void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session, uint16_t channel) +{ + Mutex::ScopedLock l(lock); + 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 + } + // If we get here, we didn't find any available channel. + throw ResourceLimitExceededException("There are no channels available"); +} + +void ConnectionImpl::handle(framing::AMQFrame& frame) +{ + handler.outgoing(frame); +} + +void ConnectionImpl::incoming(framing::AMQFrame& frame) +{ + boost::shared_ptr<SessionImpl> s; + { + Mutex::ScopedLock l(lock); + s = sessions[frame.getChannel()].lock(); + } + if (!s) { + QPID_LOG(info, *this << " dropping frame received on invalid channel: " << frame); + } else { + s->in(frame); + } +} + +bool ConnectionImpl::isOpen() const +{ + return handler.isOpen(); +} + +void ConnectionImpl::open() +{ + const std::string& protocol = handler.protocol; + const std::string& host = handler.host; + int port = handler.port; + + theIO().add(); + connector.reset(Connector::create(protocol, theIO().poller(), version, handler, this)); + connector->setInputHandler(&handler); + connector->setShutdownHandler(this); + try { + std::string p = boost::lexical_cast<std::string>(port); + connector->connect(host, p); + + } catch (const std::exception& e) { + QPID_LOG(debug, "Failed to connect to " << protocol << ":" << host << ":" << port << " " << e.what()); + connector.reset(); + throw; + } + connector->init(); + + // 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); + } + + // If the connect fails then the connector is cleaned up either when we try to connect again + // - in that case in connector.reset() above; + // - or when we are deleted + handler.waitForOpen(); + QPID_LOG(info, *this << " connected to " << protocol << ":" << host << ":" << port); + + // 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, *this << " activating security layer"); + connector->activateSecurityLayer(securityLayer); + } else { + QPID_LOG(debug, *this << " no security layer in place"); + } +} + +void ConnectionImpl::idleIn() +{ + connector->abort(); +} + +void ConnectionImpl::idleOut() +{ + AMQFrame frame((AMQHeartbeatBody())); + connector->send(frame); +} + +void ConnectionImpl::close() +{ + 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) { + 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); + } +} + +void ConnectionImpl::closed(uint16_t code, const std::string& text) { + Mutex::ScopedLock l(lock); + setException(new ConnectionException(ConnectionHandler::convert(code), text)); + closeInternal(boost::bind(&SessionImpl::connectionClosed, _1, code, text)); +} + +void ConnectionImpl::shutdown() { + if (!handler.isClosed()) { + failedConnection(); + } + bool canDelete; + { + Mutex::ScopedLock l(lock); + //association with IO thread is now ended + shutdownComplete = true; + //If we have already been released, we can now delete ourselves + canDelete = released; + } + if (canDelete) delete this; +} + +void ConnectionImpl::release() { + bool isActive; + { + Mutex::ScopedLock l(lock); + isActive = connector && !shutdownComplete; + } + //If we are still active - i.e. associated with an IO thread - + //then we cannot delete ourselves yet, but must wait for the + //shutdown callback which we can trigger by calling + //connector.close() + if (isActive) { + connector->close(); + bool canDelete; + { + Mutex::ScopedLock l(lock); + released = true; + canDelete = shutdownComplete; + } + if (canDelete) delete this; + } else { + delete this; + } +} + +static const std::string CONN_CLOSED("Connection closed"); + +void ConnectionImpl::failedConnection() { + if ( failureCallback ) + failureCallback(); + + if (handler.isClosed()) return; + + bool isClosing = handler.isClosing(); + bool isOpen = handler.isOpen(); + + std::ostringstream msg; + msg << *this << " closed"; + + // FIXME aconway 2008-06-06: exception use, amqp0-10 does not seem to have + // an appropriate close-code. connection-forced is not right. + handler.fail(msg.str());//ensure connection is marked as failed before notifying sessions + + // At this point if the object isn't open and isn't closing it must have failed to open + // so we can't do the rest of the cleanup + if (!isClosing && !isOpen) return; + + Mutex::ScopedLock l(lock); + closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, msg.str())); + setException(new TransportFailure(msg.str())); +} + +void ConnectionImpl::erase(uint16_t ch) { + Mutex::ScopedLock l(lock); + sessions.erase(ch); +} + +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; +} + +std::ostream& operator<<(std::ostream& o, const ConnectionImpl& c) { + if (c.connector) + return o << "Connection " << c.connector->getIdentifier(); + else + return o << "Connection <not connected>"; +} + + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/ConnectionImpl.h b/qpid/cpp/src/qpid/client/ConnectionImpl.h new file mode 100644 index 0000000000..cc81500b18 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionImpl.h @@ -0,0 +1,107 @@ +/* + * + * 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 _ConnectionImpl_ +#define _ConnectionImpl_ + +#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" +#include "qpid/sys/TimeoutHandler.h" + +#include <map> +#include <iosfwd> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +namespace qpid { +namespace client { + +class Connector; +struct ConnectionSettings; +class SessionImpl; + +class ConnectionImpl : public Bounds, + public framing::FrameHandler, + public sys::TimeoutHandler, + public sys::ShutdownHandler, + public boost::enable_shared_from_this<ConnectionImpl> +{ + 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; + bool shutdownComplete; + bool released; + + boost::intrusive_ptr<qpid::sys::TimerTask> heartbeatTask; + + template <class F> void closeInternal(const F&); + + void incoming(framing::AMQFrame& frame); + void closed(uint16_t, const std::string&); + void idleOut(); + void idleIn(); + void shutdown(); + void failedConnection(); + void release(); + ConnectionImpl(framing::ProtocolVersion version, const ConnectionSettings& settings); + + boost::function<void ()> failureCallback; + + public: + static void init(); + static boost::shared_ptr<ConnectionImpl> create(framing::ProtocolVersion version, const ConnectionSettings& settings); + ~ConnectionImpl(); + + void open(); + bool isOpen() const; + + 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; } + + friend std::ostream& operator<<(std::ostream&, const ConnectionImpl&); +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/client/ConnectionSettings.cpp b/qpid/cpp/src/qpid/client/ConnectionSettings.cpp new file mode 100644 index 0000000000..822e4af269 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionSettings.cpp @@ -0,0 +1,57 @@ +/* + * + * 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/ConnectionSettings.h" + +#include "qpid/log/Logger.h" +#include "qpid/sys/Socket.h" +#include "qpid/Url.h" +#include "qpid/Version.h" + +namespace qpid { +namespace client { + +ConnectionSettings::ConnectionSettings() : + protocol("tcp"), + host("localhost"), + port(5672), + locale("en_US"), + heartbeat(0), + maxChannels(32767), + maxFrameSize(65535), + bounds(2), + tcpNoDelay(false), + service(qpid::saslName), + minSsf(0), + maxSsf(256), + sslCertName("") +{} + +ConnectionSettings::~ConnectionSettings() {} + +void ConnectionSettings::configureSocket(qpid::sys::Socket& socket) const +{ + if (tcpNoDelay) { + socket.setTcpNoDelay(); + QPID_LOG(info, "Set TCP_NODELAY"); + } +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Connector.cpp b/qpid/cpp/src/qpid/client/Connector.cpp new file mode 100644 index 0000000000..c71dd9ecb6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Connector.cpp @@ -0,0 +1,72 @@ +/* + * + * 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/Url.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SecurityLayer.h" + +#include <map> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; + +namespace { + typedef std::map<std::string, Connector::Factory*> ProtocolRegistry; + + ProtocolRegistry& theProtocolRegistry() { + static ProtocolRegistry protocolRegistry; + + return protocolRegistry; + } +} + +Connector* Connector::create(const std::string& proto, + boost::shared_ptr<Poller> p, + framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) +{ + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i==theProtocolRegistry().end()) { + throw Exception(QPID_MSG("Unknown protocol: " << proto)); + } + return (i->second)(p, v, s, c); +} + +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; + Url::addProtocol(proto); +} + +void Connector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>) +{ +} + + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Connector.h b/qpid/cpp/src/qpid/client/Connector.h new file mode 100644 index 0000000000..bc611ffe0d --- /dev/null +++ b/qpid/cpp/src/qpid/client/Connector.h @@ -0,0 +1,84 @@ +/* + * + * 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 _Connector_ +#define _Connector_ + + +#include "qpid/framing/OutputHandler.h" +#include "qpid/framing/ProtocolVersion.h" + +#include <boost/shared_ptr.hpp> + +#include <string> + +namespace qpid { + +namespace sys { +class ShutdownHandler; +class SecurityLayer; +class Poller; +struct SecuritySettings; +} + +namespace framing { +class InputHandler; +class AMQFrame; +} + +namespace client { + +struct ConnectionSettings; +class ConnectionImpl; + +///@internal +class Connector : public framing::OutputHandler +{ + public: + // Protocol connector factory related stuff (it might be better to separate this code from the TCP Connector in the future) + typedef Connector* Factory(boost::shared_ptr<qpid::sys::Poller>, + framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static Connector* create(const std::string& proto, + boost::shared_ptr<qpid::sys::Poller>, + framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static void registerFactory(const std::string& proto, Factory* connectorFactory); + + virtual ~Connector() {}; + virtual void connect(const std::string& host, const std::string& 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 const qpid::sys::SecuritySettings* getSecuritySettings() = 0; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/client/Demux.cpp b/qpid/cpp/src/qpid/client/Demux.cpp new file mode 100644 index 0000000000..abc23c75df --- /dev/null +++ b/qpid/cpp/src/qpid/client/Demux.cpp @@ -0,0 +1,132 @@ +/* + * + * 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/Demux.h" +#include "qpid/Exception.h" +#include "qpid/framing/MessageTransferBody.h" + +#include <iostream> + +namespace qpid { +namespace client { + +ByTransferDest::ByTransferDest(const std::string& d) : dest(d) {} +bool ByTransferDest::operator()(const framing::FrameSet& frameset) const +{ + return frameset.isA<framing::MessageTransferBody>() && + frameset.as<framing::MessageTransferBody>()->getDestination() == dest; +} + +ScopedDivert::ScopedDivert(const std::string& _dest, Demux& _demuxer) : dest(_dest), demuxer(_demuxer) +{ + queue = demuxer.add(dest, ByTransferDest(dest)); +} + +ScopedDivert::~ScopedDivert() +{ + demuxer.remove(dest); +} + +Demux::Demux() : defaultQueue(new Queue()) {} + +Demux::~Demux() { close(sys::ExceptionHolder(new ClosedException())); } + +Demux::QueuePtr ScopedDivert::getQueue() +{ + return queue; +} + +void Demux::handle(framing::FrameSet::shared_ptr frameset) +{ + sys::Mutex::ScopedLock l(lock); + bool matched = false; + for (iterator i = records.begin(); i != records.end() && !matched; i++) { + if (i->condition && i->condition(*frameset)) { + matched = true; + i->queue->push(frameset); + } + } + if (!matched) { + defaultQueue->push(frameset); + } +} + +void Demux::close(const sys::ExceptionHolder& ex) +{ + sys::Mutex::ScopedLock l(lock); + for (iterator i = records.begin(); i != records.end(); i++) { + i->queue->close(ex); + } + defaultQueue->close(ex); +} + +void Demux::open() +{ + sys::Mutex::ScopedLock l(lock); + for (iterator i = records.begin(); i != records.end(); i++) { + i->queue->open(); + } + defaultQueue->open(); +} + +Demux::QueuePtr Demux::add(const std::string& name, Condition condition) +{ + sys::Mutex::ScopedLock l(lock); + iterator i = std::find_if(records.begin(), records.end(), Find(name)); + if (i == records.end()) { + Record r(name, condition); + records.push_back(r); + return r.queue; + } else { + throw Exception("Queue already exists for " + name); + } +} + +void Demux::remove(const std::string& name) +{ + sys::Mutex::ScopedLock l(lock); + records.remove_if(Find(name)); +} + +Demux::QueuePtr Demux::get(const std::string& name) +{ + sys::Mutex::ScopedLock l(lock); + iterator i = std::find_if(records.begin(), records.end(), Find(name)); + if (i == records.end()) { + throw Exception("No queue for " + name); + } + return i->queue; +} + +Demux::QueuePtr Demux::getDefault() +{ + return defaultQueue; +} + +Demux::Find::Find(const std::string& n) : name(n) {} + +bool Demux::Find::operator()(const Record& record) const +{ + return record.name == name; +} + +}} + diff --git a/qpid/cpp/src/qpid/client/Demux.h b/qpid/cpp/src/qpid/client/Demux.h new file mode 100644 index 0000000000..31dc3f9c06 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Demux.h @@ -0,0 +1,103 @@ +/* + * + * 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 <list> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include "qpid/framing/FrameSet.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/client/ClientImportExport.h" + +#ifndef _Demux_ +#define _Demux_ + +namespace qpid { +namespace client { + +///@internal +class ByTransferDest +{ + const std::string dest; +public: + ByTransferDest(const std::string& dest); + bool operator()(const framing::FrameSet& frameset) const; +}; + +///@internal +class Demux +{ +public: + typedef boost::function<bool(const framing::FrameSet&)> Condition; + typedef sys::BlockingQueue<framing::FrameSet::shared_ptr> Queue; + typedef boost::shared_ptr<Queue> QueuePtr; + + QPID_CLIENT_EXTERN Demux(); + QPID_CLIENT_EXTERN ~Demux(); + + 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 + { + const std::string name; + Condition condition; + QueuePtr queue; + + Record(const std::string& n, Condition c) : name(n), condition(c), queue(new Queue()) {} + }; + + sys::Mutex lock; + std::list<Record> records; + QueuePtr defaultQueue; + + typedef std::list<Record>::iterator iterator; + + struct Find + { + const std::string name; + Find(const std::string& name); + bool operator()(const Record& record) const; + }; +}; + +class ScopedDivert +{ + const std::string dest; + Demux& demuxer; + Demux::QueuePtr queue; +public: + ScopedDivert(const std::string& dest, Demux& demuxer); + ~ScopedDivert(); + Demux::QueuePtr getQueue(); +}; + +}} // namespace qpid::client + + +#endif diff --git a/qpid/cpp/src/qpid/client/Dispatcher.cpp b/qpid/cpp/src/qpid/client/Dispatcher.cpp new file mode 100644 index 0000000000..a715c623bf --- /dev/null +++ b/qpid/cpp/src/qpid/client/Dispatcher.cpp @@ -0,0 +1,151 @@ +/* + * + * 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/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 "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; +using qpid::sys::Mutex; +using qpid::sys::ScopedLock; +using qpid::sys::Thread; + +namespace qpid { +namespace client { + +Dispatcher::Dispatcher(const Session& s, const std::string& q) + : session(s), + running(false), + autoStop(true), + failoverHandler(0) +{ + 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."); + 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(new MessageImpl(*content)); + boost::intrusive_ptr<SubscriptionImpl> listener = find(msg.getDestination()); + if (!listener) { + QPID_LOG(error, "No listener found for destination " << msg.getDestination()); + } else { + assert(listener); + listener->received(msg); + } + } else { + if (handler.get()) { + handler->handle(*content); + } else { + QPID_LOG(warning, "No handler found for " << *(content->getMethod())); + } + } + } + session.sync(); // Make sure all our acks are received before returning. + } + 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) { + if ( failoverHandler ) { + QPID_LOG(debug, QPID_MSG(session.getId() << " failover: " << e.what())); + failoverHandler(); + } else { + QPID_LOG(error, session.getId() << " error: " << e.what()); + throw; + } + } +} + +void Dispatcher::stop() +{ + ScopedLock<Mutex> l(lock); + queue->close(); // Will interrupt thread blocked in pop() +} + +void Dispatcher::setAutoStop(bool b) +{ + ScopedLock<Mutex> l(lock); + autoStop = b; +} + +boost::intrusive_ptr<SubscriptionImpl> Dispatcher::find(const std::string& name) +{ + ScopedLock<Mutex> l(lock); + Listeners::iterator i = listeners.find(name); + if (i == listeners.end()) { + return defaultListener; + } + return i->second; +} + +void Dispatcher::listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription) { + ScopedLock<Mutex> l(lock); + listeners[subscription->getName()] = subscription; +} + +void Dispatcher::cancel(const std::string& destination) { + ScopedLock<Mutex> l(lock); + if (listeners.erase(destination) && running && autoStop && listeners.empty()) + queue->close(); +} + +}} diff --git a/qpid/cpp/src/qpid/client/Dispatcher.h b/qpid/cpp/src/qpid/client/Dispatcher.h new file mode 100644 index 0000000000..74fdb90103 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Dispatcher.h @@ -0,0 +1,87 @@ +/* + * + * 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 _Dispatcher_ +#define _Dispatcher_ + +#include <map> +#include <memory> +#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 "qpid/client/ClientImportExport.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/SubscriptionImpl.h" + +namespace qpid { +namespace client { + +class SubscriptionImpl; + +///@internal +typedef framing::Handler<framing::FrameSet> FrameSetHandler; + +///@internal +class Dispatcher : public sys::Runnable +{ + typedef std::map<std::string, boost::intrusive_ptr<SubscriptionImpl> >Listeners; + sys::Mutex lock; + sys::Thread worker; + Session session; + Demux::QueuePtr queue; + bool running; + bool autoStop; + Listeners listeners; + boost::intrusive_ptr<SubscriptionImpl> defaultListener; + std::auto_ptr<FrameSetHandler> handler; + + 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 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 registerFailoverHandler ( boost::function<void ()> fh ) + { + failoverHandler = fh; + } + + void listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription); + void cancel(const std::string& destination); +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/Execution.h b/qpid/cpp/src/qpid/client/Execution.h new file mode 100644 index 0000000000..ad622af9c1 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Execution.h @@ -0,0 +1,53 @@ +/* + * + * 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 _Execution_ +#define _Execution_ + +#include "qpid/framing/SequenceNumber.h" +#include "qpid/client/Demux.h" + +namespace qpid { +namespace client { + +/**@internal + * + * Provides access to more detailed aspects of the session + * implementation. + */ +class Execution +{ +public: + virtual ~Execution() {} + /** + * Provides access to the demultiplexing function within the + * session implementation + */ + virtual Demux& getDemux() = 0; + /** + * Wait until notification has been received of completion of the + * outgoing command with the specified id. + */ + void waitForCompletion(const framing::SequenceNumber& id); +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/FailoverListener.cpp b/qpid/cpp/src/qpid/client/FailoverListener.cpp new file mode 100644 index 0000000000..bf4fa91d49 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FailoverListener.cpp @@ -0,0 +1,97 @@ +/* + * + * 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) +{ init(true); } + +FailoverListener::FailoverListener(Connection c, bool useInitial) : + connection(c), + session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())), + subscriptions(session) +{ init(useInitial); } + +void FailoverListener::init(bool useInitial) { + if (useInitial) knownBrokers = connection.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/qpid/cpp/src/qpid/client/FailoverManager.cpp b/qpid/cpp/src/qpid/client/FailoverManager.cpp new file mode 100644 index 0000000000..9405765b47 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FailoverManager.cpp @@ -0,0 +1,130 @@ +/* + * + * 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 Address& addr = *j; + s.protocol = addr.protocol; + s.host = addr.host; + s.port = addr.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/qpid/cpp/src/qpid/client/Future.cpp b/qpid/cpp/src/qpid/client/Future.cpp new file mode 100644 index 0000000000..740cd3df59 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Future.cpp @@ -0,0 +1,46 @@ +/* + * + * 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/Future.h" +#include "qpid/client/SessionImpl.h" + +namespace qpid { +namespace client { + +void Future::wait(SessionImpl& session) +{ + if (!complete) { + session.waitForCompletion(command); + } + complete = true; +} + +bool Future::isComplete(SessionImpl& session) +{ + return complete || session.isComplete(command); +} + +void Future::setFutureResult(boost::shared_ptr<FutureResult> r) +{ + result = r; +} + +}} diff --git a/qpid/cpp/src/qpid/client/FutureCompletion.cpp b/qpid/cpp/src/qpid/client/FutureCompletion.cpp new file mode 100644 index 0000000000..ccfb073855 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FutureCompletion.cpp @@ -0,0 +1,48 @@ +/* + * + * 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/FutureCompletion.h" + +using namespace qpid::client; +using namespace qpid::sys; + +FutureCompletion::FutureCompletion() : complete(false) {} + +bool FutureCompletion::isComplete() const +{ + Monitor::ScopedLock l(lock); + return complete; +} + +void FutureCompletion::completed() +{ + Monitor::ScopedLock l(lock); + complete = true; + lock.notifyAll(); +} + +void FutureCompletion::waitForCompletion() const +{ + Monitor::ScopedLock l(lock); + while (!complete) { + lock.wait(); + } +} diff --git a/qpid/cpp/src/qpid/client/FutureResult.cpp b/qpid/cpp/src/qpid/client/FutureResult.cpp new file mode 100644 index 0000000000..0237eb1464 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FutureResult.cpp @@ -0,0 +1,43 @@ +/* + * + * 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/FutureResult.h" + +#include "qpid/client/SessionImpl.h" + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; + +const std::string& FutureResult::getResult(SessionImpl& session) const +{ + waitForCompletion(); + session.assertOpen(); + return result; +} + +void FutureResult::received(const std::string& r) +{ + Monitor::ScopedLock l(lock); + result = r; + complete = true; + lock.notifyAll(); +} diff --git a/qpid/cpp/src/qpid/client/LoadPlugins.cpp b/qpid/cpp/src/qpid/client/LoadPlugins.cpp new file mode 100644 index 0000000000..246eb60c67 --- /dev/null +++ b/qpid/cpp/src/qpid/client/LoadPlugins.cpp @@ -0,0 +1,64 @@ +/* + * + * 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 "LoadPlugins.h" + +#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 qpid { +namespace client { + +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); + } + } +}; + +} // namespace + +void theModuleLoader() { + static LoadtimeInitialise l; +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/LoadPlugins.h b/qpid/cpp/src/qpid/client/LoadPlugins.h new file mode 100644 index 0000000000..0be4ae9f0c --- /dev/null +++ b/qpid/cpp/src/qpid/client/LoadPlugins.h @@ -0,0 +1,33 @@ +/* + * + * 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 _LoadPlugins_ +#define _LoadPlugins_ + +namespace qpid { +namespace client { + +void theModuleLoader(); + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/client/LocalQueue.cpp b/qpid/cpp/src/qpid/client/LocalQueue.cpp new file mode 100644 index 0000000000..0019adabaf --- /dev/null +++ b/qpid/cpp/src/qpid/client/LocalQueue.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. + * + */ +#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; + +typedef PrivateImplRef<LocalQueue> PI; + +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::pop(sys::Duration timeout) { return impl->pop(timeout); } + +Message LocalQueue::get(sys::Duration timeout) { return impl->get(timeout); } + +bool LocalQueue::get(Message& result, sys::Duration timeout) { return impl->get(result, timeout); } + +bool LocalQueue::empty() const { return impl->empty(); } +size_t LocalQueue::size() const { return impl->size(); } + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp b/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp new file mode 100644 index 0000000000..8b191728f4 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/LocalQueueImpl.h b/qpid/cpp/src/qpid/client/LocalQueueImpl.h new file mode 100644 index 0000000000..75b62cf203 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/Message.cpp b/qpid/cpp/src/qpid/client/Message.cpp new file mode 100644 index 0000000000..00f911c57e --- /dev/null +++ b/qpid/cpp/src/qpid/client/Message.cpp @@ -0,0 +1,62 @@ +/* + * + * 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/MessageImpl.h" + +namespace qpid { +namespace client { + +Message::Message(MessageImpl* mi) : impl(mi) {} + +Message::Message(const std::string& data, const std::string& routingKey) + : impl(new MessageImpl(data, routingKey)) {} + +Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {} + +Message::~Message() { delete impl; } + +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } + +void Message::swap(Message& m) { std::swap(impl, m.impl); } + +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(); } + +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(); } + +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/qpid/cpp/src/qpid/client/MessageImpl.cpp b/qpid/cpp/src/qpid/client/MessageImpl.cpp new file mode 100644 index 0000000000..865c462b15 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/MessageImpl.h b/qpid/cpp/src/qpid/client/MessageImpl.h new file mode 100644 index 0000000000..a64ddd20d8 --- /dev/null +++ b/qpid/cpp/src/qpid/client/MessageImpl.h @@ -0,0 +1,80 @@ +#ifndef QPID_CLIENT_MESSAGEIMPL_H +#define QPID_CLIENT_MESSAGEIMPL_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/Session.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/TransferContent.h" +#include <string> + +namespace qpid { +namespace client { + +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. + */ + 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 + * the delivery tag identifyig the local subscription (often this + * is the name of the subscribed queue.) + */ + std::string getDestination() const; + + /** Check the redelivered flag. */ + bool isRedelivered() const; + /** Set the redelivered flag. */ + void setRedelivered(bool redelivered); + + /** Get a modifyable reference to the message headers. */ + framing::FieldTable& getHeaders(); + + /** Get a non-modifyable reference to the message headers. */ + const framing::FieldTable& getHeaders() const; + + ///@internal + const framing::MessageTransferBody& getMethod() const; + ///@internal + const framing::SequenceNumber& getId() const; + + /**@internal for incoming messages */ + 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: + framing::MessageTransferBody method; + framing::SequenceNumber id; +}; + +}} + +#endif /*!QPID_CLIENT_MESSAGEIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/MessageListener.cpp b/qpid/cpp/src/qpid/client/MessageListener.cpp new file mode 100644 index 0000000000..0f2a71287c --- /dev/null +++ b/qpid/cpp/src/qpid/client/MessageListener.cpp @@ -0,0 +1,24 @@ +/* + * + * 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/MessageListener.h" + +qpid::client::MessageListener::~MessageListener() {} diff --git a/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp b/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp new file mode 100644 index 0000000000..3afaae74e8 --- /dev/null +++ b/qpid/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 == 0)) { + 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/qpid/cpp/src/qpid/client/PrivateImplRef.h b/qpid/cpp/src/qpid/client/PrivateImplRef.h new file mode 100644 index 0000000000..503a383c31 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/QueueOptions.cpp b/qpid/cpp/src/qpid/client/QueueOptions.cpp new file mode 100644 index 0000000000..f4c1483859 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/RdmaConnector.cpp b/qpid/cpp/src/qpid/client/RdmaConnector.cpp new file mode 100644 index 0000000000..664640f5e7 --- /dev/null +++ b/qpid/cpp/src/qpid/client/RdmaConnector.cpp @@ -0,0 +1,431 @@ +/* + * + * 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/framing/InitiationHandler.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/rdma/rdma_exception.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 +{ + 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 dataConnectedLock; + bool dataConnected; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + Rdma::AsynchIO* aio; + Rdma::Connector* acon; + sys::Poller::shared_ptr poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + ~RdmaConnector(); + + // 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(); + 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 dataError(Rdma::AsynchIO&); + void drained(); + void connectionStopped(Rdma::Connector* acon, Rdma::AsynchIO* aio); + void dataStopped(Rdma::AsynchIO* aio); + + std::string identifier; + + void connect(const std::string& host, const std::string& 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>); + const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; } + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +public: + RdmaConnector(Poller::shared_ptr, + framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); +}; + +// Static constructor which registers connector here +namespace { + Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new RdmaConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("rdma", &create); + Connector::registerFactory("ib", &create); + }; + } init; +} + + +RdmaConnector::RdmaConnector(Poller::shared_ptr p, + ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + dataConnected(false), + shutdownHandler(0), + aio(0), + acon(0), + poller(p) +{ + QPID_LOG(debug, "RdmaConnector created for " << version); +} + +namespace { + void deleteAsynchIO(Rdma::AsynchIO& aio) { + delete &aio; + } + + void deleteConnector(Rdma::ConnectionManager& con) { + delete &con; + } +} + +RdmaConnector::~RdmaConnector() { + QPID_LOG(debug, "~RdmaConnector " << identifier); + if (aio) { + aio->stop(deleteAsynchIO); + } + if (acon) { + acon->stop(deleteConnector); + } +} + +void RdmaConnector::connect(const std::string& host, const std::string& port){ + Mutex::ScopedLock l(dataConnectedLock); + assert(!dataConnected); + + acon = new Rdma::Connector( + 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), + boost::bind(&RdmaConnector::rejected, this, poller, _1, _2)); + + SocketAddress sa(host, port); + acon->start(poller, sa); +} + +// The following only gets run when connected +void RdmaConnector::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp) { + try { + Mutex::ScopedLock l(dataConnectedLock); + assert(!dataConnected); + Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair(); + + aio = new Rdma::AsynchIO(ci->getQueuePair(), + cp.rdmaProtocolVersion, + 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::dataError, this, _1)); + + identifier = str(format("[%1% %2%]") % ci->getLocalName() % ci->getPeerName()); + ProtocolInitiation init(version); + writeDataBlock(init); + + aio->start(poller); + + dataConnected = true; + + return; + } catch (const Rdma::Exception& e) { + QPID_LOG(error, "Rdma: Cannot create new connection (Rdma exception): " << e.what()); + } catch (const std::exception& e) { + QPID_LOG(error, "Rdma: Cannot create new connection (unknown exception): " << e.what()); + } + dataConnected = false; + connectionStopped(acon, aio); +} + +void RdmaConnector::connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, Rdma::ErrorType) { + QPID_LOG(debug, "Connection Error " << identifier); + connectionStopped(acon, aio); +} + +// Bizarrely we seem to get rejected events *after* we've already got a connected event for some peer disconnects +// so we need to check whether the data connection is started or not in here +void RdmaConnector::rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams& cp) { + QPID_LOG(debug, "Connection Rejected " << identifier << ": " << cp.maxRecvBufferSize); + if (dataConnected) { + disconnected(); + } else { + connectionStopped(acon, aio); + } +} + +void RdmaConnector::disconnected() { + QPID_LOG(debug, "Connection disconnected " << identifier); + { + Mutex::ScopedLock l(dataConnectedLock); + // If we're closed already then we'll get to drained() anyway + if (!dataConnected) return; + dataConnected = false; + } + // Make sure that all the disconnected actions take place on the data "thread" + aio->requestCallback(boost::bind(&RdmaConnector::drained, this)); +} + +void RdmaConnector::dataError(Rdma::AsynchIO&) { + QPID_LOG(debug, "Data Error " << identifier); + { + Mutex::ScopedLock l(dataConnectedLock); + // If we're closed already then we'll get to drained() anyway + if (!dataConnected) return; + dataConnected = false; + } + drained(); +} + +void RdmaConnector::close() { + QPID_LOG(debug, "RdmaConnector::close " << identifier); + { + Mutex::ScopedLock l(dataConnectedLock); + if (!dataConnected) return; + dataConnected = false; + } + aio->drainWriteQueue(boost::bind(&RdmaConnector::drained, this)); +} + +void RdmaConnector::drained() { + QPID_LOG(debug, "RdmaConnector::drained " << identifier); + assert(!dataConnected); + assert(aio); + Rdma::AsynchIO* a = aio; + aio = 0; + a->stop(boost::bind(&RdmaConnector::dataStopped, this, a)); +} + +void RdmaConnector::dataStopped(Rdma::AsynchIO* a) { + QPID_LOG(debug, "RdmaConnector::dataStopped " << identifier); + assert(!dataConnected); + assert(acon); + Rdma::Connector* c = acon; + acon = 0; + c->stop(boost::bind(&RdmaConnector::connectionStopped, this, c, a)); +} + +void RdmaConnector::connectionStopped(Rdma::Connector* c, Rdma::AsynchIO* a) { + QPID_LOG(debug, "RdmaConnector::connectionStopped " << identifier); + assert(!dataConnected); + aio = 0; + acon = 0; + delete a; + delete c; + if (shutdownHandler) { + ShutdownHandler* s = shutdownHandler; + shutdownHandler = 0; + s->shutdown(); + } +} + +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) { + // It is possible that we are called to write after we are already shutting down + Mutex::ScopedLock l(dataConnectedLock); + if (!dataConnected) return; + + 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(); +} + +// Called in IO thread. (write idle routine) +// This is NOT only called in response to previously calling notifyPendingWrite +void RdmaConnector::writebuff(Rdma::AsynchIO&) { + // It's possible to be disconnected and be writable + Mutex::ScopedLock l(dataConnectedLock); + if (!dataConnected) { + return; + } + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (!codec->canEncode()) { + return; + } + Rdma::Buffer* buffer = aio->getSendBuffer(); + if (buffer) { + size_t encoded = codec->encode(buffer->bytes(), buffer->byteCount()); + buffer->dataCount(encoded); + aio->queueWrite(buffer); + } +} + +bool RdmaConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return aio->writable() && (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->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->getSendBuffer(); + framing::Buffer out(buff->bytes(), buff->byteCount()); + data.encode(out); + buff->dataCount(data.encodedSize()); + aio->queueWrite(buff); +} + +void RdmaConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Results.cpp b/qpid/cpp/src/qpid/client/Results.cpp new file mode 100644 index 0000000000..0de3e8bd04 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Results.cpp @@ -0,0 +1,75 @@ +/* + * + * 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/Results.h" +#include "qpid/client/FutureResult.h" +#include "qpid/framing/SequenceSet.h" + +using namespace qpid::framing; + +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++) { + i->second->completed(); + } + listeners.clear(); +} + +void Results::completed(const SequenceSet& set) +{ + //call complete on those listeners whose ids fall within the set + Listeners::iterator i = listeners.begin(); + while (i != listeners.end()) { + if (set.contains(i->first)) { + i->second->completed(); + listeners.erase(i++); + } else { + i++; + } + } +} + +void Results::received(const SequenceNumber& id, const std::string& result) +{ + Listeners::iterator i = listeners.find(id); + if (i != listeners.end()) { + i->second->received(result); + listeners.erase(i); + } +} + +Results::FutureResultPtr Results::listenForResult(const SequenceNumber& id) +{ + FutureResultPtr l(new FutureResult()); + listeners[id] = l; + return l; +} + +}} diff --git a/qpid/cpp/src/qpid/client/Results.h b/qpid/cpp/src/qpid/client/Results.h new file mode 100644 index 0000000000..4c49f6b05b --- /dev/null +++ b/qpid/cpp/src/qpid/client/Results.h @@ -0,0 +1,56 @@ +/* + * + * 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/framing/SequenceNumber.h" +#include <map> +#include <boost/shared_ptr.hpp> + +#ifndef _Results_ +#define _Results_ + +namespace qpid { +namespace client { + +class FutureResult; + +///@internal +class Results +{ +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); + void close(); + +private: + typedef std::map<framing::SequenceNumber, FutureResultPtr> Listeners; + Listeners listeners; +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp b/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp new file mode 100644 index 0000000000..e114b7aacc --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp @@ -0,0 +1,85 @@ +/* + * + * 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/SessionBase_0_10.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionAccess.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() +{ + if (impl) impl->close(); +} + +void SessionBase_0_10::flush() +{ + impl->sendFlush(); +} + +void SessionBase_0_10::sync() +{ + ExecutionSyncBody b; + b.setSync(true); + 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); +} + +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(); } + +bool SessionBase_0_10::isValid() const { return impl; } + +Connection SessionBase_0_10::getConnection() +{ + Connection c; + ConnectionAccess::setImpl(c, impl->getConnection()); + return c; +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h b/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h new file mode 100644 index 0000000000..4d08a7ceaf --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLIENT_SESSIONBASEACCESS_H +#define QPID_CLIENT_SESSIONBASEACCESS_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/SessionBase_0_10.h" + +/**@file @internal Internal use only */ + +namespace qpid { +namespace client { + +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() const { return sb.impl; } + private: + SessionBase_0_10& sb; +}; +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SESSIONBASEACCESS_H*/ diff --git a/qpid/cpp/src/qpid/client/SessionImpl.cpp b/qpid/cpp/src/qpid/client/SessionImpl.cpp new file mode 100644 index 0000000000..b507625b11 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionImpl.cpp @@ -0,0 +1,824 @@ +/* + * + * 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/SessionImpl.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/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; } + +namespace qpid { +namespace client { + +using namespace qpid::framing; +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, boost::shared_ptr<ConnectionImpl> conn) + : state(INACTIVE), + detachedLifetime(0), + maxFrameSize(conn->getNegotiatedSettings().maxFrameSize), + id(conn->getNegotiatedSettings().username, name.empty() ? Uuid(true).str() : name), + connection(conn), + ioHandler(*this), + proxy(ioHandler), + nextIn(0), + nextOut(0), + sendMsgCredit(0), + doClearDeliveryPropertiesExchange(true), + autoDetach(true) +{ + channel.next = connection.get(); +} + +SessionImpl::~SessionImpl() { + { + Lock l(state); + if (state != DETACHED && state != DETACHING) { + if (autoDetach) { + QPID_LOG(warning, "Session was not closed cleanly: " << id); + // Inform broker but don't wait for detached as that deadlocks. + // The detached will be ignored as the channel will be invalid. + try { detach(); } catch (...) {} // ignore errors. + } + setState(DETACHED); + handleClosed(); + state.waitWaiters(); + } + delete sendMsgCredit; + } + connection->erase(channel); +} + + +FrameSet::shared_ptr SessionImpl::get() // user thread +{ + // No lock here: pop does a blocking wait. + return demux.getDefault()->pop(); +} + +const SessionId SessionImpl::getId() const //user thread +{ + return id; //id is immutable +} + +void SessionImpl::open(uint32_t timeout) // user thread +{ + Lock l(state); + if (state == INACTIVE) { + setState(ATTACHING); + proxy.attach(id.getName(), false); + waitFor(ATTACHED); + //TODO: timeout will not be set locally until get response to + //confirm, should we wait for that? + setTimeout(timeout); + proxy.commandPoint(nextOut, 0); + } else { + throw Exception("Open already called for this session"); + } +} + +void SessionImpl::close() //user thread +{ + Lock l(state); + // 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); + } +} + +void SessionImpl::resume(boost::shared_ptr<ConnectionImpl>) // user thread +{ + throw NotImplementedException("Resume not yet implemented by client!"); +} + +void SessionImpl::suspend() //user thread +{ + Lock l(state); + detach(); +} + +void SessionImpl::detach() //call with lock held +{ + if (state == ATTACHED) { + setState(DETACHING); + proxy.detach(id.getName()); + } +} + + +uint16_t SessionImpl::getChannel() const // user thread +{ + return channel; +} + +void SessionImpl::setChannel(uint16_t c) // user thread +{ + //channel will only ever be set when session is detached (and + //about to be resumed) + channel = c; +} + +Demux& SessionImpl::getDemux() +{ + return demux; +} + +void SessionImpl::waitForCompletion(const SequenceNumber& id) +{ + Lock l(state); + waitForCompletionImpl(id); +} + +void SessionImpl::waitForCompletionImpl(const SequenceNumber& id) //call with lock held +{ + while (incompleteOut.contains(id)) { + checkOpen(); + state.wait(); + } +} + +bool SessionImpl::isComplete(const SequenceNumber& id) +{ + Lock l(state); + return !incompleteOut.contains(id); +} + +struct IsCompleteUpTo +{ + const SequenceNumber& id; + bool result; + + IsCompleteUpTo(const SequenceNumber& _id) : id(_id), result(true) {} + void operator()(const SequenceNumber& start, const SequenceNumber&) + { + if (start <= id) result = false; + } + +}; + +bool SessionImpl::isCompleteUpTo(const SequenceNumber& id) +{ + Lock l(state); + //return false if incompleteOut contains anything less than id, + //true otherwise + IsCompleteUpTo f(id); + incompleteIn.for_each(f); + return f.result; +} + +framing::SequenceNumber SessionImpl::getCompleteUpTo() +{ + SequenceNumber firstIncomplete; + { + Lock l(state); + firstIncomplete = incompleteIn.front(); + } + return --firstIncomplete; +} + +struct MarkCompleted +{ + const SequenceNumber& id; + SequenceSet& completedIn; + + MarkCompleted(const SequenceNumber& _id, SequenceSet& set) : id(_id), completedIn(set) {} + + void operator()(const SequenceNumber& start, const SequenceNumber& end) + { + if (id >= end) { + completedIn.add(start, end); + } else if (id >= start) { + completedIn.add(start, id); + } + } + +}; + +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); + if (cumulative) { + //everything in incompleteIn less than or equal to id is now complete + MarkCompleted f(id, completedIn); + incompleteIn.for_each(f); + //make sure id itself is in + completedIn.add(id); + //then remove anything thats completed from the incomplete set + incompleteIn.remove(completedIn); + } else if (incompleteIn.contains(id)) { + incompleteIn.remove(id); + completedIn.add(id); + } + if (notifyPeer) { + sendCompletion(); + } +} + +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) { + setException(createConnectionException(code, text)); + handleClosed(); +} + +/** + * Called by ConnectionImpl to notify active sessions when connection + * is disconnected + */ +void SessionImpl::connectionBroke(const std::string& _text) { + setException(sys::ExceptionHolder(new TransportFailure(_text))); + handleClosed(); +} + +Future SessionImpl::send(const AMQBody& command) +{ + return sendCommand(command); +} + +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++; + { + 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); + if (content) { + frame.setEof(false); + } + handleOut(frame); + if (content) { + sendContent(*content); + } + 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){ + header.setLastSegment(false); + handleOut(header); + /*Note: end of frame marker included in overhead but not in size*/ + const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead(); + + if(data_length < frag_size){ + AMQFrame frame((AMQContentBody(content.getData()))); + frame.setFirstSegment(false); + handleOut(frame); + }else{ + uint32_t offset = 0; + uint32_t remaining = data_length - offset; + while (remaining > 0) { + uint32_t length = remaining > frag_size ? frag_size : remaining; + string frag(content.getData().substr(offset, length)); + AMQFrame frame((AMQContentBody(frag))); + frame.setFirstSegment(false); + frame.setLastSegment(true); + if (offset > 0) { + frame.setFirstFrame(false); + } + offset += length; + remaining = data_length - offset; + if (remaining) { + frame.setLastFrame(false); + } + handleOut(frame); + } + } + } else { + handleOut(header); + } +} + + +bool isMessageMethod(AMQMethodBody* method) +{ + return method->isA<MessageTransferBody>(); +} + +bool isMessageMethod(AMQBody* body) +{ + AMQMethodBody* method=body->getMethod(); + return method && isMessageMethod(method); +} + +bool isContentFrame(AMQFrame& frame) +{ + AMQBody* body = frame.getBody(); + uint8_t type = body->type(); + return type == HEADER_BODY || type == CONTENT_BODY || isMessageMethod(body); +} + +void SessionImpl::handleIn(AMQFrame& frame) // network thread +{ + try { + 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) { + setException(createSessionException(e.code, e.getMessage())); + } + catch (const ChannelException& e) { + setException(createChannelException(e.code, e.getMessage())); + } +} + +void SessionImpl::handleOut(AMQFrame& frame) // user thread +{ + sendFrame(frame, true); +} + +void SessionImpl::proxyOut(AMQFrame& frame) // network thread +{ + //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) +{ + connection->expand(frame.encodedSize(), canBlock); + channel.handle(frame); +} + +void SessionImpl::deliver(AMQFrame& frame) // network thread +{ + if (!arriving) { + arriving = FrameSet::shared_ptr(new FrameSet(nextIn++)); + } + arriving->append(frame); + if (arriving->isComplete()) { + //message.transfers will be marked completed only when 'acked' + //as completion affects flow control; other commands will be + //considered completed as soon as processed here + if (arriving->isA<MessageTransferBody>()) { + Lock l(state); + incompleteIn.add(arriving->getId()); + } else { + Lock l(state); + completedIn.add(arriving->getId()); + } + demux.handle(arriving); + arriving.reset(); + } +} + +//control handler methods (called by network thread when controls are +//received from peer): + +void SessionImpl::attach(const std::string& /*name*/, bool /*force*/) +{ + throw NotImplementedException("Client does not support attach"); +} + +void SessionImpl::attached(const std::string& _name) +{ + Lock l(state); + if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); + setState(ATTACHED); +} + +void SessionImpl::detach(const std::string& _name) +{ + Lock l(state); + 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); + handleClosed(); +} + +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 + setExceptionLH(createChannelException(_code, "Session detached by peer")); + QPID_LOG(error, exceptionHolder.what()); + } + if (detachedLifetime == 0) { + handleClosed(); +} +} + +void SessionImpl::requestTimeout(uint32_t t) +{ + Lock l(state); + detachedLifetime = t; + proxy.timeout(t); +} + +void SessionImpl::timeout(uint32_t t) +{ + Lock l(state); + detachedLifetime = t; +} + +void SessionImpl::commandPoint(const framing::SequenceNumber& id, uint64_t offset) +{ + if (offset) throw NotImplementedException("Non-zero byte offset not yet supported for command-point"); + + Lock l(state); + nextIn = id; +} + +void SessionImpl::expected(const framing::SequenceSet& commands, const framing::Array& fragments) +{ + if (!commands.empty() || fragments.encodedSize()) { + throw NotImplementedException("Session resumption not yet supported"); + } +} + +void SessionImpl::confirmed(const framing::SequenceSet& /*commands*/, const framing::Array& /*fragments*/) +{ + //don't really care too much about this yet +} + +void SessionImpl::completed(const framing::SequenceSet& commands, bool timelyReply) +{ + Lock l(state); + incompleteOut.remove(commands); + state.notifyAll();//notify any waiters of completion + completedOut.add(commands); + //notify any waiting results of completion + results.completed(commands); + + if (timelyReply) { + proxy.knownCompleted(completedOut); + completedOut.clear(); + } +} + +void SessionImpl::knownCompleted(const framing::SequenceSet& commands) +{ + Lock l(state); + completedIn.remove(commands); +} + +void SessionImpl::flush(bool expected, bool confirmed, bool completed) +{ + Lock l(state); + if (expected) { + proxy.expected(SequenceSet(nextIn), Array()); + } + if (confirmed) { + proxy.confirmed(completedIn, Array()); + } + if (completed) { + proxy.completed(completedIn, true); + } +} + +void SessionImpl::sendCompletion() +{ + Lock l(state); + sendCompletionImpl(); +} + +void SessionImpl::sendFlush() +{ + Lock l(state); + proxy.flush(false, false, true); +} + +void SessionImpl::sendCompletionImpl() +{ + proxy.completed(completedIn, completedIn.span() > 1000); +} + +void SessionImpl::gap(const framing::SequenceSet& /*commands*/) +{ + throw NotImplementedException("gap not yet supported"); +} + +void SessionImpl::sync() {} + +void SessionImpl::result(const framing::SequenceNumber& commandId, const std::string& value) +{ + Lock l(state); + results.received(commandId, value); +} + +void SessionImpl::exception(uint16_t errorCode, + const framing::SequenceNumber& commandId, + uint8_t classCode, + uint8_t commandCode, + uint8_t /*fieldIndex*/, + const std::string& description, + const framing::FieldTable& /*errorInfo*/) +{ + 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); + 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: + +inline void SessionImpl::setState(State s) //call with lock held +{ + state = s; +} + +inline void SessionImpl::waitFor(State s) //call with lock held +{ + // We can be DETACHED at any time + if (s == DETACHED) state.waitFor(DETACHED); + else state.waitFor(States(s, DETACHED)); + check(); +} + +void SessionImpl::check() const //call with lock held. +{ + exceptionHolder.raise(); +} + +void SessionImpl::checkOpen() const //call with lock held. +{ + check(); + if (state != ATTACHED) { + throw NotAttachedException(QPID_MSG("Session " << getId() << " isn't attached")); + } +} + +void SessionImpl::assertOpen() const +{ + Lock l(state); + checkOpen(); +} + +bool SessionImpl::hasError() const +{ + Lock l(state); + return !exceptionHolder.empty(); +} + +void SessionImpl::handleClosed() +{ + 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/qpid/cpp/src/qpid/client/SessionImpl.h b/qpid/cpp/src/qpid/client/SessionImpl.h new file mode 100644 index 0000000000..cd7b2c123d --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionImpl.h @@ -0,0 +1,254 @@ +/* + * + * 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 _SessionImpl_ +#define _SessionImpl_ + +#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/SessionState.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/ChannelHandler.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/AMQP_ClientOperations.h" +#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 { + +namespace framing { + +class FrameSet; +class MethodContent; +class SequenceSet; + +} + +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::MessageHandler +{ +public: + SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl>); + ~SessionImpl(); + + + //NOTE: Public functions called in user thread. + framing::FrameSet::shared_ptr get(); + + const SessionId getId() const; + + uint16_t getChannel() const; + void setChannel(uint16_t channel); + + void open(uint32_t detachedLifetime); + void close(); + void resume(boost::shared_ptr<ConnectionImpl>); + void suspend(); + + QPID_CLIENT_EXTERN void assertOpen() const; + QPID_CLIENT_EXTERN bool hasError() 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(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 State { + INACTIVE, + ATTACHING, + ATTACHED, + DETACHING, + DETACHED + }; + 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; + void checkOpen() const; + void handleClosed(); + + 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 sendCompletionImpl(); + + // Note: Following methods are called by network thread in + // response to session controls from the broker + 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 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); + void confirmed(const framing::SequenceSet& commands, const framing::Array& fragments); + void completed(const framing::SequenceSet& commands, bool timelyReply); + void knownCompleted(const framing::SequenceSet& commands); + void flush(bool expected, bool confirmed, bool completed); + void gap(const framing::SequenceSet& commands); + + // Note: Following methods are called by network thread in + // response to execution commands from the broker + void sync(); + void result(const framing::SequenceNumber& commandId, const std::string& value); + void exception(uint16_t errorCode, + const framing::SequenceNumber& commandId, + uint8_t classCode, + uint8_t commandCode, + uint8_t fieldIndex, + const std::string& description, + const framing::FieldTable& errorInfo); + + // 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; + + boost::shared_ptr<ConnectionImpl> connection; + + framing::FrameHandler::MemFunRef<SessionImpl, &SessionImpl::proxyOut> ioHandler; + framing::ChannelHandler channel; + framing::AMQP_ServerProxy::Session proxy; + + Results results; + Demux demux; + framing::FrameSet::shared_ptr arriving; + + framing::SequenceSet incompleteIn;//incoming commands that are as yet incomplete + framing::SequenceSet completedIn;//incoming commands that are have completed + framing::SequenceSet incompleteOut;//outgoing commands not yet known to be complete + framing::SequenceSet completedOut;//outgoing commands that we know to be completed + 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 + +#endif diff --git a/qpid/cpp/src/qpid/client/SslConnector.cpp b/qpid/cpp/src/qpid/client/SslConnector.cpp new file mode 100644 index 0000000000..f121cfb1ab --- /dev/null +++ b/qpid/cpp/src/qpid/client/SslConnector.cpp @@ -0,0 +1,381 @@ +/* + * + * 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/framing/InitiationHandler.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/sys/SecuritySettings.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 +{ + 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; + SecuritySettings securitySettings; + + sys::Mutex closedLock; + bool closed; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + Writer writer; + + sys::ssl::SslSocket socket; + + sys::ssl::SslIO* aio; + Poller::shared_ptr poller; + + ~SslConnector(); + + 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&); + void disconnected(qpid::sys::ssl::SslIO&); + + std::string identifier; + + void connect(const std::string& host, const std::string& 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; + const SecuritySettings* getSecuritySettings(); + void socketClosed(qpid::sys::ssl::SslIO&, const qpid::sys::ssl::SslSocket&); + +public: + SslConnector(Poller::shared_ptr p, framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); +}; + +struct SslConnector::Buff : public SslIO::BufferBase { + Buff(size_t size) : SslIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +// Static constructor which registers connector here +namespace { + Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new SslConnector(p, 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(Poller::shared_ptr p, + ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + version(ver), + initiated(false), + closed(true), + shutdownHandler(0), + writer(maxFrameSize, cimpl), + aio(0), + poller(p) +{ + QPID_LOG(debug, "SslConnector created for " << version.toString()); + + if (settings.sslCertName != "") { + QPID_LOG(debug, "ssl-cert-name = " << settings.sslCertName); + socket.setCertName(settings.sslCertName); + } +} + +SslConnector::~SslConnector() { + close(); +} + +void SslConnector::connect(const std::string& host, const std::string& port){ + Mutex::ScopedLock l(closedLock); + assert(closed); + try { + socket.connect(host, port); + } catch (const std::exception& e) { + socket.close(); + throw ConnectionException(framing::connection::CLOSE_CODE_FRAMING_ERROR, e.what()); + } + + identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + closed = false; + aio = new SslIO(socket, + boost::bind(&SslConnector::readbuff, this, _1, _2), + boost::bind(&SslConnector::eof, this, _1), + boost::bind(&SslConnector::disconnected, this, _1), + boost::bind(&SslConnector::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&SslConnector::writebuff, this, _1)); + writer.init(identifier, aio); +} + +void SslConnector::init(){ + Mutex::ScopedLock l(closedLock); + ProtocolInitiation init(version); + writeDataBlock(init); + for (int i = 0; i < 32; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + aio->start(poller); +} + +void SslConnector::close() { + Mutex::ScopedLock l(closedLock); + if (!closed) { + closed = true; + if (aio) + aio->queueWriteClose(); + } +} + +void SslConnector::socketClosed(SslIO&, const SslSocket&) { + if (aio) + aio->queueForDeletion(); + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +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); +} + +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&) { + close(); +} + +void SslConnector::disconnected(SslIO&) { + close(); + socketClosed(*aio, socket); +} + +const SecuritySettings* SslConnector::getSecuritySettings() +{ + securitySettings.ssf = socket.getKeyLen(); + securitySettings.authid = "dummy";//set to non-empty string to enable external authentication + return &securitySettings; +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/StateManager.cpp b/qpid/cpp/src/qpid/client/StateManager.cpp new file mode 100644 index 0000000000..839d92abdc --- /dev/null +++ b/qpid/cpp/src/qpid/client/StateManager.cpp @@ -0,0 +1,100 @@ +/* + * + * 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/StateManager.h" +#include "qpid/framing/amqp_framing.h" + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; + +StateManager::StateManager(int s) : state(s) {} + +void StateManager::waitForStateChange(int current) +{ + Monitor::ScopedLock l(stateLock); + while (state == current) { + stateLock.wait(); + } +} + +void StateManager::waitFor(int desired) +{ + Monitor::ScopedLock l(stateLock); + while (state != desired) { + stateLock.wait(); + } +} + +void StateManager::waitFor(std::set<int> desired) +{ + Monitor::ScopedLock l(stateLock); + while (desired.find(state) == desired.end()) { + stateLock.wait(); + } +} + +bool StateManager::waitFor(int desired, qpid::sys::Duration timeout) +{ + AbsTime end(now(), timeout); + Monitor::ScopedLock l(stateLock); + while (state != desired && now() < end) { + stateLock.wait(end); + } + return state == desired; +} + +bool StateManager::waitFor(std::set<int> desired, qpid::sys::Duration timeout) +{ + AbsTime end(now(), timeout); + Monitor::ScopedLock l(stateLock); + while (desired.find(state) == desired.end() && now() < end) { + stateLock.wait(end); + } + return desired.find(state) != desired.end(); +} + + +void StateManager::setState(int s) +{ + Monitor::ScopedLock l(stateLock); + state = 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); + return state; +} + diff --git a/qpid/cpp/src/qpid/client/StateManager.h b/qpid/cpp/src/qpid/client/StateManager.h new file mode 100644 index 0000000000..f06dbc493c --- /dev/null +++ b/qpid/cpp/src/qpid/client/StateManager.h @@ -0,0 +1,50 @@ +/* + * + * 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 _StateManager_ +#define _StateManager_ + +#include <set> +#include "qpid/sys/Monitor.h" + +namespace qpid { +namespace client { + +///@internal +class StateManager +{ + int state; + mutable sys::Monitor stateLock; + +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); + void waitFor(int state); + bool waitFor(std::set<int> states, qpid::sys::Duration); + bool waitFor(int state, qpid::sys::Duration); +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/Subscription.cpp b/qpid/cpp/src/qpid/client/Subscription.cpp new file mode 100644 index 0000000000..988f372604 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp b/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp new file mode 100644 index 0000000000..a8a0b47d94 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/SubscriptionImpl.h b/qpid/cpp/src/qpid/client/SubscriptionImpl.h new file mode 100644 index 0000000000..da77213423 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/SubscriptionManager.cpp b/qpid/cpp/src/qpid/client/SubscriptionManager.cpp new file mode 100644 index 0000000000..485361d577 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SubscriptionManager.cpp @@ -0,0 +1,106 @@ +/* + * + * 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/PrivateImplRef.h" + + +namespace qpid { +namespace client { + +typedef PrivateImplRef<SubscriptionManager> PI; + +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); } + +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(listener, q, ss, n); } + +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(lq, q, ss, n); } + + +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ return impl->subscribe(listener, q, n); } + + +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ return impl->subscribe(lq, q, n); } + +void SubscriptionManager::cancel(const std::string& dest) { return impl->cancel(dest); } + +void SubscriptionManager::setAutoStop(bool set) { impl->setAutoStop(set); } + +void SubscriptionManager::run() { impl->run(); } + +void SubscriptionManager::start() { impl->start(); } + +void SubscriptionManager::wait() { impl->wait(); } + +void SubscriptionManager::stop() { impl->stop(); } + +bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) { + return impl->get(result, queue, timeout); +} + +Message SubscriptionManager::get(const std::string& queue, sys::Duration timeout) { + return impl->get(queue, timeout); +} + +Session SubscriptionManager::getSession() const { return impl->getSession(); } + +Subscription SubscriptionManager::getSubscription(const std::string& name) const { + return impl->getSubscription(name); +} +void SubscriptionManager::registerFailoverHandler (boost::function<void ()> fh) { + impl->registerFailoverHandler(fh); +} + +void SubscriptionManager::setFlowControl(const std::string& name, const FlowControl& flow) { + impl->setFlowControl(name, flow); +} + +void SubscriptionManager::setDefaultSettings(const SubscriptionSettings& s){ + impl->setDefaultSettings(s); +} + +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 + + diff --git a/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp new file mode 100644 index 0000000000..a558d90be8 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h new file mode 100644 index 0000000000..6376a05c45 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/client/TCPConnector.cpp b/qpid/cpp/src/qpid/client/TCPConnector.cpp new file mode 100644 index 0000000000..0070b24ec0 --- /dev/null +++ b/qpid/cpp/src/qpid/client/TCPConnector.cpp @@ -0,0 +1,331 @@ +/* + * + * 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/framing/InitiationHandler.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; + +struct TCPConnector::Buff : public AsynchIO::BufferBase { + Buff(size_t size) : AsynchIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +// Static constructor which registers connector here +namespace { + Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new TCPConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("tcp", &create); + }; + } init; +} + +TCPConnector::TCPConnector(Poller::shared_ptr p, + ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + closed(true), + shutdownHandler(0), + connector(0), + aio(0), + poller(p) +{ + QPID_LOG(debug, "TCPConnector created for " << version); + settings.configureSocket(socket); +} + +TCPConnector::~TCPConnector() { + close(); +} + +void TCPConnector::connect(const std::string& host, const std::string& port) { + Mutex::ScopedLock l(lock); + assert(closed); + connector = AsynchConnector::create( + socket, + host, port, + boost::bind(&TCPConnector::connected, this, _1), + boost::bind(&TCPConnector::connectFailed, this, _3)); + closed = false; + + connector->start(poller); +} + +void TCPConnector::connected(const Socket&) { + connector = 0; + aio = AsynchIO::create(socket, + boost::bind(&TCPConnector::readbuff, this, _1, _2), + boost::bind(&TCPConnector::eof, this, _1), + boost::bind(&TCPConnector::disconnected, this, _1), + boost::bind(&TCPConnector::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&TCPConnector::writebuff, this, _1)); + start(aio); + initAmqp(); + aio->start(poller); +} + +void TCPConnector::start(sys::AsynchIO* aio_) { + aio = aio_; + for (int i = 0; i < 4; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + + identifier = str(format("[%1%]") % socket.getFullAddress()); +} + +void TCPConnector::initAmqp() { + ProtocolInitiation init(version); + writeDataBlock(init); +} + +void TCPConnector::connectFailed(const std::string& msg) { + connector = 0; + QPID_LOG(warning, "Connect failed: " << msg); + socket.close(); + if (!closed) + closed = true; + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +void TCPConnector::close() { + Mutex::ScopedLock l(lock); + if (!closed) { + closed = true; + if (aio) + aio->queueWriteClose(); + } +} + +void TCPConnector::socketClosed(AsynchIO&, const Socket&) { + if (aio) + aio->queueForDeletion(); + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +void TCPConnector::abort() { + // Can't abort a closed connection + if (!closed) { + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&TCPConnector::eof, this, _1)); + } else if (connector) { + // We're still connecting + connector->stop(); + 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) { + 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); + } + /* + NOTE: Moving the following line into this mutex block + is a workaround for BZ 570168, in which the test + testConcurrentSenders causes a hang about 1.5% + of the time. ( To see the hang much more frequently + leave this line out of the mutex block, and put a + small usleep just before it.) + + TODO mgoulish - fix the underlying cause and then + move this call back outside the mutex. + */ + if (notifyWrite && !closed) aio->notifyPendingWrite(); + } +} + +void TCPConnector::writebuff(AsynchIO& /*aio*/) +{ + // It's possible to be disconnected and be writable + if (closed) + return; + + 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 = aio->getQueuedBuffer(); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void TCPConnector::eof(AsynchIO&) { + close(); +} + +void TCPConnector::disconnected(AsynchIO&) { + close(); + socketClosed(*aio, socket); +} + +void TCPConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/TCPConnector.h b/qpid/cpp/src/qpid/client/TCPConnector.h new file mode 100644 index 0000000000..eb3f696013 --- /dev/null +++ b/qpid/cpp/src/qpid/client/TCPConnector.h @@ -0,0 +1,120 @@ +/* + * + * 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 framing { + class InitiationHandler; +} + +namespace client { + +class TCPConnector : public Connector, public sys::Codec +{ + 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; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + sys::Socket socket; + + sys::AsynchConnector* connector; + sys::AsynchIO* aio; + std::string identifier; + boost::shared_ptr<sys::Poller> poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + virtual void connected(const sys::Socket&); + void writeDataBlock(const framing::AMQDataBlock& data); + + 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>); + const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; } + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +protected: + virtual ~TCPConnector(); + void connect(const std::string& host, const std::string& port); + void start(sys::AsynchIO* aio_); + void initAmqp(); + virtual void connectFailed(const std::string& msg); + bool readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void writebuff(qpid::sys::AsynchIO&); + void eof(qpid::sys::AsynchIO&); + void disconnected(qpid::sys::AsynchIO&); + void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&); + +public: + TCPConnector(boost::shared_ptr<sys::Poller>, + framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); +}; + +}} // namespace qpid::client + +#endif /* _TCPConnector_ */ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp new file mode 100644 index 0000000000..bfb20118b5 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.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 "AcceptTracker.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +void AcceptTracker::State::accept() +{ + unconfirmed.add(unaccepted); + unaccepted.clear(); +} + +void AcceptTracker::State::accept(qpid::framing::SequenceNumber id) +{ + if (unaccepted.contains(id)) { + unaccepted.remove(id); + unconfirmed.add(id); + } +} + +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::accept(qpid::framing::SequenceNumber id, qpid::client::AsyncSession& session) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.accept(id); + } + Record record; + record.accepted.add(id); + record.status = session.messageAccept(record.accepted); + pending.push_back(record); + aggregateState.accept(id); +} + +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/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h new file mode 100644 index 0000000000..87890e41cc --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h @@ -0,0 +1,87 @@ +#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 accept(qpid::framing::SequenceNumber, 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 accept has been issued but not + * yet confirmed as completed + */ + qpid::framing::SequenceSet unconfirmed; + + void accept(); + void accept(qpid::framing::SequenceNumber); + 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/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp new file mode 100644 index 0000000000..f1295a3b66 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -0,0 +1,966 @@ +/* + * + * 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/amqp_0_10/Codecs.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/types/Variant.h" +#include "qpid/messaging/exceptions.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::AddressError; +using qpid::messaging::MalformedAddress; +using qpid::messaging::ResolutionError; +using qpid::messaging::NotFound; +using qpid::messaging::AssertionFailed; +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::types; +using namespace qpid::framing::message; +using namespace qpid::amqp_0_10; +using namespace boost::assign; + +class Verifier +{ + public: + Verifier(); + void verify(const Address& address) const; + private: + Variant::Map defined; + void verify(const Variant::Map& allowed, const Variant::Map& actual) const; +}; + +namespace{ +const Variant EMPTY_VARIANT; +const FieldTable EMPTY_FIELD_TABLE; +const Variant::List EMPTY_LIST; +const std::string EMPTY_STRING; + +//policy types +const std::string CREATE("create"); +const std::string ASSERT("assert"); +const std::string DELETE("delete"); + +//option names +const std::string NODE("node"); +const std::string LINK("link"); +const std::string MODE("mode"); +const std::string RELIABILITY("reliability"); +const std::string NAME("name"); +const std::string DURABLE("durable"); +const std::string X_DECLARE("x-declare"); +const std::string X_SUBSCRIBE("x-subscribe"); +const std::string X_BINDINGS("x-bindings"); +const std::string EXCHANGE("exchange"); +const std::string QUEUE("queue"); +const std::string KEY("key"); +const std::string ARGUMENTS("arguments"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string TYPE("type"); +const std::string EXCLUSIVE("exclusive"); +const std::string AUTO_DELETE("auto-delete"); + +//policy values +const std::string ALWAYS("always"); +const std::string NEVER("never"); +const std::string RECEIVER("receiver"); +const std::string SENDER("sender"); + +//address types +const std::string QUEUE_ADDRESS("queue"); +const std::string TOPIC_ADDRESS("topic"); + +//reliability options: +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"); + +//receiver modes: +const std::string BROWSE("browse"); +const std::string CONSUME("consume"); + +//0-10 exchange types: +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("#"); + +const Verifier verifier; +} + +struct Binding +{ + Binding(const Variant::Map&); + Binding(const std::string& exchange, const std::string& queue, const std::string& key); + + std::string exchange; + std::string queue; + std::string key; + FieldTable arguments; +}; + +struct Bindings : std::vector<Binding> +{ + void add(const Variant::List& bindings); + void setDefaultExchange(const std::string&); + void setDefaultQueue(const std::string&); + void bind(qpid::client::AsyncSession& session); + void unbind(qpid::client::AsyncSession& session); + void check(qpid::client::AsyncSession& session); +}; + +class Node +{ + protected: + enum CheckMode {FOR_RECEIVER, FOR_SENDER}; + + Node(const Address& address); + + const std::string name; + Variant createPolicy; + Variant assertPolicy; + Variant deletePolicy; + Bindings nodeBindings; + Bindings linkBindings; + + 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: + const bool durable; + const bool autoDelete; + const bool exclusive; + const std::string alternateExchange; + FieldTable arguments; +}; + +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); + + protected: + const std::string specifiedType; + private: + const bool durable; + const bool autoDelete; + const std::string alternateExchange; + FieldTable arguments; +}; + +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& actualType); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + const std::string queue; + const bool reliable; + const bool durable; + const std::string actualType; + FieldTable queueOptions; + FieldTable subscriptionOptions; + Bindings bindings; + + void bindSubject(const std::string& subject); + void bindAll(); + void add(const std::string& exchange, const std::string& key); + static std::string getSubscriptionName(const std::string& base, const std::string& 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; +} + +const Variant& getOption(const Variant::Map& options, const std::string& name) +{ + Variant::Map::const_iterator j = options.find(name); + if (j == options.end()) { + return EMPTY_VARIANT; + } else { + return j->second; + } +} + +const Variant& getOption(const Address& address, const std::string& name) +{ + return getOption(address.getOptions(), name); +} + +bool getReceiverPolicy(const Address& address, const std::string& key) +{ + return in(getOption(address, key), list_of<std::string>(ALWAYS)(RECEIVER)); +} + +bool getSenderPolicy(const Address& address, const std::string& key) +{ + return in(getOption(address, key), list_of<std::string>(ALWAYS)(SENDER)); +} + +struct Opt +{ + Opt(const Address& address); + Opt(const Variant::Map& base); + Opt& operator/(const std::string& name); + operator bool() const; + std::string str() const; + const Variant::List& asList() const; + void collect(qpid::framing::FieldTable& args) const; + + const Variant::Map* options; + const Variant* value; +}; + +Opt::Opt(const Address& address) : options(&(address.getOptions())), value(0) {} +Opt::Opt(const Variant::Map& base) : options(&base), value(0) {} +Opt& Opt::operator/(const std::string& name) +{ + if (options) { + Variant::Map::const_iterator j = options->find(name); + if (j == options->end()) { + value = 0; + options = 0; + } else { + value = &(j->second); + if (value->getType() == VAR_MAP) options = &(value->asMap()); + else options = 0; + } + } + return *this; +} + + +Opt::operator bool() const +{ + return value && !value->isVoid() && value->asBool(); +} + +std::string Opt::str() const +{ + if (value) return value->asString(); + else return EMPTY_STRING; +} + +const Variant::List& Opt::asList() const +{ + if (value) return value->asList(); + else return EMPTY_LIST; +} + +void Opt::collect(qpid::framing::FieldTable& args) const +{ + if (value) { + translate(value->asMap(), args); + } +} + +bool AddressResolution::is_unreliable(const Address& address) +{ + + return in((Opt(address)/LINK/RELIABILITY).str(), + list_of<std::string>(UNRELIABLE)(AT_MOST_ONCE)); +} + +bool AddressResolution::is_reliable(const Address& address) +{ + return in((Opt(address)/LINK/RELIABILITY).str(), + list_of<std::string>(AT_LEAST_ONCE)(EXACTLY_ONCE)); +} + +std::string checkAddressType(qpid::client::Session session, const Address& address) +{ + verifier.verify(address); + if (address.getName().empty()) { + throw MalformedAddress("Name cannot be null"); + } + std::string type = (Opt(address)/NODE/TYPE).str(); + 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 ResolutionError("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::string exchangeType = sync(session).exchangeQuery(address.getName()).getType(); + std::auto_ptr<MessageSource> source(new Subscription(address, exchangeType)); + 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 ResolutionError("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 ResolutionError("Unrecognised type: " + type); + } +} + +bool isBrowse(const Address& address) +{ + const Variant& mode = getOption(address, MODE); + if (!mode.isVoid()) { + std::string value = mode.asString(); + if (value == BROWSE) return true; + else if (value != CONSUME) throw ResolutionError("Invalid mode"); + } + return false; +} + +QueueSource::QueueSource(const Address& address) : + Queue(address), + acceptMode(AddressResolution::is_unreliable(address) ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT), + acquireMode(isBrowse(address) ? ACQUIRE_MODE_NOT_ACQUIRED : ACQUIRE_MODE_PRE_ACQUIRED), + exclusive(false) +{ + //extract subscription arguments from address options (nb: setting + //of accept-mode/acquire-mode/destination controlled though other + //options) + exclusive = Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE; + (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(options); +} + +void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + linkBindings.bind(session); + 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) +{ + linkBindings.unbind(session); + session.messageCancel(destination); + checkDelete(session, FOR_RECEIVER); +} + +std::string Subscription::getSubscriptionName(const std::string& base, const std::string& name) +{ + if (name.empty()) { + return (boost::format("%1%_%2%") % base % Uuid(true).str()).str(); + } else { + return (boost::format("%1%_%2%") % base % name).str(); + } +} + +Subscription::Subscription(const Address& address, const std::string& type) + : Exchange(address), + queue(getSubscriptionName(name, (Opt(address)/LINK/NAME).str())), + reliable(AddressResolution::is_reliable(address)), + durable(Opt(address)/LINK/DURABLE), + actualType(type.empty() ? (specifiedType.empty() ? TOPIC_EXCHANGE : specifiedType) : type) +{ + (Opt(address)/LINK/X_DECLARE/ARGUMENTS).collect(queueOptions); + (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(subscriptionOptions); + + if (!address.getSubject().empty()) bindSubject(address.getSubject()); + else if (linkBindings.empty()) bindAll(); +} + +void Subscription::bindSubject(const std::string& subject) +{ + if (actualType == HEADERS_EXCHANGE) { + Binding b(name, queue, subject); + b.arguments.setString("qpid.subject", subject); + b.arguments.setString("x-match", "all"); + bindings.push_back(b); + } else if (actualType == XML_EXCHANGE) { + Binding b(name, queue, subject); + std::string query = (boost::format("declare variable $qpid.subject external; $qpid.subject = '%1%'") + % subject).str(); + b.arguments.setString("xquery", query); + bindings.push_back(b); + } else { + //Note: the fanout exchange doesn't support any filtering, so + //the subject is ignored in that case + add(name, subject); + } +} + +void Subscription::bindAll() +{ + if (actualType == TOPIC_EXCHANGE) { + add(name, WILDCARD_ANY); + } else if (actualType == FANOUT_EXCHANGE) { + add(name, queue); + } else if (actualType == HEADERS_EXCHANGE) { + Binding b(name, queue, "match-all"); + b.arguments.setString("x-match", "all"); + bindings.push_back(b); + } else if (actualType == XML_EXCHANGE) { + Binding b(name, queue, EMPTY_STRING); + b.arguments.setString("xquery", "true()"); + bindings.push_back(b); + } else { + add(name, EMPTY_STRING); + } +} + +void Subscription::add(const std::string& exchange, const std::string& key) +{ + bindings.push_back(Binding(exchange, queue, key)); +} + +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); + //'default' binding: + bindings.bind(session); + //any explicit bindings: + linkBindings.setDefaultQueue(queue); + linkBindings.bind(session); + //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) +{ + linkBindings.unbind(session); + session.messageCancel(destination); + session.queueDelete(arg::queue=queue); + checkDelete(session, FOR_RECEIVER); +} + +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); + linkBindings.bind(session); +} + +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&) +{ + linkBindings.unbind(session); + 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); + linkBindings.bind(session); +} +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&) +{ + linkBindings.unbind(session); + 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; + } +} + +Node::Node(const Address& address) : name(address.getName()), + createPolicy(getOption(address, CREATE)), + assertPolicy(getOption(address, ASSERT)), + deletePolicy(getOption(address, DELETE)) +{ + nodeBindings.add((Opt(address)/NODE/X_BINDINGS).asList()); + linkBindings.add((Opt(address)/LINK/X_BINDINGS).asList()); +} + +Queue::Queue(const Address& a) : Node(a), + durable(Opt(a)/NODE/DURABLE), + autoDelete(Opt(a)/NODE/X_DECLARE/AUTO_DELETE), + exclusive(Opt(a)/NODE/X_DECLARE/EXCLUSIVE), + alternateExchange((Opt(a)/NODE/X_DECLARE/ALTERNATE_EXCHANGE).str()) +{ + (Opt(a)/NODE/X_DECLARE/ARGUMENTS).collect(arguments); + nodeBindings.setDefaultQueue(name); + linkBindings.setDefaultQueue(name); +} + +void Queue::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + QPID_LOG(debug, "Auto-creating queue '" << name << "'"); + try { + session.queueDeclare(arg::queue=name, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::exclusive=exclusive, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + nodeBindings.bind(session); + session.sync(); + } catch (const qpid::framing::ResourceLockedException& e) { + throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str()); + } catch (const qpid::framing::NotAllowedException& e) { + throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str()); + } catch (const qpid::framing::NotFoundException& e) {//may be thrown when creating bindings + throw ResolutionError((boost::format("Creation failed for 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 NotFound((boost::format("Queue %1% does not exist") % name).str()); + } + } +} + +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 NotFound((boost::format("Queue not found: %1%") % name).str()); + } else { + if (durable && !result.getDurable()) { + throw AssertionFailed((boost::format("Queue not durable: %1%") % name).str()); + } + if (autoDelete && !result.getAutoDelete()) { + throw AssertionFailed((boost::format("Queue not set to auto-delete: %1%") % name).str()); + } + if (exclusive && !result.getExclusive()) { + throw AssertionFailed((boost::format("Queue not exclusive: %1%") % name).str()); + } + if (!alternateExchange.empty() && result.getAlternateExchange() != alternateExchange) { + throw AssertionFailed((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 AssertionFailed((boost::format("Option %1% not set for %2%") % i->first % name).str()); + } else if (*i->second != *v) { + throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + nodeBindings.check(session); + } + } +} + +Exchange::Exchange(const Address& a) : Node(a), + specifiedType((Opt(a)/NODE/X_DECLARE/TYPE).str()), + durable(Opt(a)/NODE/DURABLE), + autoDelete(Opt(a)/NODE/X_DECLARE/AUTO_DELETE), + alternateExchange((Opt(a)/NODE/X_DECLARE/ALTERNATE_EXCHANGE).str()) +{ + (Opt(a)/NODE/X_DECLARE/ARGUMENTS).collect(arguments); + nodeBindings.setDefaultExchange(name); + linkBindings.setDefaultExchange(name); +} + +void Exchange::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + try { + std::string type = specifiedType; + if (type.empty()) type = TOPIC_EXCHANGE; + session.exchangeDeclare(arg::exchange=name, + arg::type=type, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + nodeBindings.bind(session); + session.sync(); + } catch (const qpid::framing::NotAllowedException& e) { + throw ResolutionError((boost::format("Create failed for exchange %1%; %2%") % name % e.what()).str()); + } catch (const qpid::framing::NotFoundException& e) {//can be caused when creating bindings + throw ResolutionError((boost::format("Create failed for 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 NotFound((boost::format("Exchange %1% does not exist") % name).str()); + } + } +} + +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 NotFound((boost::format("Exchange not found: %1%") % name).str()); + } else { + if (specifiedType.size() && result.getType() != specifiedType) { + throw AssertionFailed((boost::format("Exchange %1% is of incorrect type, expected %2% but got %3%") + % name % specifiedType % result.getType()).str()); + } + if (durable && !result.getDurable()) { + throw AssertionFailed((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 AssertionFailed((boost::format("Option %1% not set for %2%") % i->first % name).str()); + } else if (i->second != v) { + throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + nodeBindings.check(session); + } + } +} + +Binding::Binding(const Variant::Map& b) : + exchange((Opt(b)/EXCHANGE).str()), + queue((Opt(b)/QUEUE).str()), + key((Opt(b)/KEY).str()) +{ + (Opt(b)/ARGUMENTS).collect(arguments); +} + +Binding::Binding(const std::string& e, const std::string& q, const std::string& k) : exchange(e), queue(q), key(k) {} + + +void Bindings::add(const Variant::List& list) +{ + for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + push_back(Binding(i->asMap())); + } +} + +void Bindings::setDefaultExchange(const std::string& exchange) +{ + for (Bindings::iterator i = begin(); i != end(); ++i) { + if (i->exchange.empty()) i->exchange = exchange; + } +} + +void Bindings::setDefaultQueue(const std::string& queue) +{ + for (Bindings::iterator i = begin(); i != end(); ++i) { + if (i->queue.empty()) i->queue = queue; + } +} + +void Bindings::bind(qpid::client::AsyncSession& session) +{ + for (Bindings::const_iterator i = begin(); i != end(); ++i) { + session.exchangeBind(arg::queue=i->queue, + arg::exchange=i->exchange, + arg::bindingKey=i->key, + arg::arguments=i->arguments); + } +} + +void Bindings::unbind(qpid::client::AsyncSession& session) +{ + for (Bindings::const_iterator i = begin(); i != end(); ++i) { + session.exchangeUnbind(arg::queue=i->queue, + arg::exchange=i->exchange, + arg::bindingKey=i->key); + } +} + +void Bindings::check(qpid::client::AsyncSession& session) +{ + for (Bindings::const_iterator i = begin(); i != end(); ++i) { + ExchangeBoundResult result = sync(session).exchangeBound(arg::queue=i->queue, + arg::exchange=i->exchange, + arg::bindingKey=i->key); + if (result.getQueueNotMatched() || result.getKeyNotMatched()) { + throw AssertionFailed((boost::format("No such binding [exchange=%1%, queue=%2%, key=%3%]") + % i->exchange % i->queue % i->key).str()); + } + } +} + +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 = getOption(address, 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); + +Verifier::Verifier() +{ + defined[CREATE] = true; + defined[ASSERT] = true; + defined[DELETE] = true; + defined[MODE] = true; + Variant::Map node; + node[TYPE] = true; + node[DURABLE] = true; + node[X_DECLARE] = true; + node[X_BINDINGS] = true; + defined[NODE] = node; + Variant::Map link; + link[NAME] = true; + link[DURABLE] = true; + link[RELIABILITY] = true; + link[X_SUBSCRIBE] = true; + link[X_DECLARE] = true; + link[X_BINDINGS] = true; + defined[LINK] = link; +} +void Verifier::verify(const Address& address) const +{ + verify(defined, address.getOptions()); +} + +void Verifier::verify(const Variant::Map& allowed, const Variant::Map& actual) const +{ + for (Variant::Map::const_iterator i = actual.begin(); i != actual.end(); ++i) { + Variant::Map::const_iterator option = allowed.find(i->first); + if (option == allowed.end()) { + throw AddressError((boost::format("Unrecognised option: %1%") % i->first).str()); + } else if (option->second.getType() == qpid::types::VAR_MAP) { + verify(option->second.asMap(), i->second.asMap()); + } + } +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h new file mode 100644 index 0000000000..fc8f1a1d18 --- /dev/null +++ b/qpid/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/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&); + static bool is_unreliable(const qpid::messaging::Address& address); + static bool is_reliable(const qpid::messaging::Address& address); + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp new file mode 100644 index 0000000000..a87a8dea67 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -0,0 +1,323 @@ +/* + * + * 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 "SimpleUrlParser.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/PrivateImplRef.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/Url.h" +#include <boost/intrusive_ptr.hpp> +#include <vector> +#include <sstream> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::types::Variant; +using qpid::types::VAR_LIST; +using qpid::framing::Uuid; + +namespace { +void convert(const Variant::List& from, std::vector<std::string>& to) +{ + for (Variant::List::const_iterator i = from.begin(); i != from.end(); ++i) { + to.push_back(i->asString()); + } +} + +std::string asString(const std::vector<std::string>& v) { + std::stringstream os; + os << "["; + for(std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i ) { + if (i != v.begin()) os << ", "; + os << *i; + } + os << "]"; + return os.str(); +} +} + +ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) : + reconnect(false), timeout(-1), limit(-1), + minReconnectInterval(3), maxReconnectInterval(60), + retries(0), reconnectOnLimitExceeded(true) +{ + setOptions(options); + urls.insert(urls.begin(), url); + QPID_LOG(debug, "Created connection " << url << " with " << options); +} + +void ConnectionImpl::setOptions(const Variant::Map& options) +{ + for (Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) { + setOption(i->first, i->second); + } +} + +void ConnectionImpl::setOption(const std::string& name, const Variant& value) +{ + sys::Mutex::ScopedLock l(lock); + if (name == "reconnect") { + reconnect = value; + } else if (name == "reconnect-timeout" || name == "reconnect_timeout") { + timeout = value; + } else if (name == "reconnect-limit" || name == "reconnect_limit") { + limit = value; + } else if (name == "reconnect-interval" || name == "reconnect_interval") { + maxReconnectInterval = minReconnectInterval = value; + } else if (name == "reconnect-interval-min" || name == "reconnect_interval_min") { + minReconnectInterval = value; + } else if (name == "reconnect-interval-max" || name == "reconnect_interval_max") { + maxReconnectInterval = value; + } else if (name == "reconnect-urls" || name == "reconnect_urls") { + if (value.getType() == VAR_LIST) { + convert(value.asList(), urls); + } else { + urls.push_back(value.asString()); + } + } else if (name == "username") { + settings.username = value.asString(); + } else if (name == "password") { + settings.password = value.asString(); + } else if (name == "sasl-mechanism" || name == "sasl_mechanism" || + name == "sasl-mechanisms" || name == "sasl_mechanisms") { + settings.mechanism = value.asString(); + } else if (name == "sasl-service" || name == "sasl_service") { + settings.service = value.asString(); + } else if (name == "sasl-min-ssf" || name == "sasl_min_ssf") { + settings.minSsf = value; + } else if (name == "sasl-max-ssf" || name == "sasl_max_ssf") { + settings.maxSsf = value; + } else if (name == "heartbeat") { + settings.heartbeat = value; + } else if (name == "tcp-nodelay" || name == "tcp_nodelay") { + settings.tcpNoDelay = value; + } else if (name == "locale") { + settings.locale = value.asString(); + } else if (name == "max-channels" || name == "max_channels") { + settings.maxChannels = value; + } else if (name == "max-frame-size" || name == "max_frame_size") { + settings.maxFrameSize = value; + } else if (name == "bounds") { + settings.bounds = value; + } else if (name == "transport") { + settings.protocol = value.asString(); + } else if (name == "ssl-cert-name" || name == "ssl_cert_name") { + settings.sslCertName = value.asString(); + } else { + throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised")); + } +} + + +void ConnectionImpl::close() +{ + while(true) { + messaging::Session session; + { + qpid::sys::Mutex::ScopedLock l(lock); + if (sessions.empty()) break; + session = sessions.begin()->second; + } + session.close(); + } + detach(); +} + +void ConnectionImpl::detach() +{ + qpid::sys::Mutex::ScopedLock l(lock); + connection.close(); +} + +bool ConnectionImpl::isOpen() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return connection.isOpen(); +} + +boost::intrusive_ptr<SessionImpl> getImplPtr(qpid::messaging::Session& session) +{ + return boost::dynamic_pointer_cast<SessionImpl>( + qpid::messaging::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)); + while (true) { + try { + getImplPtr(impl)->setSession(connection.newSession(name)); + qpid::sys::Mutex::ScopedLock l(lock); + sessions[name] = impl; + break; + } catch (const qpid::TransportFailure&) { + open(); + } catch (const qpid::SessionException& e) { + throw qpid::messaging::SessionError(e.what()); + } catch (const std::exception& e) { + throw qpid::messaging::MessagingException(e.what()); + } + } + return impl; +} + +void ConnectionImpl::open() +{ + qpid::sys::AbsTime start = qpid::sys::now(); + qpid::sys::ScopedLock<qpid::sys::Semaphore> l(semaphore); + try { + if (!connection.isOpen()) connect(start); + } + catch (const types::Exception&) { throw; } + catch (const qpid::Exception& e) { throw messaging::ConnectionError(e.what()); } +} + +bool expired(const qpid::sys::AbsTime& start, int64_t timeout) +{ + if (timeout == 0) return true; + if (timeout < 0) return false; + qpid::sys::Duration used(start, qpid::sys::now()); + qpid::sys::Duration allowed = timeout * qpid::sys::TIME_SEC; + return allowed < used; +} + +void ConnectionImpl::connect(const qpid::sys::AbsTime& started) +{ + for (int64_t i = minReconnectInterval; !tryConnect(); i = std::min(i * 2, maxReconnectInterval)) { + if (!reconnect) { + throw qpid::messaging::TransportFailure("Failed to connect (reconnect disabled)"); + } + if (limit >= 0 && retries++ >= limit) { + throw qpid::messaging::TransportFailure("Failed to connect within reconnect limit"); + } + if (expired(started, timeout)) { + throw qpid::messaging::TransportFailure("Failed to connect within reconnect timeout"); + } + else qpid::sys::sleep(i); + } + retries = 0; +} + +void ConnectionImpl::mergeUrls(const std::vector<Url>& more, const sys::Mutex::ScopedLock&) { + if (more.size()) { + for (size_t i = 0; i < more.size(); ++i) { + if (std::find(urls.begin(), urls.end(), more[i].str()) == urls.end()) { + urls.push_back(more[i].str()); + } + } + QPID_LOG(debug, "Added known-hosts, reconnect-urls=" << asString(urls)); + } +} + +bool ConnectionImpl::tryConnect() +{ + sys::Mutex::ScopedLock l(lock); + for (std::vector<std::string>::const_iterator i = urls.begin(); i != urls.end(); ++i) { + try { + QPID_LOG(info, "Trying to connect to " << *i << "..."); + //TODO: when url support is more complete can avoid this test here + if (i->find("amqp:") == 0) { + Url url(*i); + connection.open(url, settings); + } else { + SimpleUrlParser::parse(*i, settings); + connection.open(settings); + } + QPID_LOG(info, "Connected to " << *i); + mergeUrls(connection.getInitialBrokers(), l); + return resetSessions(l); + } catch (const qpid::ConnectionException& e) { + //TODO: need to fix timeout on + //qpid::client::Connection::open() so that it throws + //TransportFailure rather than a ConnectionException + QPID_LOG(info, "Failed to connect to " << *i << ": " << e.what()); + } + } + return false; +} + +bool ConnectionImpl::resetSessions(const sys::Mutex::ScopedLock& ) +{ + 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 qpid::TransportFailure&) { + QPID_LOG(debug, "Connection failed while re-initialising sessions"); + return false; + } catch (const qpid::framing::ResourceLimitExceededException& e) { + if (reconnectOnLimitExceeded) { + QPID_LOG(debug, "Detaching and reconnecting due to: " << e.what()); + detach(); + return false; + } else { + throw qpid::messaging::TargetCapacityExceeded(e.what()); + } + } +} + +bool ConnectionImpl::backoff() +{ + if (reconnectOnLimitExceeded) { + detach(); + open(); + return true; + } else { + return false; + } +} +std::string ConnectionImpl::getAuthenticatedUsername() +{ + return connection.getNegotiatedSettings().username; +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h new file mode 100644 index 0000000000..09f2038312 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -0,0 +1,80 @@ +#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/types/Variant.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Semaphore.h" +#include <map> +#include <vector> + +namespace qpid { +struct Url; + +namespace client { +namespace amqp0_10 { + +class SessionImpl; + +class ConnectionImpl : public qpid::messaging::ConnectionImpl +{ + public: + ConnectionImpl(const std::string& url, const qpid::types::Variant::Map& options); + void open(); + bool isOpen() const; + 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 detach(); + void setOption(const std::string& name, const qpid::types::Variant& value); + bool backoff(); + std::string getAuthenticatedUsername(); + 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::vector<std::string> urls; + qpid::client::ConnectionSettings settings; + bool reconnect; + int64_t timeout; + int32_t limit; + int64_t minReconnectInterval; + int64_t maxReconnectInterval; + int32_t retries; + bool reconnectOnLimitExceeded; + + void setOptions(const qpid::types::Variant::Map& options); + void connect(const qpid::sys::AbsTime& started); + bool tryConnect(); + bool resetSessions(const sys::Mutex::ScopedLock&); // dummy parameter indicates call with lock held. + void mergeUrls(const std::vector<Url>& more, const sys::Mutex::ScopedLock&); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp new file mode 100644 index 0000000000..71e89bdba1 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp @@ -0,0 +1,361 @@ +/* + * + * 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/amqp_0_10/Codecs.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/Duration.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/types/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 namespace qpid::amqp_0_10; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::messaging::MessageImplAccess; +using qpid::types::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) +{ + sys::Mutex::ScopedLock l(lock); + session = s; + incoming = SessionBase_0_10Access(session).get()->getDemux().getDefault(); + acceptTracker.reset(); +} + +bool IncomingMessages::get(Handler& handler, Duration timeout) +{ + { + sys::Mutex::ScopedLock l(lock); + //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) +{ + sys::Mutex::ScopedLock l(lock); + //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() +{ + sys::Mutex::ScopedLock l(lock); + acceptTracker.accept(session); +} + +void IncomingMessages::accept(qpid::framing::SequenceNumber id) +{ + sys::Mutex::ScopedLock l(lock); + acceptTracker.accept(id, session); +} + + +void IncomingMessages::releaseAll() +{ + { + //first process any received messages... + sys::Mutex::ScopedLock l(lock); + 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 + sys::Mutex::ScopedLock l(lock); + 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... + sys::Mutex::ScopedLock l(lock); + 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; + try { + 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"); + sys::Mutex::ScopedLock l(lock); + received.push_back(content); + } + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + } + catch (const qpid::ClosedException&) {} // Just return false if queue closed. + 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"); + sys::Mutex::ScopedLock l(lock); + 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() +{ + sys::Mutex::ScopedLock l(lock); + return acceptTracker.acceptsPending(); +} +uint32_t IncomingMessages::pendingAccept(const std::string& destination) +{ + sys::Mutex::ScopedLock l(lock); + 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 + sys::Mutex::ScopedLock l(lock); + 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 + sys::Mutex::ScopedLock l(lock); + 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); +} + + +namespace { +//TODO: unify conversion to and from 0-10 message that is currently +//split between IncomingMessages and OutgoingMessage +const std::string SUBJECT("qpid.subject"); + +const std::string X_APP_ID("x-amqp-0-10.app-id"); +const std::string X_ROUTING_KEY("x-amqp-0-10.routing-key"); +const std::string X_CONTENT_ENCODING("x-amqp-0-10.content-encoding"); +} + +void populateHeaders(qpid::messaging::Message& message, + const DeliveryProperties* deliveryProperties, + const MessageProperties* messageProperties) +{ + if (deliveryProperties) { + message.setTtl(qpid::messaging::Duration(deliveryProperties->getTtl())); + message.setDurable(deliveryProperties->getDeliveryMode() == DELIVERY_MODE_PERSISTENT); + message.setPriority(deliveryProperties->getPriority()); + message.setRedelivered(deliveryProperties->getRedelivered()); + } + if (messageProperties) { + message.setContentType(messageProperties->getContentType()); + if (messageProperties->hasReplyTo()) { + message.setReplyTo(AddressResolution::convert(messageProperties->getReplyTo())); + } + message.setSubject(messageProperties->getApplicationHeaders().getAsString(SUBJECT)); + message.getProperties().clear(); + translate(messageProperties->getApplicationHeaders(), message.getProperties()); + message.setCorrelationId(messageProperties->getCorrelationId()); + message.setUserId(messageProperties->getUserId()); + if (messageProperties->hasMessageId()) { + message.setMessageId(messageProperties->getMessageId().str()); + } + //expose 0-10 specific items through special properties: + // app-id, content-encoding + if (messageProperties->hasAppId()) { + message.getProperties()[X_APP_ID] = messageProperties->getAppId(); + } + if (messageProperties->hasContentEncoding()) { + message.getProperties()[X_CONTENT_ENCODING] = messageProperties->getContentEncoding(); + } + // routing-key, others? + if (deliveryProperties && deliveryProperties->hasRoutingKey()) { + message.getProperties()[X_ROUTING_KEY] = deliveryProperties->getRoutingKey(); + } + } +} + +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()); + + message.setContent(command.getContent()); + + populateHeaders(message, command.getHeaders()); +} + + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h new file mode 100644 index 0000000000..f6a291bc68 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h @@ -0,0 +1,100 @@ +#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 { + +/** + * Queue of incoming messages. + */ +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 accept(qpid::framing::SequenceNumber id); + 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; + + sys::Mutex lock; + 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/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h new file mode 100644 index 0000000000..8d87a3c7bb --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h @@ -0,0 +1,52 @@ +#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 + * 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 "qpid/client/AsyncSession.h" + +namespace qpid { + +namespace messaging { +class Message; +} + +namespace client { +namespace amqp0_10 { + +struct OutgoingMessage; + +/** + * + */ +class MessageSink +{ + public: + 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/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h b/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h new file mode 100644 index 0000000000..74f2732f59 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h @@ -0,0 +1,47 @@ +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H +#define QPID_CLIENT_AMQP0_10_MESSAGESOURCE_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 "qpid/client/AsyncSession.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * 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/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp new file mode 100644 index 0000000000..d93416da75 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp @@ -0,0 +1,104 @@ +/* + * + * 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/amqp_0_10/Codecs.h" +#include "qpid/types/Variant.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/framing/enum.h" +#include <sstream> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Address; +using qpid::messaging::MessageImplAccess; +using qpid::types::Variant; +using namespace qpid::framing::message; +using namespace qpid::amqp_0_10; + +namespace { +//TODO: unify conversion to and from 0-10 message that is currently +//split between IncomingMessages and OutgoingMessage +const std::string SUBJECT("qpid.subject"); +const std::string X_APP_ID("x-amqp-0-10.app-id"); +const std::string X_ROUTING_KEY("x-amqp-0-10.routing-key"); +const std::string X_CONTENT_ENCODING("x-amqp-0-10.content-encoding"); +} + +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()); + message.getMessageProperties().setCorrelationId(from.getCorrelationId()); + message.getMessageProperties().setUserId(from.getUserId()); + const Address& address = from.getReplyTo(); + if (address) { + message.getMessageProperties().setReplyTo(AddressResolution::convert(address)); + } + translate(from.getProperties(), message.getMessageProperties().getApplicationHeaders()); + if (from.getTtl().getMilliseconds()) { + message.getDeliveryProperties().setTtl(from.getTtl().getMilliseconds()); + } + if (from.getDurable()) { + message.getDeliveryProperties().setDeliveryMode(DELIVERY_MODE_PERSISTENT); + } + if (from.getRedelivered()) { + message.getDeliveryProperties().setRedelivered(true); + } + if (from.getPriority()) message.getDeliveryProperties().setPriority(from.getPriority()); + + //allow certain 0-10 specific items to be set through special properties: + // message-id, app-id, content-encoding + if (from.getMessageId().size()) { + qpid::framing::Uuid uuid; + std::istringstream data(from.getMessageId()); + data >> uuid; + message.getMessageProperties().setMessageId(uuid); + } + Variant::Map::const_iterator i; + i = from.getProperties().find(X_APP_ID); + if (i != from.getProperties().end()) { + message.getMessageProperties().setAppId(i->second.asString()); + } + i = from.getProperties().find(X_CONTENT_ENCODING); + if (i != from.getProperties().end()) { + message.getMessageProperties().setContentEncoding(i->second.asString()); + } +} + +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/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h new file mode 100644 index 0000000000..0cdd2a2336 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h @@ -0,0 +1,48 @@ +#ifndef QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H +#define QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_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/Completion.h" +#include "qpid/client/Message.h" + +namespace qpid { +namespace messaging { +class Message; +} +namespace client { +namespace amqp0_10 { + +struct OutgoingMessage +{ + qpid::client::Message message; + qpid::client::Completion status; + + void convert(const qpid::messaging::Message&); + void setSubject(const std::string& subject); + std::string getSubject() const; +}; + + + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp new file mode 100644 index 0000000000..030b804143 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp @@ -0,0 +1,225 @@ +/* + * + * 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/exceptions.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::NoMessageAvailable; +using qpid::messaging::Receiver; +using qpid::messaging::Duration; + +void ReceiverImpl::received(qpid::messaging::Message&) +{ + //TODO: should this be configurable + sys::Mutex::ScopedLock l(lock); + if (capacity && --window <= capacity/2) { + session.sendCompletion(); + window = capacity; + } +} + +qpid::messaging::Message ReceiverImpl::get(qpid::messaging::Duration timeout) +{ + qpid::messaging::Message result; + if (!get(result, timeout)) throw NoMessageAvailable(); + return result; +} + +qpid::messaging::Message ReceiverImpl::fetch(qpid::messaging::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw NoMessageAvailable(); + return result; +} + +bool ReceiverImpl::get(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + Get f(*this, message, timeout); + while (!parent->execute(f)) {} + return f.result; +} + +bool ReceiverImpl::fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + Fetch f(*this, message, timeout); + while (!parent->execute(f)) {} + return f.result; +} + +void ReceiverImpl::close() +{ + execute<Close>(); +} + +void ReceiverImpl::start() +{ + sys::Mutex::ScopedLock l(lock); + if (state == STOPPED) { + state = STARTED; + startFlow(l); + } +} + +void ReceiverImpl::stop() +{ + sys::Mutex::ScopedLock l(lock); + state = STOPPED; + session.messageStop(destination); +} + +void ReceiverImpl::setCapacity(uint32_t c) +{ + execute1<SetCapacity>(c); +} + +void ReceiverImpl::startFlow(const sys::Mutex::ScopedLock&) +{ + 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) +{ + sys::Mutex::ScopedLock l(lock); + session = s; + if (state == CANCELLED) return; + if (state == UNRESOLVED) { + source = resolver.resolveSource(session, address); + assert(source.get()); + state = STARTED; + } + source->subscribe(session, destination); + startFlow(l); +} + +const std::string& ReceiverImpl::getName() const { + sys::Mutex::ScopedLock l(lock); + return destination; +} + +uint32_t ReceiverImpl::getCapacity() +{ + sys::Mutex::ScopedLock l(lock); + return capacity; +} + +uint32_t ReceiverImpl::getAvailable() +{ + return parent->getReceivable(destination); +} + +uint32_t ReceiverImpl::getUnsettled() +{ + return parent->getUnsettledAcks(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::messaging::Duration timeout) +{ + { + sys::Mutex::ScopedLock l(lock); + if (state == CANCELLED) return false; + } + return parent->get(*this, message, timeout); +} + +bool ReceiverImpl::fetchImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + { + sys::Mutex::ScopedLock l(lock); + if (state == CANCELLED) return false; + + 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 { + qpid::client::Session s; + { + sys::Mutex::ScopedLock l(lock); + if (state == CANCELLED) return false; // Might have been closed during get. + s = sync(session); + } + s.messageFlush(destination); + { + sys::Mutex::ScopedLock l(lock); + startFlow(l); //reallocate credit + } + return getImpl(message, Duration::IMMEDIATE); + } +} + +void ReceiverImpl::closeImpl() +{ + sys::Mutex::ScopedLock l(lock); + if (state != CANCELLED) { + state = CANCELLED; + sync(session).messageStop(destination); + parent->releasePending(destination); + source->cancel(session, destination); + parent->receiverCancelled(destination); + } +} + +bool ReceiverImpl::isClosed() const { + sys::Mutex::ScopedLock l(lock); + return state == CANCELLED; +} + +void ReceiverImpl::setCapacityImpl(uint32_t c) +{ + sys::Mutex::ScopedLock l(lock); + if (c != capacity) { + capacity = c; + if (state == STARTED) { + session.messageStop(destination); + startFlow(l); + } + } +} + +qpid::messaging::Session ReceiverImpl::getSession() const +{ + return qpid::messaging::Session(parent.get()); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h new file mode 100644 index 0000000000..5693b7b71f --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -0,0 +1,151 @@ +#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/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include "qpid/messaging/Duration.h" +#include "qpid/sys/Mutex.h" +#include <boost/intrusive_ptr.hpp> +#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::messaging::Duration timeout); + qpid::messaging::Message get(qpid::messaging::Duration timeout); + bool fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + qpid::messaging::Message fetch(qpid::messaging::Duration timeout); + void close(); + void start(); + void stop(); + const std::string& getName() const; + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getAvailable(); + uint32_t getUnsettled(); + void received(qpid::messaging::Message& message); + qpid::messaging::Session getSession() const; + bool isClosed() const; + + private: + mutable sys::Mutex lock; + boost::intrusive_ptr<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(const sys::Mutex::ScopedLock&); // Dummy param, call with lock held + //implementation of public facing methods + bool fetchImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + bool getImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + void closeImpl(); + void setCapacityImpl(uint32_t); + + //functors for public facing methods. + struct Command + { + ReceiverImpl& impl; + + Command(ReceiverImpl& i) : impl(i) {} + }; + + struct Get : Command + { + qpid::messaging::Message& message; + qpid::messaging::Duration timeout; + bool result; + + Get(ReceiverImpl& i, qpid::messaging::Message& m, qpid::messaging::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::messaging::Duration timeout; + bool result; + + Fetch(ReceiverImpl& i, qpid::messaging::Message& m, qpid::messaging::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.fetchImpl(message, timeout); } + }; + + struct Close : Command + { + Close(ReceiverImpl& i) : Command(i) {} + void operator()() { impl.closeImpl(); } + }; + + 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/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp new file mode 100644 index 0000000000..f2f0f1a9e5 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -0,0 +1,182 @@ +/* + * + * 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), unreliable(AddressResolution::is_unreliable(address)) {} + +void SenderImpl::send(const qpid::messaging::Message& message, bool sync) +{ + if (unreliable) { // immutable, don't need lock + UnreliableSend f(*this, &message); + parent->execute(f); + } else { + Send f(*this, &message); + while (f.repeat) parent->execute(f); + } + if (sync) parent->sync(true); +} + +void SenderImpl::close() +{ + execute<Close>(); +} + +void SenderImpl::setCapacity(uint32_t c) +{ + bool flush; + { + sys::Mutex::ScopedLock l(lock); + flush = c < capacity; + capacity = c; + } + execute1<CheckPendingSends>(flush); +} + +uint32_t SenderImpl::getCapacity() { + sys::Mutex::ScopedLock l(lock); + return capacity; +} + +uint32_t SenderImpl::getUnsettled() +{ + CheckPendingSends f(*this, false); + parent->execute(f); + return f.pending; +} + +void SenderImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + sys::Mutex::ScopedLock l(lock); + session = s; + if (state == UNRESOLVED) { + sink = resolver.resolveSink(session, address); + state = ACTIVE; + } + if (state == CANCELLED) { + sink->cancel(session, name); + sys::Mutex::ScopedUnlock u(lock); + parent->senderCancelled(name); + } else { + sink->declare(session, name); + replay(l); + } +} + +void SenderImpl::waitForCapacity() +{ + sys::Mutex::ScopedLock l(lock); + //TODO: add option to throw exception rather than blocking? + if (!unreliable && capacity <= + (flushed ? checkPendingSends(false, l) : 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, l); + } + //flush periodically and check for conmpleted sends + if (++window > (capacity / 4)) {//TODO: make this configurable? + checkPendingSends(true, l); + window = 0; + } +} + +void SenderImpl::sendImpl(const qpid::messaging::Message& m) +{ + sys::Mutex::ScopedLock l(lock); + 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::sendUnreliable(const qpid::messaging::Message& m) +{ + sys::Mutex::ScopedLock l(lock); + OutgoingMessage msg; + msg.convert(m); + msg.setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject()); + sink->send(session, name, msg); +} + +void SenderImpl::replay(const sys::Mutex::ScopedLock&) +{ + for (OutgoingMessages::iterator i = outgoing.begin(); i != outgoing.end(); ++i) { + i->message.setRedelivered(true); + sink->send(session, name, *i); + } +} + +uint32_t SenderImpl::checkPendingSends(bool flush) { + sys::Mutex::ScopedLock l(lock); + return checkPendingSends(flush, l); +} + +uint32_t SenderImpl::checkPendingSends(bool flush, const sys::Mutex::ScopedLock&) +{ + if (flush) { + session.flush(); + flushed = true; + } else { + flushed = false; + } + while (!outgoing.empty() && outgoing.front().status.isComplete()) { + outgoing.pop_front(); + } + return outgoing.size(); +} + +void SenderImpl::closeImpl() +{ + sys::Mutex::ScopedLock l(lock); + state = CANCELLED; + sink->cancel(session, name); + parent->senderCancelled(name); +} + +const std::string& SenderImpl::getName() const +{ + sys::Mutex::ScopedLock l(lock); + return name; +} + +qpid::messaging::Session SenderImpl::getSession() const +{ + sys::Mutex::ScopedLock l(lock); + return qpid::messaging::Session(parent.get()); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h new file mode 100644 index 0000000000..c10c77ae18 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -0,0 +1,160 @@ +#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/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include <memory> +#include <boost/intrusive_ptr.hpp> +#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&, bool sync); + void close(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getUnsettled(); + void init(qpid::client::AsyncSession, AddressResolution&); + const std::string& getName() const; + qpid::messaging::Session getSession() const; + + private: + mutable sys::Mutex lock; + boost::intrusive_ptr<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; + const bool unreliable; + + uint32_t checkPendingSends(bool flush); + // Dummy ScopedLock parameter means call with lock held + uint32_t checkPendingSends(bool flush, const sys::Mutex::ScopedLock&); + void replay(const sys::Mutex::ScopedLock&); + void waitForCapacity(); + + //logic for application visible methods: + void sendImpl(const qpid::messaging::Message&); + void sendUnreliable(const qpid::messaging::Message&); + void closeImpl(); + + + //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 UnreliableSend : Command + { + const qpid::messaging::Message* message; + + UnreliableSend(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m) {} + void operator()() + { + //TODO: ideally want to put messages on the outbound + //queue and pull them off in io thread, but the old + //0-10 client doesn't support that option so for now + //we simply don't queue unreliable messages + impl.sendUnreliable(*message); + } + }; + + struct Close : Command + { + Close(SenderImpl& i) : Command(i) {} + void operator()() { impl.closeImpl(); } + }; + + 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/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp new file mode 100644 index 0000000000..75a71997fd --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp @@ -0,0 +1,525 @@ +/* + * + * 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/SessionBase_0_10Access.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/messaging/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 <boost/format.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> + +using qpid::messaging::KeyError; +using qpid::messaging::NoMessageAvailable; +using qpid::messaging::MessagingException; +using qpid::messaging::TransactionAborted; +using qpid::messaging::SessionError; +using qpid::messaging::MessageImplAccess; +using qpid::messaging::Sender; +using qpid::messaging::Receiver; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +typedef qpid::sys::Mutex::ScopedLock ScopedLock; +typedef qpid::sys::Mutex::ScopedUnlock ScopedUnlock; + +SessionImpl::SessionImpl(ConnectionImpl& c, bool t) : connection(&c), transactional(t) {} + +void SessionImpl::checkError() +{ + qpid::client::SessionBase_0_10Access s(session); + s.get()->assertOpen(); +} + +bool SessionImpl::hasError() +{ + qpid::client::SessionBase_0_10Access s(session); + return s.get()->hasError(); +} + +void SessionImpl::sync(bool block) +{ + if (block) retry<Sync>(); + else execute<NonBlockingSync>(); +} + +void SessionImpl::commit() +{ + if (!execute<Commit>()) { + throw TransactionAborted("Transaction aborted due to transport failure"); + } +} + +void SessionImpl::rollback() +{ + //If the session fails during this operation, the transaction will + //be rolled back anyway. + execute<Rollback>(); +} + +void SessionImpl::acknowledge(bool sync_) +{ + //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>(); + sync(sync_); +} + +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::release(qpid::messaging::Message& m) +{ + execute1<Release>(m); +} + +void SessionImpl::acknowledge(qpid::messaging::Message& m) +{ + //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 + execute1<Acknowledge1>(m); +} + +void SessionImpl::close() +{ + if (hasError()) { + ScopedLock l(lock); + senders.clear(); + receivers.clear(); + } else { + while (true) { + Sender s; + { + ScopedLock l(lock); + if (senders.empty()) break; + s = senders.begin()->second; + } + s.close(); // outside the lock, will call senderCancelled + } + while (true) { + Receiver r; + { + ScopedLock l(lock); + if (receivers.empty()) break; + r = receivers.begin()->second; + } + r.close(); // outside the lock, will call receiverCancelled + } + } + connection->closed(*this); + if (!hasError()) session.close(); +} + +template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t) +{ + return boost::dynamic_pointer_cast<S>(qpid::messaging::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) +{ + 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); + } + session.sync(); +} + +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) +{ + ScopedLock l(lock); + 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) +{ + ScopedLock l(lock); + 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 SessionError(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) +{ + ScopedLock l(lock); + 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; + } +} + +qpid::sys::Duration adjust(qpid::messaging::Duration timeout) +{ + uint64_t ms = timeout.getMilliseconds(); + if (ms < (uint64_t) (qpid::sys::TIME_INFINITE/qpid::sys::TIME_MSEC)) { + return ms * qpid::sys::TIME_MSEC; + } else { + return qpid::sys::TIME_INFINITE; + } +} + +bool SessionImpl::getIncoming(IncomingMessages::Handler& handler, qpid::messaging::Duration timeout) +{ + return incoming.get(handler, adjust(timeout)); +} + +bool SessionImpl::get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + IncomingMessageHandler handler(boost::bind(&SessionImpl::accept, this, &receiver, &message, _1)); + return getIncoming(handler, timeout); +} + +bool SessionImpl::nextReceiver(qpid::messaging::Receiver& receiver, qpid::messaging::Duration timeout) +{ + while (true) { + try { + std::string destination; + if (incoming.getNextDestination(destination, adjust(timeout))) { + qpid::sys::Mutex::ScopedLock l(lock); + Receivers::const_iterator i = receivers.find(destination); + if (i == receivers.end()) { + throw qpid::messaging::ReceiverError(QPID_MSG("Received message for unknown destination " << destination)); + } else { + receiver = i->second; + } + return true; + } else { + return false; + } + } catch (TransportFailure&) { + reconnect(); + } catch (const qpid::framing::ResourceLimitExceededException& e) { + if (backoff()) return false; + else throw qpid::messaging::TargetCapacityExceeded(e.what()); + } catch (const qpid::framing::UnauthorizedAccessException& e) { + throw qpid::messaging::UnauthorizedAccess(e.what()); + } catch (const qpid::SessionException& e) { + throw qpid::messaging::SessionError(e.what()); + } catch (const qpid::ConnectionException& e) { + throw qpid::messaging::ConnectionError(e.what()); + } catch (const qpid::ChannelException& e) { + throw qpid::messaging::MessagingException(e.what()); + } + } +} + +qpid::messaging::Receiver SessionImpl::nextReceiver(qpid::messaging::Duration timeout) +{ + qpid::messaging::Receiver receiver; + if (!nextReceiver(receiver, timeout)) throw NoMessageAvailable(); + if (!receiver) throw SessionError("Bad receiver returned!"); + return receiver; +} + +uint32_t SessionImpl::getReceivable() +{ + return get1<Receivable, uint32_t>((const std::string*) 0); +} +uint32_t SessionImpl::getReceivable(const std::string& destination) +{ + return get1<Receivable, uint32_t>(&destination); +} + +struct SessionImpl::Receivable : Command +{ + const std::string* destination; + uint32_t result; + + Receivable(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.getReceivableImpl(destination); } +}; + +uint32_t SessionImpl::getReceivableImpl(const std::string* destination) +{ + ScopedLock l(lock); + if (destination) { + return incoming.available(*destination); + } else { + return incoming.available(); + } +} + +uint32_t SessionImpl::getUnsettledAcks() +{ + return get1<UnsettledAcks, uint32_t>((const std::string*) 0); +} + +uint32_t SessionImpl::getUnsettledAcks(const std::string& destination) +{ + return get1<UnsettledAcks, uint32_t>(&destination); +} + +struct SessionImpl::UnsettledAcks : Command +{ + const std::string* destination; + uint32_t result; + + UnsettledAcks(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.getUnsettledAcksImpl(destination); } +}; + +uint32_t SessionImpl::getUnsettledAcksImpl(const std::string* destination) +{ + ScopedLock l(lock); + if (destination) { + return incoming.pendingAccept(*destination); + } else { + return incoming.pendingAccept(); + } +} + +void SessionImpl::syncImpl(bool block) +{ + if (block) session.sync(); + else session.flush(); + //cleanup unconfirmed accept records: + incoming.pendingAccept(); +} + +void SessionImpl::commitImpl() +{ + ScopedLock l(lock); + incoming.accept(); + session.txCommit(); +} + +void SessionImpl::rollbackImpl() +{ + ScopedLock l(lock); + 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() +{ + ScopedLock l(lock); + if (!transactional) incoming.accept(); +} + +void SessionImpl::acknowledgeImpl(qpid::messaging::Message& m) +{ + ScopedLock l(lock); + if (!transactional) incoming.accept(MessageImplAccess::get(m).getInternalId()); +} + +void SessionImpl::rejectImpl(qpid::messaging::Message& m) +{ + SequenceSet set; + set.add(MessageImplAccess::get(m).getInternalId()); + session.messageReject(set); +} + +void SessionImpl::releaseImpl(qpid::messaging::Message& m) +{ + SequenceSet set; + set.add(MessageImplAccess::get(m).getInternalId()); + session.messageRelease(set); +} + +void SessionImpl::receiverCancelled(const std::string& name) +{ + ScopedLock l(lock); + receivers.erase(name); + session.sync(); + incoming.releasePending(name); +} + +void SessionImpl::releasePending(const std::string& name) +{ + ScopedLock l(lock); + incoming.releasePending(name); +} + +void SessionImpl::senderCancelled(const std::string& name) +{ + ScopedLock l(lock); + senders.erase(name); +} + +void SessionImpl::reconnect() +{ + connection->open(); +} + +bool SessionImpl::backoff() +{ + return connection->backoff(); +} + +qpid::messaging::Connection SessionImpl::getConnection() const +{ + return qpid::messaging::Connection(connection.get()); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h new file mode 100644 index 0000000000..2a2aa47df6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h @@ -0,0 +1,247 @@ +#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/Duration.h" +#include "qpid/messaging/exceptions.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" +#include "qpid/framing/reply_exceptions.h" +#include <boost/intrusive_ptr.hpp> + +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(bool sync); + void reject(qpid::messaging::Message&); + void release(qpid::messaging::Message&); + void acknowledge(qpid::messaging::Message& msg); + void close(); + void sync(bool block); + 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::messaging::Duration timeout); + qpid::messaging::Receiver nextReceiver(qpid::messaging::Duration timeout); + + qpid::messaging::Connection getConnection() const; + void checkError(); + bool hasError(); + + bool get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::messaging::Duration timeout); + + void releasePending(const std::string& destination); + void receiverCancelled(const std::string& name); + void senderCancelled(const std::string& name); + + uint32_t getReceivable(); + uint32_t getReceivable(const std::string& destination); + + uint32_t getUnsettledAcks(); + uint32_t getUnsettledAcks(const std::string& destination); + + void setSession(qpid::client::Session); + + template <class T> bool execute(T& f) + { + try { + f(); + return true; + } catch (const qpid::TransportFailure&) { + reconnect(); + return false; + } catch (const qpid::framing::ResourceLimitExceededException& e) { + if (backoff()) return false; + else throw qpid::messaging::TargetCapacityExceeded(e.what()); + } catch (const qpid::framing::UnauthorizedAccessException& e) { + throw qpid::messaging::UnauthorizedAccess(e.what()); + } catch (const qpid::SessionException& e) { + throw qpid::messaging::SessionError(e.what()); + } catch (const qpid::ConnectionException& e) { + throw qpid::messaging::ConnectionError(e.what()); + } catch (const qpid::ChannelException& e) { + throw qpid::messaging::MessagingException(e.what()); + } + } + + 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; + boost::intrusive_ptr<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::messaging::Duration timeout); + bool getNextReceiver(qpid::messaging::Receiver* receiver, IncomingMessages::MessageTransfer& transfer); + void reconnect(); + bool backoff(); + + void commitImpl(); + void rollbackImpl(); + void acknowledgeImpl(); + void acknowledgeImpl(qpid::messaging::Message&); + void rejectImpl(qpid::messaging::Message&); + void releaseImpl(qpid::messaging::Message&); + void closeImpl(); + void syncImpl(bool block); + qpid::messaging::Sender createSenderImpl(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiverImpl(const qpid::messaging::Address& address); + uint32_t getReceivableImpl(const std::string* destination); + uint32_t getUnsettledAcksImpl(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(true); } + }; + + struct NonBlockingSync : Command + { + NonBlockingSync(SessionImpl& i) : Command(i) {} + void operator()() { impl.syncImpl(false); } + }; + + struct Reject : Command + { + qpid::messaging::Message& message; + + Reject(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.rejectImpl(message); } + }; + + struct Release : Command + { + qpid::messaging::Message& message; + + Release(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.releaseImpl(message); } + }; + + struct Acknowledge1 : Command + { + qpid::messaging::Message& message; + + Acknowledge1(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.acknowledgeImpl(message); } + }; + + struct CreateSender; + struct CreateReceiver; + struct UnsettledAcks; + struct Receivable; + + //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/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp new file mode 100644 index 0000000000..327c2274a6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp @@ -0,0 +1,79 @@ +/* + * + * 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 "SimpleUrlParser.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/Exception.h" +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +bool split(const std::string& in, char delim, std::pair<std::string, std::string>& result) +{ + std::string::size_type i = in.find(delim); + if (i != std::string::npos) { + result.first = in.substr(0, i); + if (i+1 < in.size()) { + result.second = in.substr(i+1); + } + return true; + } else { + return false; + } +} + +void parseUsernameAndPassword(const std::string& in, qpid::client::ConnectionSettings& result) +{ + std::pair<std::string, std::string> parts; + if (!split(in, '/', parts)) { + result.username = in; + } else { + result.username = parts.first; + result.password = parts.second; + } +} + +void parseHostAndPort(const std::string& in, qpid::client::ConnectionSettings& result) +{ + std::pair<std::string, std::string> parts; + if (!split(in, ':', parts)) { + result.host = in; + } else { + result.host = parts.first; + if (parts.second.size()) { + result.port = boost::lexical_cast<uint16_t>(parts.second); + } + } +} + +void SimpleUrlParser::parse(const std::string& url, qpid::client::ConnectionSettings& result) +{ + std::pair<std::string, std::string> parts; + if (!split(url, '@', parts)) { + parseHostAndPort(url, result); + } else { + parseUsernameAndPassword(parts.first, result); + parseHostAndPort(parts.second, result); + } +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h new file mode 100644 index 0000000000..24f90ca9d6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H +#define QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_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> + +namespace qpid { +namespace client { + +struct ConnectionSettings; + +namespace amqp0_10 { + +/** + * Parses a simple url of the form user/password@hostname:port + */ +struct SimpleUrlParser +{ + static void parse(const std::string& url, qpid::client::ConnectionSettings& result); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H*/ diff --git a/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp new file mode 100644 index 0000000000..d1ae762f1b --- /dev/null +++ b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp @@ -0,0 +1,177 @@ +/* + * + * 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/SaslFactory.h" + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/log/Statement.h" + +#include "boost/tokenizer.hpp" + +namespace qpid { + +using qpid::sys::SecurityLayer; +using qpid::sys::SecuritySettings; +using qpid::framing::InternalErrorException; + +struct WindowsSaslSettings +{ + WindowsSaslSettings ( ) : + username ( std::string(0) ), + password ( std::string(0) ), + service ( std::string(0) ), + host ( std::string(0) ), + minSsf ( 0 ), + maxSsf ( 0 ) + { + } + + WindowsSaslSettings ( const std::string & user, const std::string & password, const std::string & service, const std::string & host, int minSsf, int maxSsf ) : + username(user), + password(password), + service(service), + host(host), + minSsf(minSsf), + maxSsf(maxSsf) + { + } + + std::string username, + password, + service, + host; + + int minSsf, + maxSsf; +}; + +class WindowsSasl : public Sasl +{ + public: + WindowsSasl( const std::string &, const std::string &, const std::string &, const std::string &, int, int ); + ~WindowsSasl(); + std::string start(const std::string& mechanisms, const SecuritySettings* externalSettings); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + WindowsSaslSettings 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 std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool ) +{ + std::auto_ptr<Sasl> sasl(new WindowsSasl( username, password, serviceName, hostName, minSsf, maxSsf )); + return sasl; +} + +namespace { + const std::string ANONYMOUS = "ANONYMOUS"; + const std::string PLAIN = "PLAIN"; +} + +WindowsSasl::WindowsSasl( const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf ) + : settings(username, password, serviceName, hostName, minSsf, maxSsf) +{ +} + +WindowsSasl::~WindowsSasl() +{ +} + +std::string WindowsSasl::start(const std::string& mechanisms, + const SecuritySettings* /*externalSettings*/) +{ + 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 diff --git a/qpid/cpp/src/qpid/client/windows/SslConnector.cpp b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp new file mode 100644 index 0000000000..785c817928 --- /dev/null +++ b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp @@ -0,0 +1,181 @@ +/* + * + * 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 "config.h" +#include "qpid/Msg.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/windows/check.h" +#include "qpid/sys/windows/SslAsynchIO.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#include <memory.h> +// security.h needs to see this to distinguish from kernel use. +#define SECURITY_WIN32 +#include <security.h> +#include <Schnlsp.h> +#undef SECURITY_WIN32 +#include <winsock2.h> + +namespace qpid { +namespace client { +namespace windows { + +using namespace qpid::sys; +using boost::format; +using boost::str; + + +class SslConnector : public qpid::client::TCPConnector +{ + qpid::sys::windows::ClientSslAsynchIO *shim; + boost::shared_ptr<qpid::sys::Poller> poller; + std::string brokerHost; + SCHANNEL_CRED cred; + CredHandle credHandle; + TimeStamp credExpiry; + + virtual ~SslConnector(); + void negotiationDone(SECURITY_STATUS status); + + // A number of AsynchIO callbacks go right through to TCPConnector, but + // we can't boost::bind to a protected ancestor, so these methods redirect + // to those TCPConnector methods. + bool redirectReadbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void redirectWritebuff(qpid::sys::AsynchIO&); + void redirectEof(qpid::sys::AsynchIO&); + +public: + SslConnector(boost::shared_ptr<qpid::sys::Poller>, + framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + virtual void connect(const std::string& host, const std::string& port); + virtual void connected(const Socket&); + unsigned int getSSF(); +}; + +// Static constructor which registers connector here +namespace { + Connector* create(boost::shared_ptr<qpid::sys::Poller> p, + framing::ProtocolVersion v, + const ConnectionSettings& s, + ConnectionImpl* c) { + return new SslConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + try { + Connector::registerFactory("ssl", &create); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL connector: " << e.what()); + } + }; + ~StaticInit() { } + } init; +} + +void SslConnector::negotiationDone(SECURITY_STATUS status) +{ + if (status == SEC_E_OK) + initAmqp(); + else + connectFailed(QPID_MSG(qpid::sys::strError(status))); +} + +bool SslConnector::redirectReadbuff(qpid::sys::AsynchIO& a, + qpid::sys::AsynchIOBufferBase* b) { + return readbuff(a, b); +} + +void SslConnector::redirectWritebuff(qpid::sys::AsynchIO& a) { + writebuff(a); +} + +void SslConnector::redirectEof(qpid::sys::AsynchIO& a) { + eof(a); +} + +SslConnector::SslConnector(boost::shared_ptr<qpid::sys::Poller> p, + framing::ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : TCPConnector(p, ver, settings, cimpl), shim(0), poller(p) +{ + memset(&cred, 0, sizeof(cred)); + cred.dwVersion = SCHANNEL_CRED_VERSION; + SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, + UNISP_NAME, + SECPKG_CRED_OUTBOUND, + NULL, + &cred, + NULL, + NULL, + &credHandle, + &credExpiry); + if (status != SEC_E_OK) + throw QPID_WINDOWS_ERROR(status); + QPID_LOG(debug, "SslConnector created for " << ver.toString()); +} + +SslConnector::~SslConnector() +{ + ::FreeCredentialsHandle(&credHandle); +} + + // Will this get reach via virtual method via boost::bind???? + +void SslConnector::connect(const std::string& host, const std::string& port) { + brokerHost = host; + TCPConnector::connect(host, port); +} + +void SslConnector::connected(const Socket& s) { + shim = new qpid::sys::windows::ClientSslAsynchIO(brokerHost, + s, + credHandle, + boost::bind(&SslConnector::redirectReadbuff, this, _1, _2), + boost::bind(&SslConnector::redirectEof, this, _1), + boost::bind(&SslConnector::redirectEof, this, _1), + 0, // closed + 0, // nobuffs + boost::bind(&SslConnector::redirectWritebuff, this, _1), + boost::bind(&SslConnector::negotiationDone, this, _1)); + start(shim); + shim->start(poller); +} + +unsigned int SslConnector::getSSF() +{ + return shim->getSslKeySize(); +} + +}}} // namespace qpid::client::windows |