diff options
author | Robert Godfrey <rgodfrey@apache.org> | 2009-10-05 12:26:55 +0000 |
---|---|---|
committer | Robert Godfrey <rgodfrey@apache.org> | 2009-10-05 12:26:55 +0000 |
commit | 69e05a6b0979d2e12588635a4ed46a09a87cdec0 (patch) | |
tree | 883b432624cb9508d3d275c85aa0eb00388b166a | |
parent | 2e20e00f3816aa63f3633a5a16f1891261750c74 (diff) | |
download | qpid-python-69e05a6b0979d2e12588635a4ed46a09a87cdec0.tar.gz |
Merged from trunk up to r807984
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/java-broker-0-10@821770 13f79535-47bb-0310-9956-ffa450edef68
176 files changed, 11172 insertions, 1251 deletions
diff --git a/qpid/buildtools/buildCreator/buildCreator.py b/qpid/buildtools/buildCreator/buildCreator.py index 0eae0b5422..6b9fc3c68e 100755 --- a/qpid/buildtools/buildCreator/buildCreator.py +++ b/qpid/buildtools/buildCreator/buildCreator.py @@ -1112,7 +1112,7 @@ def downloadSource(source, destination): command = SVN_BIN+" co "+url+" "+targetdir if (source.getElementsByTagName(REVISION).length > 0): revision = getValue(source.getElementsByTagName(REVISION)[0]) - command = SVN_BIN+" co "+url+"@"+revision+" "+targetdir + command = SVN_BIN+" co -r"+revision+" "+url+" "+targetdir else: if (type == HTTP): command = WGET_BIN+" --no-directories -P "+targetdir+" "+url diff --git a/qpid/cpp/bindings/qmf/qmfengine.i b/qpid/cpp/bindings/qmf/qmfengine.i index 3c67d92031..8ae28730e5 100644 --- a/qpid/cpp/bindings/qmf/qmfengine.i +++ b/qpid/cpp/bindings/qmf/qmfengine.i @@ -19,24 +19,22 @@ %{ -#include "Agent.h" -#include <ResilientConnection.h> +#include "qmf/AgentEngine.h" +#include <qmf/ResilientConnection.h> %} - -%include <Query.h> -%include <Message.h> -%include <Agent.h> -%include <ResilientConnection.h> -%include <Typecode.h> -%include <Schema.h> -%include <Value.h> -%include <ObjectId.h> -%include <Object.h> - -%include <qpid/client/ClientImportExport.h> -%include <qpid/client/ConnectionSettings.h> +%include <qmf/QmfImportExport.h> +%include <qmf/Query.h> +%include <qmf/Message.h> +%include <qmf/AgentEngine.h> +%include <qmf/ConnectionSettings.h> +%include <qmf/ResilientConnection.h> +%include <qmf/Typecode.h> +%include <qmf/Schema.h> +%include <qmf/Value.h> +%include <qmf/ObjectId.h> +%include <qmf/Object.h> %inline { diff --git a/qpid/cpp/bindings/qmf/ruby/Makefile.am b/qpid/cpp/bindings/qmf/ruby/Makefile.am index 532fdb6875..0537dd1cd8 100644 --- a/qpid/cpp/bindings/qmf/ruby/Makefile.am +++ b/qpid/cpp/bindings/qmf/ruby/Makefile.am @@ -19,7 +19,7 @@ if HAVE_RUBY_DEVEL -INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src/qmf -I$(top_srcdir)/src -I$(top_builddir)/src +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src -I$(top_builddir)/src EXTRA_DIST = ruby.i BUILT_SOURCES = qmfengine.cpp @@ -36,7 +36,7 @@ rubylibarchdir = $(RUBY_LIB_ARCH) rubylibarch_LTLIBRARIES = qmfengine.la qmfengine_la_LDFLAGS = -avoid-version -module -shrext ".$(RUBY_DLEXT)" -qmfengine_la_LIBADD = $(RUBY_LIBS) -L$(top_builddir)/src/.libs -lqpidclient $(top_builddir)/src/libqmfcommon.la +qmfengine_la_LIBADD = $(RUBY_LIBS) -L$(top_builddir)/src/.libs -lqpidclient $(top_builddir)/src/libqmfagent.la qmfengine_la_CXXFLAGS = $(INCLUDES) -I$(RUBY_INC) -I$(RUBY_INC_ARCH) nodist_qmfengine_la_SOURCES = qmfengine.cpp diff --git a/qpid/cpp/bindings/qmf/ruby/qmf.rb b/qpid/cpp/bindings/qmf/ruby/qmf.rb index 7ee447c675..79b18423db 100644 --- a/qpid/cpp/bindings/qmf/ruby/qmf.rb +++ b/qpid/cpp/bindings/qmf/ruby/qmf.rb @@ -30,14 +30,40 @@ module Qmf end end - class ConnectionSettings < Qmfengine::ConnectionSettings + class ConnectionSettings + attr_reader :impl + + def initialize(url = nil) + if url + @impl = Qmfengine::ConnectionSettings.new(url) + else + @impl = Qmfengine::ConnectionSettings.new() + end + end + + def set_attr(key, val) + if val.class == String + v = Qmfengine::Value.new(TYPE_LSTR) + v.setString(val) + elsif val.class == TrueClass or val.class == FalseClass + v = Qmfengine::Value.new(TYPE_BOOL) + v.setBool(val) + elsif val.class == Fixnum + v = Qmfengine::Value.new(TYPE_UINT32) + v.setUint(val) + else + raise ArgumentError, "Value for attribute '#{key}' has unsupported type: #{val.class}" + end + + @impl.setAttr(key, v) + end end - class ConnectionEvent + class ConnectionHandler def conn_event_connected(); end def conn_event_disconnected(error); end - def conn_event_session_closed(context, error); end - def conn_event_recv(context, message); end + def sess_event_session_closed(context, error); end + def sess_event_recv(context, message); end end class Query @@ -63,16 +89,11 @@ module Qmf end end - class AgentHandler - def get_query(context, query, userId); end - def method_call(context, name, object_id, args, userId); end - end - class Connection attr_reader :impl - def initialize(settings, event_handler = nil, delay_min = 1, delay_max = 128, delay_factor = 2) - @impl = Qmfengine::ResilientConnection.new(settings, delay_min, delay_max, delay_factor) + def initialize(settings) + @impl = Qmfengine::ResilientConnection.new(settings.impl) @sockEngine, @sock = Socket::socketpair(Socket::PF_UNIX, Socket::SOCK_STREAM, 0) @impl.setNotifyFd(@sockEngine.fileno) @new_conn_handlers = Array.new @@ -153,12 +174,19 @@ module Qmf @impl = Qmfengine::ObjectId.new end end + def object_num_high return @impl.getObjectNumHi end + def object_num_low return @impl.getObjectNumLo end + + def ==(other) + return (@impl.getObjectNumHi == other.impl.getObjectNumHi) && + (@impl.getObjectNumLo == other.impl.getObjectNumLo) + end end class Arguments @@ -232,7 +260,12 @@ module Qmf end end - class Agent + class AgentHandler + def get_query(context, query, userId); end + def method_call(context, name, object_id, args, userId); end + end + + class Agent < ConnectionHandler def initialize(handler, label="") if label == "" @agentLabel = "rb-%s.%d" % [Socket.gethostname, Process::pid] @@ -241,7 +274,7 @@ module Qmf end @conn = nil @handler = handler - @impl = Qmfengine::Agent.new(@agentLabel) + @impl = Qmfengine::AgentEngine.new(@agentLabel) @event = Qmfengine::AgentEvent.new @xmtMessage = Qmfengine::Message.new end diff --git a/qpid/cpp/bindings/qmf/ruby/ruby.i b/qpid/cpp/bindings/qmf/ruby/ruby.i index a8a2a87a97..b7fed403bd 100644 --- a/qpid/cpp/bindings/qmf/ruby/ruby.i +++ b/qpid/cpp/bindings/qmf/ruby/ruby.i @@ -38,17 +38,33 @@ %typemap (out) uint16_t { - $result = UINT2NUM((unsigned short) $1); + $result = UINT2NUM((uint16_t) $1); } %typemap (in) uint32_t { - $1 = NUM2UINT ($input); + if (TYPE($input) == T_BIGNUM) + $1 = NUM2UINT($input); + else + $1 = FIX2UINT($input); } %typemap (out) uint32_t { - $result = UINT2NUM((unsigned int) $1); + $result = UINT2NUM((uint32_t) $1); +} + +%typemap (in) int32_t +{ + if (TYPE($input) == T_BIGNUM) + $1 = NUM2INT($input); + else + $1 = FIX2INT($input); +} + +%typemap (out) int32_t +{ + $result = INT2NUM((int32_t) $1); } %typemap (typecheck, precedence=SWIG_TYPECHECK_INTEGER) uint32_t { @@ -57,12 +73,28 @@ %typemap (in) uint64_t { - $1 = NUM2ULONG ($input); + if (TYPE($input) == T_BIGNUM) + $1 = NUM2ULL($input); + else + $1 = (uint64_t) FIX2LONG($input); } %typemap (out) uint64_t { - $result = ULONG2NUM((unsigned long) $1); + $result = ULL2NUM((uint64_t) $1); +} + +%typemap (in) int64_t +{ + if (TYPE($input) == T_BIGNUM) + $1 = NUM2LL($input); + else + $1 = (int64_t) FIX2LONG($input); +} + +%typemap (out) int64_t +{ + $result = LL2NUM((int64_t) $1); } %typemap (typecheck, precedence=SWIG_TYPECHECK_INTEGER) uint64_t { diff --git a/qpid/cpp/bindings/qmf/tests/agent_ruby.rb b/qpid/cpp/bindings/qmf/tests/agent_ruby.rb index d395810cb6..0f85c90f37 100755 --- a/qpid/cpp/bindings/qmf/tests/agent_ruby.rb +++ b/qpid/cpp/bindings/qmf/tests/agent_ruby.rb @@ -29,9 +29,27 @@ class Model @parent_class = Qmf::SchemaObjectClass.new("org.apache.qpid.qmf", "parent") @parent_class.add_property(Qmf::SchemaProperty.new("name", Qmf::TYPE_SSTR, :index => true)) @parent_class.add_property(Qmf::SchemaProperty.new("state", Qmf::TYPE_SSTR)) + + @parent_class.add_property(Qmf::SchemaProperty.new("uint64val", Qmf::TYPE_UINT64)) @parent_class.add_property(Qmf::SchemaProperty.new("uint32val", Qmf::TYPE_UINT32)) + @parent_class.add_property(Qmf::SchemaProperty.new("uint16val", Qmf::TYPE_UINT16)) + @parent_class.add_property(Qmf::SchemaProperty.new("uint8val", Qmf::TYPE_UINT8)) + + @parent_class.add_property(Qmf::SchemaProperty.new("int64val", Qmf::TYPE_INT64)) + @parent_class.add_property(Qmf::SchemaProperty.new("int32val", Qmf::TYPE_INT32)) + @parent_class.add_property(Qmf::SchemaProperty.new("int16val", Qmf::TYPE_INT16)) + @parent_class.add_property(Qmf::SchemaProperty.new("int8val", Qmf::TYPE_INT8)) + @parent_class.add_statistic(Qmf::SchemaStatistic.new("queryCount", Qmf::TYPE_UINT32, :unit => "query", :desc => "Query count")) + method = Qmf::SchemaMethod.new("echo", :desc => "Check responsiveness of the agent object") + method.add_argument(Qmf::SchemaArgument.new("sequence", Qmf::TYPE_UINT32, :dir => Qmf::DIR_IN_OUT)) + @parent_class.add_method(method) + + method = Qmf::SchemaMethod.new("set_numerics", :desc => "Set the numeric values in the object") + method.add_argument(Qmf::SchemaArgument.new("test", Qmf::TYPE_SSTR, :dir => Qmf::DIR_IN)) + @parent_class.add_method(method) + method = Qmf::SchemaMethod.new("create_child", :desc => "Create a new child object") method.add_argument(Qmf::SchemaArgument.new("child_name", Qmf::TYPE_LSTR, :dir => Qmf::DIR_IN)) method.add_argument(Qmf::SchemaArgument.new("child_ref", Qmf::TYPE_REF, :dir => Qmf::DIR_OUT)) @@ -55,24 +73,76 @@ class App < Qmf::AgentHandler #@parent.inc_attr("queryCount") if query.class_name == 'parent' @agent.query_response(context, @parent) + elsif query.object_id == @parent_oid + @agent.query_response(context, @parent) end @agent.query_complete(context) end def method_call(context, name, object_id, args, userId) # puts "Method: user=#{userId} context=#{context} method=#{name} object_num=#{object_id.object_num_low if object_id} args=#{args}" - oid = @agent.alloc_object_id(2) - args['child_ref'] = oid - @child = Qmf::QmfObject.new(@model.child_class) - @child.set_attr("name", args.by_key("child_name")) - @child.set_object_id(oid) - @agent.method_response(context, 0, "OK", args) + + if name == "echo" + @agent.method_response(context, 0, "OK", args) + + elsif name == "set_numerics" + retCode = 0 + retText = "OK" + + if args['test'] == "big" + @parent.set_attr("uint64val", 0x9494949449494949) + @parent.set_attr("uint32val", 0xa5a55a5a) + @parent.set_attr("uint16val", 0xb66b) + @parent.set_attr("uint8val", 0xc7) + + @parent.set_attr("int64val", 1000000000000000000) + @parent.set_attr("int32val", 1000000000) + @parent.set_attr("int16val", 10000) + @parent.set_attr("int8val", 100) + + elsif args['test'] == "small" + @parent.set_attr("uint64val", 4) + @parent.set_attr("uint32val", 5) + @parent.set_attr("uint16val", 6) + @parent.set_attr("uint8val", 7) + + @parent.set_attr("int64val", 8) + @parent.set_attr("int32val", 9) + @parent.set_attr("int16val", 10) + @parent.set_attr("int8val", 11) + + elsif args['test'] == "negative" + @parent.set_attr("uint64val", 0) + @parent.set_attr("uint32val", 0) + @parent.set_attr("uint16val", 0) + @parent.set_attr("uint8val", 0) + + @parent.set_attr("int64val", -10000000000) + @parent.set_attr("int32val", -100000) + @parent.set_attr("int16val", -1000) + @parent.set_attr("int8val", -100) + + else + retCode = 1 + retText = "Invalid argument value for test" + end + + @agent.method_response(context, retCode, retText, args) + + elsif name == "create_child" + oid = @agent.alloc_object_id(2) + args['child_ref'] = oid + @child = Qmf::QmfObject.new(@model.child_class) + @child.set_attr("name", args.by_key("child_name")) + @child.set_object_id(oid) + @agent.method_response(context, 0, "OK", args) + end end def main @settings = Qmf::ConnectionSettings.new - @settings.host = ARGV[0] if ARGV.size > 0 - @settings.port = ARGV[1].to_i if ARGV.size > 1 + @settings.set_attr("host", ARGV[0]) if ARGV.size > 0 + @settings.set_attr("port", ARGV[1].to_i) if ARGV.size > 1 @connection = Qmf::Connection.new(@settings) @agent = Qmf::Agent.new(self) @@ -84,10 +154,19 @@ class App < Qmf::AgentHandler @parent = Qmf::QmfObject.new(@model.parent_class) @parent.set_attr("name", "Parent One") @parent.set_attr("state", "OPERATIONAL") - @parent.set_attr("uint32val", 0xa5a5a5a5) - oid = @agent.alloc_object_id(1) - @parent.set_object_id(oid) + @parent.set_attr("uint64val", 0) + @parent.set_attr("uint32val", 0) + @parent.set_attr("uint16val", 0) + @parent.set_attr("uint8val", 0) + + @parent.set_attr("int64val", 0) + @parent.set_attr("int32val", 0) + @parent.set_attr("int16val", 0) + @parent.set_attr("int8val", 0) + + @parent_oid = @agent.alloc_object_id(1) + @parent.set_object_id(@parent_oid) sleep end diff --git a/qpid/cpp/bindings/qmf/tests/python_console.py b/qpid/cpp/bindings/qmf/tests/python_console.py index 7a8a2cb9fe..365f2ac33a 100755 --- a/qpid/cpp/bindings/qmf/tests/python_console.py +++ b/qpid/cpp/bindings/qmf/tests/python_console.py @@ -36,16 +36,97 @@ class QmfInteropTests(TestBase010): agents = qmf.getObjects(_class="agent") sleep(1) count += 1 - if count > 5: + if count > 10: self.fail("Timed out waiting for remote agent") - def test_B_basic_types(self): + def test_B_basic_method_invocation(self): self.startQmf(); qmf = self.qmf parents = qmf.getObjects(_class="parent") self.assertEqual(len(parents), 1) - self.assertEqual(parents[0].uint32val, 0xA5A5A5A5) + parent = parents[0] + for seq in range(10): + result = parent.echo(seq) + self.assertEqual(result.status, 0) + self.assertEqual(result.text, "OK") + self.assertEqual(result.sequence, seq) + + result = parent.set_numerics("bogus") + self.assertEqual(result.status, 1) + self.assertEqual(result.text, "Invalid argument value for test") + + def test_C_basic_types_numeric_big(self): + self.startQmf(); + qmf = self.qmf + + parents = qmf.getObjects(_class="parent") + self.assertEqual(len(parents), 1) + parent = parents[0] + + result = parent.set_numerics("big") + self.assertEqual(result.status, 0) + self.assertEqual(result.text, "OK") + + parent.update() + + self.assertEqual(parent.uint64val, 0x9494949449494949) + self.assertEqual(parent.uint32val, 0xA5A55A5A) + self.assertEqual(parent.uint16val, 0xB66B) + self.assertEqual(parent.uint8val, 0xC7) + + self.assertEqual(parent.int64val, 1000000000000000000) + self.assertEqual(parent.int32val, 1000000000) + self.assertEqual(parent.int16val, 10000) + self.assertEqual(parent.int8val, 100) + + def test_C_basic_types_numeric_small(self): + self.startQmf(); + qmf = self.qmf + + parents = qmf.getObjects(_class="parent") + self.assertEqual(len(parents), 1) + parent = parents[0] + + result = parent.set_numerics("small") + self.assertEqual(result.status, 0) + self.assertEqual(result.text, "OK") + + parent.update() + + self.assertEqual(parent.uint64val, 4) + self.assertEqual(parent.uint32val, 5) + self.assertEqual(parent.uint16val, 6) + self.assertEqual(parent.uint8val, 7) + + self.assertEqual(parent.int64val, 8) + self.assertEqual(parent.int32val, 9) + self.assertEqual(parent.int16val, 10) + self.assertEqual(parent.int8val, 11) + + def test_C_basic_types_numeric_negative(self): + self.startQmf(); + qmf = self.qmf + + parents = qmf.getObjects(_class="parent") + self.assertEqual(len(parents), 1) + parent = parents[0] + + result = parent.set_numerics("negative") + self.assertEqual(result.status, 0) + self.assertEqual(result.text, "OK") + + parent.update() + + self.assertEqual(parent.uint64val, 0) + self.assertEqual(parent.uint32val, 0) + self.assertEqual(parent.uint16val, 0) + self.assertEqual(parent.uint8val, 0) + + self.assertEqual(parent.int64val, -10000000000) + self.assertEqual(parent.int32val, -100000) + self.assertEqual(parent.int16val, -1000) + self.assertEqual(parent.int8val, -100) def getProperty(self, msg, name): for h in msg.headers: diff --git a/qpid/cpp/configure.ac b/qpid/cpp/configure.ac index adf3da06b8..a4e45ad19b 100644 --- a/qpid/cpp/configure.ac +++ b/qpid/cpp/configure.ac @@ -512,6 +512,7 @@ AC_CONFIG_FILES([ examples/xml-exchange/Makefile examples/qmf-console/Makefile examples/tradedemo/Makefile + examples/messaging/Makefile bindings/qmf/Makefile bindings/qmf/ruby/Makefile bindings/qmf/tests/Makefile diff --git a/qpid/cpp/docs/api/doxygen_mainpage.h b/qpid/cpp/docs/api/doxygen_mainpage.h index cb59cfa260..83efaba31d 100644 --- a/qpid/cpp/docs/api/doxygen_mainpage.h +++ b/qpid/cpp/docs/api/doxygen_mainpage.h @@ -26,6 +26,7 @@ * <h2>Messaging Client API classes</h2> * <ul> * <li><p>\ref clientapi</p></li> + * <li><p>\ref qmfapi</p></li> * </ul> * * <h2>Code for common tasks</h2> @@ -127,5 +128,6 @@ /** * \defgroup clientapi Qpid C++ Client API + * \defgroup qmfapi Qpid Management Framework C++ API * */ diff --git a/qpid/cpp/docs/api/user.doxygen.in b/qpid/cpp/docs/api/user.doxygen.in index 6ade9ab846..f6f8c161b7 100644 --- a/qpid/cpp/docs/api/user.doxygen.in +++ b/qpid/cpp/docs/api/user.doxygen.in @@ -456,7 +456,7 @@ WARN_LOGFILE = doxygen.log # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = @top_srcdir@/include @top_builddir@/include +INPUT = @top_srcdir@/docs/api @top_srcdir@/include @top_builddir@/include # If the value of the INPUT tag contains directories, you can use the @@ -471,7 +471,7 @@ FILE_PATTERNS = *.h # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a diff --git a/qpid/cpp/examples/CMakeLists.txt b/qpid/cpp/examples/CMakeLists.txt index 7d0a4d0d21..126adab32e 100644 --- a/qpid/cpp/examples/CMakeLists.txt +++ b/qpid/cpp/examples/CMakeLists.txt @@ -62,3 +62,4 @@ add_subdirectory(qmf-console) add_subdirectory(request-response) add_subdirectory(tradedemo) add_subdirectory(xml-exchange) +add_subdirectory(messaging) diff --git a/qpid/cpp/examples/Makefile.am b/qpid/cpp/examples/Makefile.am index 9c355b84e4..b526424e2a 100644 --- a/qpid/cpp/examples/Makefile.am +++ b/qpid/cpp/examples/Makefile.am @@ -16,7 +16,7 @@ # specific language governing permissions and limitations # under the License. # -SUBDIRS = direct fanout pub-sub request-response failover qmf-console tradedemo +SUBDIRS = direct fanout pub-sub request-response failover qmf-console tradedemo messaging if HAVE_XML SUBDIRS += xml-exchange broker_args = "--no-module-dir --data-dir \"\" --auth no --load-module $(top_builddir)/src/.libs/xml.so" diff --git a/qpid/cpp/examples/messaging/CMakeLists.txt b/qpid/cpp/examples/messaging/CMakeLists.txt new file mode 100644 index 0000000000..e7885d0b50 --- /dev/null +++ b/qpid/cpp/examples/messaging/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# 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. +# + +add_example(messaging queue_listener) +add_example(messaging queue_receiver) +add_example(messaging queue_sender) + +add_example(messaging topic_listener) +add_example(messaging topic_receiver) +add_example(messaging topic_sender) + +add_example(messaging map_receiver) +add_example(messaging map_sender) + +add_example(messaging client) +add_example(messaging server) diff --git a/qpid/cpp/examples/messaging/Makefile.am b/qpid/cpp/examples/messaging/Makefile.am new file mode 100644 index 0000000000..250e549e82 --- /dev/null +++ b/qpid/cpp/examples/messaging/Makefile.am @@ -0,0 +1,54 @@ +# +# 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. +# +examplesdir=$(pkgdatadir)/examples/messaging + +MAKELDFLAGS=$(CLIENTFLAGS) +include $(top_srcdir)/examples/makedist.mk + +noinst_PROGRAMS=queue_sender queue_listener queue_receiver topic_sender topic_listener topic_receiver client server map_sender map_receiver + +queue_sender_SOURCES=queue_sender.cpp +queue_sender_LDADD=$(CLIENT_LIB) + +queue_listener_SOURCES=queue_listener.cpp +queue_listener_LDADD=$(CLIENT_LIB) + +queue_receiver_SOURCES=queue_receiver.cpp +queue_receiver_LDADD=$(CLIENT_LIB) + +topic_sender_SOURCES=topic_sender.cpp +topic_sender_LDADD=$(CLIENT_LIB) + +topic_listener_SOURCES=topic_listener.cpp +topic_listener_LDADD=$(CLIENT_LIB) + +topic_receiver_SOURCES=topic_receiver.cpp +topic_receiver_LDADD=$(CLIENT_LIB) + +client_SOURCES=client.cpp +client_LDADD=$(CLIENT_LIB) + +server_SOURCES=server.cpp +server_LDADD=$(CLIENT_LIB) + +map_sender_SOURCES=map_sender.cpp +map_sender_LDADD=$(CLIENT_LIB) + +map_receiver_SOURCES=map_receiver.cpp +map_receiver_LDADD=$(CLIENT_LIB) diff --git a/qpid/cpp/examples/messaging/client.cpp b/qpid/cpp/examples/messaging/client.cpp new file mode 100644 index 0000000000..45c065880b --- /dev/null +++ b/qpid/cpp/examples/messaging/client.cpp @@ -0,0 +1,76 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + + Sender sender = session.createSender("service_queue"); + + //create temp queue & receiver... + Address responseQueue = session.createTempQueue(); + Receiver receiver = session.createReceiver(responseQueue); + + // Now send some messages ... + string s[] = { + "Twas brillig, and the slithy toves", + "Did gire and gymble in the wabe.", + "All mimsy were the borogroves,", + "And the mome raths outgrabe." + }; + + Message request; + request.setReplyTo(responseQueue); + for (int i=0; i<4; i++) { + request.setContent(s[i]); + sender.send(request); + Message response = receiver.fetch(); + std::cout << request.getContent().asString() << " -> " << response.getContent().asString() << std::endl; + } + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/map_receiver.cpp b/qpid/cpp/examples/messaging/map_receiver.cpp new file mode 100644 index 0000000000..e6557b1560 --- /dev/null +++ b/qpid/cpp/examples/messaging/map_receiver.cpp @@ -0,0 +1,54 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + Receiver receiver = session.createReceiver("message_queue"); + Message message = receiver.fetch(); + std::cout << message.getContent().asMap() << std::endl; + session.acknowledge(); + receiver.cancel(); + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/examples/messaging/map_sender.cpp b/qpid/cpp/examples/messaging/map_sender.cpp new file mode 100644 index 0000000000..9301c1fe1f --- /dev/null +++ b/qpid/cpp/examples/messaging/map_sender.cpp @@ -0,0 +1,66 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + Sender sender = session.createSender("message_queue"); + + Message message; + message.getContent()["id"] = 987654321; + message.getContent()["name"] = "Widget"; + message.getContent()["price"] = 0.99;//bad use of floating point number, just an example! + Variant::List colours; + colours.push_back(Variant("red")); + colours.push_back(Variant("green")); + colours.push_back(Variant("white")); + message.getContent()["colours"] = colours; + + sender.send(message); + session.sync(); + + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/queue_listener.cpp b/qpid/cpp/examples/messaging/queue_listener.cpp new file mode 100644 index 0000000000..099e8e145a --- /dev/null +++ b/qpid/cpp/examples/messaging/queue_listener.cpp @@ -0,0 +1,82 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/MessageListener.h> +#include <qpid/messaging/Receiver.h> + +#include <cstdlib> +#include <iostream> + +using namespace qpid::messaging; + +class Listener : public MessageListener +{ + public: + Listener(const Receiver& receiver); + void received(Message& message); + bool isFinished(); + private: + Receiver receiver; + bool finished; +}; + +Listener::Listener(const Receiver& r) : receiver(r), finished(false) {} + +bool Listener::isFinished() { return finished; } + +void Listener::received(Message& message) +{ + std::cout << "Message: " << message.getContent().asString() << std::endl; + if (message.getContent().asString() == "That's all, folks!") { + std::cout << "Shutting down listener" << std::endl; + receiver.cancel(); + finished = true; + } +} + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + + Receiver receiver = session.createReceiver("message_queue"); + Listener listener(receiver); + receiver.setListener(&listener); + receiver.setCapacity(1); + receiver.start(); + while (session.dispatch()) { + session.acknowledge(); + if (listener.isFinished()) break; + } + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/queue_receiver.cpp b/qpid/cpp/examples/messaging/queue_receiver.cpp new file mode 100644 index 0000000000..83a44b2ca9 --- /dev/null +++ b/qpid/cpp/examples/messaging/queue_receiver.cpp @@ -0,0 +1,65 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + + try { + Variant::Map options; + if (argc>2) parseOptionString(argv[2], options); + Connection connection = Connection::open(url, options); + Session session = connection.newSession(); + Receiver receiver = session.createReceiver("message_queue"); + while (true) { + Message message = receiver.fetch(); + std::cout << "Message: " << message.getContent() << std::endl; + session.acknowledge(); + if (message.getContent().asString() == "That's all, folks!") { + std::cout << "Cancelling receiver" << std::endl; + receiver.cancel(); + break; + } + } + + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/queue_sender.cpp b/qpid/cpp/examples/messaging/queue_sender.cpp new file mode 100644 index 0000000000..637e7eb8e4 --- /dev/null +++ b/qpid/cpp/examples/messaging/queue_sender.cpp @@ -0,0 +1,65 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + int count = argc>2 ? atoi(argv[2]) : 10; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + Sender sender = session.createSender("message_queue"); + + // Now send some messages ... + for (int i=0; i<count; i++) { + Message message; + message.getContent() << "Message " << i; + sender.send(message); + } + + // And send a final message to indicate termination. + Message message("That's all, folks!"); + sender.send(message); + session.sync(); + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/server.cpp b/qpid/cpp/examples/messaging/server.cpp new file mode 100644 index 0000000000..6f0f2af02d --- /dev/null +++ b/qpid/cpp/examples/messaging/server.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/messaging/Address.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Variant.h> + +#include <algorithm> +#include <cstdlib> +#include <iostream> +#include <memory> +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + VariantMap options; + options["auto_acknowledge"] = 0; + Receiver receiver = session.createReceiver("service_queue", options); + + while (true) { + Message request = receiver.fetch(); + const Address& address = request.getReplyTo(); + if (address) { + Sender sender = session.createSender(address); + std::string s = request.getContent().asString(); + std::transform(s.begin(), s.end(), s.begin(), toupper); + Message response(s); + sender.send(response); + std::cout << "Processed request: " + << request.getContent().asString() + << " -> " + << response.getContent().asString() << std::endl; + session.acknowledge(); + } else { + std::cerr << "Error: no reply address specified for request: " << request.getContent().asString() << std::endl; + session.reject(request); + } + } + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/topic_listener.cpp b/qpid/cpp/examples/messaging/topic_listener.cpp new file mode 100644 index 0000000000..700e03cdf9 --- /dev/null +++ b/qpid/cpp/examples/messaging/topic_listener.cpp @@ -0,0 +1,82 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Filter.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/MessageListener.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Receiver.h> + +#include <cstdlib> +#include <iostream> + +using namespace qpid::messaging; + +class Listener : public MessageListener +{ + public: + Listener(const Receiver& receiver); + void received(Message& message); + bool isFinished(); + private: + Receiver receiver; + bool finished; +}; + +Listener::Listener(const Receiver& r) : receiver(r), finished(false) {} + +bool Listener::isFinished() { return finished; } + +void Listener::received(Message& message) +{ + std::cout << "Message: " << message.getContent().asString() << std::endl; + if (message.getContent().asString() == "That's all, folks!") { + std::cout << "Shutting down listener" << std::endl; + receiver.cancel(); + finished = true; + } +} + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + const char* pattern = argc>2 ? argv[2] : "#.#"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + + Filter filter(Filter::WILDCARD, pattern, "control"); + Receiver receiver = session.createReceiver("news_service", filter); + Listener listener(receiver); + receiver.setListener(&listener); + receiver.setCapacity(1); + receiver.start(); + while (session.dispatch() && !listener.isFinished()) ; + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/topic_receiver.cpp b/qpid/cpp/examples/messaging/topic_receiver.cpp new file mode 100644 index 0000000000..063f0d9cb0 --- /dev/null +++ b/qpid/cpp/examples/messaging/topic_receiver.cpp @@ -0,0 +1,66 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Filter.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + const char* pattern = argc>2 ? argv[2] : "#.#"; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + Filter filter(Filter::WILDCARD, pattern, "control"); + Receiver receiver = session.createReceiver(Address("news_service", "topic"), filter); + while (true) { + Message message = receiver.fetch(); + std::cout << "Message: " << message.getContent().asString() << std::endl; + if (message.getContent().asString() == "That's all, folks!") { + std::cout << "Cancelling receiver" << std::endl; + receiver.cancel(); + break; + } + } + + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/examples/messaging/topic_sender.cpp b/qpid/cpp/examples/messaging/topic_sender.cpp new file mode 100644 index 0000000000..5665fc45f9 --- /dev/null +++ b/qpid/cpp/examples/messaging/topic_sender.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/messaging/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> + +#include <cstdlib> +#include <iostream> + +#include <sstream> + +using namespace qpid::messaging; + +using std::stringstream; +using std::string; + +void sendMessages(Sender& sender, int count, const std::string& subject, const std::string& text) +{ + Message message; + message.setSubject(subject); + for (int i=0; i<count; i++) { + stringstream message_data; + message_data << text << i; + + message.setContent(message_data.str()); + sender.send(message); + } +} + +int main(int argc, char** argv) { + const char* url = argc>1 ? argv[1] : "amqp:tcp:127.0.0.1:5672"; + int count = argc>2 ? atoi(argv[2]) : 10; + + try { + Connection connection = Connection::open(url); + Session session = connection.newSession(); + Sender sender = session.createSender("news_service"); + + // Now send some messages to each topic... + sendMessages(sender, count, "usa.news", "news about the usa"); + sendMessages(sender, count, "usa.weather", "weather report for the usa"); + sendMessages(sender, count, "europe.news", "news about europe"); + sendMessages(sender, count, "europe.weather", "weather report for europe"); + + // And send a final message to indicate termination. + Message message("That's all, folks!"); + message.setSubject("control"); + sender.send(message); + session.sync(); + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/include/qmf/Agent.h b/qpid/cpp/include/qmf/Agent.h new file mode 100644 index 0000000000..e61cd737d0 --- /dev/null +++ b/qpid/cpp/include/qmf/Agent.h @@ -0,0 +1,287 @@ +#ifndef _QmfAgent_ +#define _QmfAgent_ + +/* + * 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 "qmf/QmfImportExport.h" + +namespace qmf { + + class AgentImpl; + class Connection; + class ObjectId; + class AgentObject; + class Value; + class Event; + class SchemaObjectClass; + + /** + * AgentListener is used by agents that select the internalStore=false option (see Agent + * constructor) or by agents that wish to provide access control for queries and methods. + * + * \ingroup qmfapi + */ + class AgentListener { + QMF_EXTERN virtual ~AgentListener(); + + /** + * allowQuery is called before a query operation is executed. If true is returned + * by this function, the query will proceed. If false is returned, the query will + * be forbidden. + * + * @param q The query being requested. + * @param userId The authenticated identity of the user requesting the query. + */ + virtual bool allowQuery(const Query& q, const char* userId); + + /** + * allowMethod is called before a method call is executed. If true is returned + * by this function, the method call will proceed. If false is returned, the method + * call will be forbidden. + * + * @param name The name of the method being called. + * @param args A value object (of type "map") that contains both input and output arguments. + * @param oid The objectId that identifies the instance of the object being called. + * @param cls The Schema describing the object being called. + * @param userId The authenticated identity of the requesting user. + */ + virtual bool allowMethod(const char* name, const Value& args, const ObjectId& oid, + const SchemaObjectClass& cls, const char* userId); + + /** + * query is called when the agent receives a query request. The handler must invoke + * Agent::queryResponse zero or more times (using the supplied context) followed by + * a single invocation of Agent::queryComplete. These calls do not need to be made + * synchronously in the context of this function. They may occur before or after this + * function returns. + * + * This function will only be invoked if internalStore=false in the Agent's constructor. + * + * @param context A context value to use in resulting calls to queryResponse and quertComplete. + * @param q The query requested by the console. + * @param userId the authenticated identity of the user requesting the query. + */ + virtual void query(uint32_t context, const Query& q, const char* userId); + + /** + * syncStart is called when a console requests a standing query. This function must + * behave exactly like AgentListener::query (i.e. send zero or more responses followed + * by a queryComplete) except it then remembers the context and the query and makes + * subsequent queryResponse calls whenever appropriate according the the query. + * + * The standing query shall stay in effect until syncStop is called with the same context + * value or until a specified period of time elapses without receiving a syncTouch for the + * context. + * + * This function will only be invoked if internalStore=false in the Agent's constructor. + * + * @param context A context value to use in resulting calls to queryResponse and queryComplete. + * @param q The query requested by the console. + * @param userId the authenticated identity of the user requesting the query. + */ + virtual void syncStart(uint32_t context, const Query& q, const char* userId); + + /** + * syncTouch is called when the console that requested a standing query refreshes its + * interest in the query. The console must periodically "touch" a standing query to keep + * it alive. This prevents standing queries from accumulating when the console disconnects + * before it can stop the query. + * + * This function will only be invoked if internalStore=false in the Agent's constructor. + * + * @param context The context supplied in a previous call to syncStart. + * @param userId The authenticated identity of the requesting user. + */ + virtual void syncTouch(uint32_t context, const char* userId); + + /** + * syncStop is called when the console that requested a standing query no longer wishes to + * receive data associated with that query. The application shall stop processing this + * query and shall remove its record of the context value. + * + * This function will only be invoked if internalStore=false in the Agent's constructor. + * + * @param context The context supplied in a previous call to syncStart. + * @param userId The authenticated identity of the requesting user. + */ + virtual void syncStop(uint32_t context, const char* userId); + + /** + * methodCall is called when a console invokes a method on a QMF object. The application + * must call Agent::methodResponse once in response to this function. The response does + * not need to be called synchronously in the context of this function. It may be called + * before or after this function returns. + * + * This function will only be invoked if internalStore=false in the Agent's constructor. + * + * @param context A context value to use in resulting call to methodResponse. + * @param name The name of the method being called. + * @param args A value object (of type "map") that contains both input and output arguments. + * @param oid The objectId that identifies the instance of the object being called. + * @param cls The Schema describing the object being called. + * @param userId The authenticated identity of the requesting user. + */ + virtual void methodCall(uint32_t context, const char* name, Value& args, + const ObjectId& oid, const SchemaObjectClass& cls, const char* userId); + }; + + /** + * The Agent class is the QMF Agent portal. It should be instantiated once and associated with a + * Connection (setConnection) to connect an agent to the QMF infrastructure. + * + * \ingroup qmfapi + */ + class Agent { + public: + /** + * Create an instance of the Agent class. + * + * @param label An optional string label that can be used to identify the agent. + * + * @param internalStore If true, objects shall be tracked internally by the agent. + * If false, the user of the agent must track the objects. + * If the agent is tracking the objects, queries and syncs are handled by + * the agent. The only involvement the user has is to optionally authorize + * individual operations. If the user is tracking the objects, the user code + * must implement queries and syncs (standing queries). + * + * @param listener A pointer to a class that implements the AgentListener interface. + * This must be supplied if any of the following conditions are true: + * - The agent model contains methods + * - The user wishes to individually authorize query and sync operations. + * - internalStore = false + */ + QMF_EXTERN Agent(char* label="qmfa", bool internalStore=true, AgentListener* listener=0); + + /** + * Destroy an instance of the Agent class. + */ + QMF_EXTERN ~Agent(); + + /** + * Set the persistent store file. This file, if specified, is used to store state information + * about the Agent. For example, if object-ids must be persistent across restarts of the Agent + * program, this file path must be supplied. + * + * @param path Full path to a file that is both writable and readable by the Agent program. + */ + QMF_EXTERN void setStoreDir(const char* path); + + /** + * Provide a connection (to a Qpid broker) over which the agent can communicate. + * + * @param conn Pointer to a Connection object. + */ + QMF_EXTERN void setConnection(Connection* conn); + + /** + * Register a class schema (object or event) with the agent. The agent must have a registered + * schema for an object class or an event class before it can handle objects or events of that + * class. + * + * @param cls Pointer to the schema structure describing the class. + */ + QMF_EXTERN void registerClass(SchemaObjectClass* cls); + QMF_EXTERN void registerClass(SchemaEventClass* cls); + + /** + * Add an object to the agent (for internal storage mode only). + * + * @param obj Reference to the object to be managed by the agent. + * + * @param persistent Iff true, the object ID assigned to the object shall indicate persistence + * (i.e. the object ID shall be the same across restarts of the agent program). + * + * @param oid 64-bit value for the oid (if zero, the agent will assign the value). + * + * @param oidLo 32-bit value for the lower 32-bits of the oid. + * + * @param oidHi 32-bit value for the upper 32-bits of the oid. + */ + QMF_EXTERN const ObjectId* addObject(AgentObject& obj, bool persistent=false, uint64_t oid=0); + QMF_EXTERN const ObjectId* addObject(AgentObject& obj, bool persistent, uint32_t oidLo, uint32_t oidHi); + + /** + * Allocate an object ID for an object (for external storage mode only). + * + * @param persistent Iff true, the object ID allocated shall indicate persistence + * (i.e. the object ID shall be the same across restarts of the agent program). + * + * @param oid 64-bit value for the oid (if zero, the agent will assign the value). + * + * @param oidLo 32-bit value for the lower 32-bits of the oid. + * + * @param oidHi 32-bit value for the upper 32-bits of the oid. + */ + QMF_EXTERN const ObjectId* allocObjectId(bool persistent=false, uint64_t oid=0); + QMF_EXTERN const ObjectId* allocObjectId(bool persistent, uint32_t oidLo, uint32_t oidHi); + + /** + * Raise a QMF event. + * + * @param event Reference to an event object to be raised to the QMF infrastructure. + */ + QMF_EXTERN void raiseEvent(Event& event); + + /** + * Provide a response to a query (for external storage mode only). + * + * @param context The context value supplied in the query (via the AgentListener interface). + * + * @param object A reference to the agent that matched the query criteria. + * + * @param prop If true, transmit the property attributes of this object. + * + * @param stat If true, transmit the statistic attributes of this object. + */ + QMF_EXTERN void queryResponse(uint32_t context, AgentObject& object, bool prop = true, bool stat = true); + + /** + * Indicate that a query (or the initial dump of a sync) is complete (for external storage mode only). + * + * @param context The context value supplied in the query/sync (via the AgentListener interface). + */ + QMF_EXTERN void queryComplete(uint32_t context); + + /** + * Provide the response to a method call. + * + * @param context The context value supplied in the method request (via the AgentListener interface). + * + * @param args The argument list from the method call. Must include the output arguments (may include + * the input arguments). + * + * @param status Numerical return status: zero indicates no error, non-zero indicates error. + * + * @param exception Pointer to an exception value. If status is non-zero, the exception value is + * sent to the caller. It is optional (i.e. leave the pointer as 0), or may be + * set to any legal value. A string may be supplied, but an unmanaged object of + * any schema may also be passed. + */ + QMF_EXTERN void methodResponse(uint32_t context, const Value& args, uint32_t status=0, + const Value* exception=0); + + private: + AgentImpl* impl; + }; + +} + +#endif diff --git a/qpid/cpp/include/qmf/AgentObject.h b/qpid/cpp/include/qmf/AgentObject.h new file mode 100644 index 0000000000..a1878605eb --- /dev/null +++ b/qpid/cpp/include/qmf/AgentObject.h @@ -0,0 +1,95 @@ +#ifndef _QmfAgentObject_ +#define _QmfAgentObject_ + +/* + * 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 "qmf/QmfImportExport.h" + +namespace qmf { + + class AgentObjectImpl; + class SchemaObjectClass; + class ObjectId; + class Value; + class Agent; + + /** + * AgentObject is an extension of Object with agent-specific methods added. + * + * \ingroup qmfapi + */ + class AgentObject : public Object { + public: + /** + * Create a new Object of a specific type. + * + * @param type Pointer to the schema class to use as a type for this object. + */ + QMF_EXTERN AgentObject(const SchemaObjectClass* type); + + /** + * Schedule this object for deletion. Agent objects should never be directly + * destroyed, rather this method should be called and all pointers to this + * object dropped. The agent will clean up and properly delete the object at + * the appropraite time. + */ + QMF_EXTERN void destroy(); + + /** + * Set the object ID for this object if it is to be managed by the agent. + * + * @param oid The new object ID for the managed object. + */ + QMF_EXTERN void setObjectId(ObjectId& oid); + + /** + * Handler for invoked method calls. This will only be called for objects that + * are being managed and stored by an agent (see internalStore argument in Agent::Agent). + * If this function is not overridden in a child class, the default implementation will + * cause AgentListener::methodCall to be invoked in the application program. + * + * If this function is overridden in a sub-class, the implementation must perform + * the actions associated with the method call (i.e. implement the method). Once the + * method execution is complete, it must call Agent::methodResponse with the result + * of the method execution. Agent::methodResponse does not need to be called + * synchronously in the context of this function call. It may be called at a later + * time from a different thread. + * + * @param context Context supplied by the agent and required to be passed in the + * call to Agent::methodResponse + * + * @param name The name of the method. + * + * @param args A Value (of type map) that contains the input and output arguments. + * + * @param userId The authenticated identity of the user who invoked the method. + */ + QMF_EXTERN virtual void methodInvoked(uint32_t context, const char* name, Value& args, + const char* userId); + private: + friend class Agent; + virtual ~AgentObject(); + void setAgent(Agent* agent); + AgentObjectImpl* impl; + }; + +} + +#endif diff --git a/qpid/cpp/include/qmf/Connection.h b/qpid/cpp/include/qmf/Connection.h new file mode 100644 index 0000000000..f648b1427f --- /dev/null +++ b/qpid/cpp/include/qmf/Connection.h @@ -0,0 +1,125 @@ +#ifndef _QmfConnection_ +#define _QmfConnection_ + +/* + * 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 "qmf/QmfImportExport.h" +#include "qmf/ConnectionSettings.h" + +namespace qmf { + + /** + * Operational states for Connections. + * + * \ingroup qmfapi + */ + enum ConnectionState { + CONNECTION_UP = 1, + CONNECTION_DOWN = 2 + }; + + /** + * Implement a subclass of ConnectionListener and provide it with the + * Connection constructor to receive notification of changes in the + * connection state. + * + * \ingroup qmfapi + */ + class ConnectionListener { + QMF_EXTERN virtual ~ConnectionListener(); + + /** + * Called each time the state of the connection changes. + * + * @param state the new state + */ + virtual void newState(ConnectionState state); + + /** + * Called if the connection requires input from an interactive client. + * + * @param prompt Text of the prompt - describes what information is required. + * @param answer The interactive user input. + * @param answerLen on Input - the maximum number of bytes that can be copied to answer. + * on Output - the number of bytes copied to answer. + */ + virtual void interactivePrompt(const char* prompt, char* answer, uint32_t answerLen); + }; + + class ConnectionImpl; + + /** + * The Connection class represents a connection to a QPID broker that can + * be used by agents and consoles, possibly multiple at the same time. + * + * \ingroup qmfapi + */ + class Connection { + public: + + /** + * Creates a connection object and begins the process of attempting to + * connect to the QPID broker. + * + * @param settings The settings that control how the connection is set + * up. + * + * @param listener An optional pointer to a subclass of + * ConnectionListener to receive notifications of events related to + * this connection. + */ + QMF_EXTERN Connection(const ConnectionSettings& settings, + const ConnectionListener* listener = 0); + + /** + * Destroys a connection, causing the connection to be closed. + */ + QMF_EXTERN ~Connection(); + + /** + * Set the administrative state of the connection (enabled or disabled). + * + * @param enabled True => enable connection, False => disable connection + */ + QMF_EXTERN void setAdminState(bool enabled); + + /** + * Return the current operational state of the connection (up or down). + * + * @return the current connection state. + */ + QMF_EXTERN ConnectionState getOperState() const; + + /** + * Get the error message from the last failure to connect. + * + * @return Null-terminated string containing the error message. + */ + QMF_EXTERN const char* getLastError() const; + + private: + friend class AgentImpl; + friend class ConsoleImpl; + ConnectionImpl* impl; + }; + +} + +#endif diff --git a/qpid/cpp/include/qmf/ConnectionSettings.h b/qpid/cpp/include/qmf/ConnectionSettings.h new file mode 100644 index 0000000000..9bd6922a56 --- /dev/null +++ b/qpid/cpp/include/qmf/ConnectionSettings.h @@ -0,0 +1,143 @@ +#ifndef _QmfConnectionSettings_ +#define _QmfConnectionSettings_ + +/* + * 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 "qmf/QmfImportExport.h" +#include "qpid/sys/IntegerTypes.h" + +namespace qmf { + + class ConnectionSettingsImpl; + class Value; + + /** + * Settings for AMQP connections to the broker. + * + * \ingroup qmfapi + */ + class ConnectionSettings { + public: + + ConnectionSettings(const ConnectionSettings& copy); + + /** + * Create a set of default connection settings. + * + * If no further attributes are set, the settings will cause a connection to be made to + * the default broker (on localhost or at a host/port supplied by service discovery) and + * authentication will be the best-available (GSSAPI/Kerberos, Anonymous, Plain with prompts + * for username and password). + */ + QMF_EXTERN ConnectionSettings(); + + /** + * Create a set of connection settings by URL. + * + * @param url Universal resource locator describing the broker address and additional attributes. + * + * The URL is of the form: + * amqp[s]://host[:port][?key=value[&key=value]*] + * + * For example: + * amqp://localhost + * amqp://broker?transport=rdma&authmech=GSSAPI&authservice=qpidd + * amqps://broker?authmech=PLAIN&authuser=guest&authpass=guest + */ + QMF_EXTERN ConnectionSettings(const char* url); + + /** + * Destroy the connection settings object. + */ + QMF_EXTERN ~ConnectionSettings(); + + /** + * Set an attribute to control connection setup. + * + * @param key A null-terminated string that is an attribute name. + * + * @param value Reference to a value to be stored as the attribute. The type of the value + * is specific to the key. + */ + QMF_EXTERN void setAttr(const char* key, const Value& value); + + /** + * Get the value of an attribute. + * + * @param key A null-terminated attribute name. + * + * @return The value associated with the attribute name. + */ + QMF_EXTERN Value getAttr(const char* key) const; + + /** + * Get the attribute string (the portion of the URL following the '?') for the settings. + * + * @return A pointer to the attribute string. If the content of this string needs to be + * available beyond the scope of the calling function, it should be copied. The + * returned pointer may become invalid if the set of attributes is changed. + */ + QMF_EXTERN const char* getAttrString() const; + + /** + * Shortcuts for setting the transport for the connection. + * + * @param port The port value for the connection address. + */ + QMF_EXTERN void transportTcp(uint16_t port = 5672); + QMF_EXTERN void transportSsl(uint16_t port = 5671); + QMF_EXTERN void transportRdma(uint16_t port = 5672); + + /** + * Shortcuts for setting authentication mechanisms. + * + * @param username Null-terminated authentication user name. + * + * @param password Null-terminated authentication password. + * + * @param serviceName Null-terminated GSSAPI service name (Kerberos service principal) + * + * @param minSsf Minimum security factor for connections. 0 = encryption not required. + * + * @param maxSsf Maximum security factor for connections. 0 = encryption not permitted. + */ + QMF_EXTERN void authAnonymous(const char* username = 0); + QMF_EXTERN void authPlain(const char* username = 0, const char* password = 0); + QMF_EXTERN void authGssapi(const char* serviceName, uint32_t minSsf = 0, uint32_t maxSsf = 256); + + /** + * Shortcut for setting connection retry attributes. + * + * @param delayMin Minimum delay (in seconds) between connection attempts. + * + * @param delaxMax Maximum delay (in seconds) between connection attempts. + * + * @param delayFactor Factor to multiply the delay by between failed connection attempts. + */ + QMF_EXTERN void setRetry(int delayMin = 1, int delayMax = 128, int delayFactor = 2); + + private: + friend class ResilientConnectionImpl; + ConnectionSettingsImpl* impl; + }; + +} + +#endif diff --git a/qpid/cpp/include/qmf/QmfImportExport.h b/qpid/cpp/include/qmf/QmfImportExport.h new file mode 100644 index 0000000000..8353a3cc16 --- /dev/null +++ b/qpid/cpp/include/qmf/QmfImportExport.h @@ -0,0 +1,33 @@ +#ifndef QMF_IMPORT_EXPORT_H +#define QMF_IMPORT_EXPORT_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. + */ + +#if defined(WIN32) && !defined(QPID_DECLARE_STATIC) +# if defined(QMF_EXPORT) || defined (qmf_EXPORTS) +# define QMF_EXTERN __declspec(dllexport) +# else +# define QMF_EXTERN __declspec(dllimport) +# endif +#else +# define QMF_EXTERN +#endif + +#endif diff --git a/qpid/cpp/include/qpid/client/Connection.h b/qpid/cpp/include/qpid/client/Connection.h index b7b967d232..0f5999cdcc 100644 --- a/qpid/cpp/include/qpid/client/Connection.h +++ b/qpid/cpp/include/qpid/client/Connection.h @@ -43,7 +43,21 @@ class ConnectionImpl; * * \ingroup clientapi * + * Some methods use an AMQP 0-10 URL to specify connection parameters. + * This is defined in the AMQP 0-10 specification (http://jira.amqp.org/confluence/display/AMQP/AMQP+Specification). + * + * amqp_url = "amqp:" prot_addr_list + * prot_addr_list = [prot_addr ","]* prot_addr + * prot_addr = tcp_prot_addr | tls_prot_addr + * + * tcp_prot_addr = tcp_id tcp_addr + * tcp_id = "tcp:" | "" + * tcp_addr = [host [":" port] ] + * host = <as per http://www.ietf.org/rfc/rfc3986.txt> + * port = number]]> + * */ + class Connection { framing::ProtocolVersion version; diff --git a/qpid/cpp/include/qpid/client/amqp0_10/Codecs.h b/qpid/cpp/include/qpid/client/amqp0_10/Codecs.h new file mode 100644 index 0000000000..5ef0b9fffe --- /dev/null +++ b/qpid/cpp/include/qpid/client/amqp0_10/Codecs.h @@ -0,0 +1,61 @@ +#ifndef QPID_CLIENT_AMQP0_10_CODECS_H +#define QPID_CLIENT_AMQP0_10_CODECS_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/Codec.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + + +/** + * Codec for encoding/decoding a map of Variants using the AMQP 0-10 + * map encoding. + */ +class MapCodec : public qpid::messaging::Codec +{ + public: + void encode(const qpid::messaging::Variant&, std::string&); + void decode(const std::string&, qpid::messaging::Variant&); + + static const std::string contentType; + private: +}; + +/** + * Codec for encoding/decoding a list of Variants using the AMQP 0-10 + * list encoding. + */ +class ListCodec : public qpid::messaging::Codec +{ + public: + void encode(const qpid::messaging::Variant&, std::string&); + void decode(const std::string&, qpid::messaging::Variant&); + + static const std::string contentType; + private: +}; + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_CODECS_H*/ diff --git a/qpid/cpp/include/qpid/framing/FieldTable.h b/qpid/cpp/include/qpid/framing/FieldTable.h index a3a5c8a4ee..b2331cd4e1 100644 --- a/qpid/cpp/include/qpid/framing/FieldTable.h +++ b/qpid/cpp/include/qpid/framing/FieldTable.h @@ -51,6 +51,8 @@ class FieldTable typedef boost::shared_ptr<FieldValue> ValuePtr; typedef std::map<std::string, ValuePtr> ValueMap; typedef ValueMap::iterator iterator; + typedef ValueMap::const_reference const_reference; + typedef ValueMap::value_type value_type; QPID_COMMON_EXTERN FieldTable() {}; QPID_COMMON_EXTERN FieldTable(const FieldTable& ft); @@ -97,12 +99,16 @@ class FieldTable QPID_COMMON_EXTERN bool operator==(const FieldTable& other) const; // Map-like interface. - // TODO: may need to duplicate into versions that return mutable iterator ValueMap::const_iterator begin() const { return values.begin(); } ValueMap::const_iterator end() const { return values.end(); } ValueMap::const_iterator find(const std::string& s) const { return values.find(s); } + ValueMap::iterator begin() { return values.begin(); } + ValueMap::iterator end() { return values.end(); } + ValueMap::iterator find(const std::string& s) { return values.find(s); } + std::pair <ValueMap::iterator, bool> insert(const ValueMap::value_type&); + ValueMap::iterator insert(ValueMap::iterator, const ValueMap::value_type&); void clear() { values.clear(); } // ### Hack Alert diff --git a/qpid/cpp/include/qpid/framing/FieldValue.h b/qpid/cpp/include/qpid/framing/FieldValue.h index 97fc56d606..ce4d06d2c8 100644 --- a/qpid/cpp/include/qpid/framing/FieldValue.h +++ b/qpid/cpp/include/qpid/framing/FieldValue.h @@ -36,7 +36,6 @@ namespace qpid { namespace framing { -//class Array; /** * Exception that is the base exception for all field table errors. * @@ -53,6 +52,8 @@ struct InvalidConversionException : public FieldValueException { InvalidConversionException() {} }; +class List; + /** * Value that can appear in an AMQP field table * @@ -82,7 +83,7 @@ class FieldValue { FieldValue(): data(0) {}; // Default assignment operator is fine void setType(uint8_t type); - uint8_t getType(); + QPID_COMMON_EXTERN uint8_t getType(); Data& getData() { return *data; } uint32_t encodedSize() const { return 1 + data->encodedSize(); }; bool empty() const { return data.get() == 0; } @@ -96,12 +97,19 @@ class FieldValue { template <typename T> bool convertsTo() const { return false; } template <typename T> T get() const { throw InvalidConversionException(); } + template <class T, int W> T getIntegerValue() const; + template <class T, int W> T getFloatingPointValue() const; + template <class T> bool get(T&) const; + protected: FieldValue(uint8_t t, Data* d): typeOctet(t), data(d) {} + QPID_COMMON_EXTERN static uint8_t* convertIfRequired(uint8_t* const octets, int width); + private: uint8_t typeOctet; std::auto_ptr<Data> data; + }; template <> @@ -165,10 +173,52 @@ class FixedWidthValue : public FieldValue::Data { return v; } uint8_t* rawOctets() { return octets; } + uint8_t* rawOctets() const { return octets; } void print(std::ostream& o) const { o << "F" << width << ":"; }; }; +template <class T, int W> +inline T FieldValue::getIntegerValue() const +{ + FixedWidthValue<W>* const fwv = dynamic_cast< FixedWidthValue<W>* const>(data.get()); + if (fwv) { + uint8_t* octets = fwv->rawOctets(); + T v = 0; + for (int i = 0; i < W-1; ++i) { + v |= octets[i]; v <<= 8; + } + v |= octets[W-1]; + return v; + } else { + throw InvalidConversionException(); + } +} + +template <class T, int W> +inline T FieldValue::getFloatingPointValue() const { + FixedWidthValue<W>* const fwv = dynamic_cast< FixedWidthValue<W>* const>(data.get()); + if (fwv) { + T value; + uint8_t* const octets = convertIfRequired(fwv->rawOctets(), W); + uint8_t* const target = reinterpret_cast<uint8_t*>(&value); + for (uint i = 0; i < W; ++i) target[i] = octets[i]; + return value; + } else { + throw InvalidConversionException(); + } +} + +template <> +inline float FieldValue::get<float>() const { + return getFloatingPointValue<float, 4>(); +} + +template <> +inline double FieldValue::get<double>() const { + return getFloatingPointValue<double, 8>(); +} + template <> class FixedWidthValue<0> : public FieldValue::Data { public: @@ -243,6 +293,27 @@ class EncodedValue : public FieldValue::Data { void print(std::ostream& o) const { o << "[" << value << "]"; }; }; +/** + * Accessor that can be used to get values of type FieldTable, Array + * and List. + */ +template <class T> +inline bool FieldValue::get(T& t) const +{ + const EncodedValue<T>* v = dynamic_cast< EncodedValue<T>* >(data.get()); + if (v != 0) { + t = v->getValue(); + return true; + } else { + try { + t = get<T>(); + return true; + } catch (const InvalidConversionException&) { + return false; + } + } +} + class Str8Value : public FieldValue { public: QPID_COMMON_EXTERN Str8Value(const std::string& v); @@ -294,6 +365,7 @@ class Unsigned64Value : public FieldValue { class FieldTableValue : public FieldValue { public: + typedef FieldTable ValueType; QPID_COMMON_EXTERN FieldTableValue(const FieldTable&); }; @@ -302,6 +374,49 @@ class ArrayValue : public FieldValue { QPID_COMMON_EXTERN ArrayValue(const Array&); }; +class VoidValue : public FieldValue { + public: + QPID_COMMON_EXTERN VoidValue(); +}; + +class BoolValue : public FieldValue { + public: + QPID_COMMON_EXTERN BoolValue(bool); +}; + +class Unsigned8Value : public FieldValue { + public: + QPID_COMMON_EXTERN Unsigned8Value(uint8_t); +}; + +class Unsigned16Value : public FieldValue { + public: + QPID_COMMON_EXTERN Unsigned16Value(uint16_t); +}; + +class Unsigned32Value : public FieldValue { + public: + QPID_COMMON_EXTERN Unsigned32Value(uint32_t); +}; + +class Integer8Value : public FieldValue { + public: + QPID_COMMON_EXTERN Integer8Value(int8_t); +}; + +class Integer16Value : public FieldValue { + public: + QPID_COMMON_EXTERN Integer16Value(int16_t); +}; + +typedef IntegerValue Integer32Value; + +class ListValue : public FieldValue { + public: + typedef List ValueType; + QPID_COMMON_EXTERN ListValue(const List&); +}; + template <class T> bool getEncodedValue(FieldTable::ValuePtr vptr, T& value) { @@ -315,7 +430,6 @@ bool getEncodedValue(FieldTable::ValuePtr vptr, T& value) return false; } - }} // qpid::framing #endif diff --git a/qpid/cpp/include/qpid/framing/List.h b/qpid/cpp/include/qpid/framing/List.h new file mode 100644 index 0000000000..cb1129ebf8 --- /dev/null +++ b/qpid/cpp/include/qpid/framing/List.h @@ -0,0 +1,76 @@ +#ifndef QPID_FRAMING_LIST_H +#define QPID_FRAMING_LIST_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/CommonImportExport.h" +#include "qpid/framing/amqp_types.h" +#include <iostream> +#include <list> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace framing { + +class Buffer; +class FieldValue; + +/** + * Representation of an AMQP 0-10 list + */ +class List +{ + public: + typedef boost::shared_ptr<FieldValue> ValuePtr; + typedef std::list<ValuePtr> Values; + typedef Values::const_iterator const_iterator; + typedef Values::iterator iterator; + typedef Values::const_reference const_reference; + + QPID_COMMON_EXTERN uint32_t encodedSize() const; + QPID_COMMON_EXTERN void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN void decode(Buffer& buffer); + + QPID_COMMON_EXTERN bool operator==(const List& other) const; + + // std collection interface. + QPID_COMMON_EXTERN const_iterator begin() const { return values.begin(); } + QPID_COMMON_EXTERN const_iterator end() const { return values.end(); } + QPID_COMMON_EXTERN iterator begin() { return values.begin(); } + QPID_COMMON_EXTERN iterator end(){ return values.end(); } + + QPID_COMMON_EXTERN ValuePtr front() const { return values.front(); } + QPID_COMMON_EXTERN ValuePtr back() const { return values.back(); } + QPID_COMMON_EXTERN size_t size() const { return values.size(); } + + QPID_COMMON_EXTERN iterator insert(iterator i, ValuePtr value) { return values.insert(i, value); } + QPID_COMMON_EXTERN void erase(iterator i) { values.erase(i); } + QPID_COMMON_EXTERN void push_back(ValuePtr value) { values.insert(end(), value); } + QPID_COMMON_EXTERN void pop_back() { values.pop_back(); } + + private: + Values values; + + friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& out, const List& list); +}; +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_LIST_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Address.h b/qpid/cpp/include/qpid/messaging/Address.h new file mode 100644 index 0000000000..e66c52f4c2 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Address.h @@ -0,0 +1,58 @@ +#ifndef QPID_MESSAGING_ADDRESS_H +#define QPID_MESSAGING_ADDRESS_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/ClientImportExport.h" +#include <ostream> + +namespace qpid { +namespace client { +} + +namespace messaging { + +/** + * Represents an address to which messages can be sent and from which + * messages can be received. Often a simple name is sufficient for + * this. However this struct allows the type of address to be + * specified allowing more sophisticated treatment if necessary. + */ +struct Address +{ + std::string value; + std::string type; + + QPID_CLIENT_EXTERN Address(); + QPID_CLIENT_EXTERN Address(const std::string& address); + QPID_CLIENT_EXTERN Address(const std::string& address, const std::string& type); + QPID_CLIENT_EXTERN operator const std::string&() const; + QPID_CLIENT_EXTERN const std::string& toStr() const; + QPID_CLIENT_EXTERN operator bool() const; + QPID_CLIENT_EXTERN bool operator !() const; +}; + +QPID_CLIENT_EXTERN std::ostream& operator<<(std::ostream& out, const Address& address); + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_ADDRESS_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Codec.h b/qpid/cpp/include/qpid/messaging/Codec.h new file mode 100644 index 0000000000..bacec5c786 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Codec.h @@ -0,0 +1,44 @@ +#ifndef QPID_MESSAGING_CODEC_H +#define QPID_MESSAGING_CODEC_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/ClientImportExport.h" + +namespace qpid { +namespace messaging { + +class Variant; +/** + * + */ +class Codec +{ + public: + QPID_CLIENT_EXTERN virtual ~Codec() {} + virtual void encode(const Variant&, std::string&) = 0; + virtual void decode(const std::string&, Variant&) = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_CODEC_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Connection.h b/qpid/cpp/include/qpid/messaging/Connection.h new file mode 100644 index 0000000000..5517e45af9 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Connection.h @@ -0,0 +1,67 @@ +#ifndef QPID_MESSAGING_CONNECTION_H +#define QPID_MESSAGING_CONNECTION_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/ClientImportExport.h" +#include "qpid/client/Handle.h" +#include "qpid/messaging/Variant.h" + +namespace qpid { +namespace client { + +template <class> class PrivateImplRef; + +} + +namespace messaging { + +class ConnectionImpl; +class Session; + +class Connection : public qpid::client::Handle<ConnectionImpl> +{ + public: + static Connection open(const std::string& url, const Variant::Map& options = Variant::Map()); + + QPID_CLIENT_EXTERN Connection(ConnectionImpl* impl = 0); + QPID_CLIENT_EXTERN Connection(const Connection&); + QPID_CLIENT_EXTERN ~Connection(); + QPID_CLIENT_EXTERN Connection& operator=(const Connection&); + QPID_CLIENT_EXTERN void close(); + QPID_CLIENT_EXTERN Session newSession(); + private: + friend class qpid::client::PrivateImplRef<Connection>; + +}; + +struct InvalidOptionString : public qpid::Exception +{ + InvalidOptionString(const std::string& msg); +}; + +QPID_CLIENT_EXTERN void parseOptionString(const std::string&, Variant::Map&); +QPID_CLIENT_EXTERN Variant::Map parseOptionString(const std::string&); + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_CONNECTION_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Filter.h b/qpid/cpp/include/qpid/messaging/Filter.h new file mode 100644 index 0000000000..5cd844cf73 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Filter.h @@ -0,0 +1,48 @@ +#ifndef QPID_MESSAGING_FILTER_H +#define QPID_MESSAGING_FILTER_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 <vector> +#include "qpid/client/ClientImportExport.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +struct Filter +{ + std::string type; + std::vector<std::string> patterns; + + QPID_CLIENT_EXTERN Filter(std::string type, std::string pattern); + QPID_CLIENT_EXTERN Filter(std::string type, std::string pattern1, std::string pattern2); + + static QPID_CLIENT_EXTERN const std::string WILDCARD; + static QPID_CLIENT_EXTERN const std::string EXACT_MATCH; +}; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_FILTER_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Message.h b/qpid/cpp/include/qpid/messaging/Message.h new file mode 100644 index 0000000000..39edae3637 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Message.h @@ -0,0 +1,90 @@ +#ifndef QPID_MESSAGING_MESSAGE_H +#define QPID_MESSAGING_MESSAGE_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/messaging/Variant.h" +#include "qpid/messaging/MessageContent.h" +#include "qpid/client/ClientImportExport.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Address; +class Codec; +class MessageImpl; + +/** + * Representation of a message. + */ +class Message +{ + public: + QPID_CLIENT_EXTERN Message(const std::string& bytes = std::string()); + QPID_CLIENT_EXTERN Message(const char*, size_t); + QPID_CLIENT_EXTERN Message(const Message&); + QPID_CLIENT_EXTERN ~Message(); + + QPID_CLIENT_EXTERN Message& operator=(const Message&); + + QPID_CLIENT_EXTERN void setReplyTo(const Address&); + QPID_CLIENT_EXTERN const Address& getReplyTo() const; + + QPID_CLIENT_EXTERN void setSubject(const std::string&); + QPID_CLIENT_EXTERN const std::string& getSubject() const; + + QPID_CLIENT_EXTERN void setContentType(const std::string&); + QPID_CLIENT_EXTERN const std::string& getContentType() const; + + QPID_CLIENT_EXTERN const VariantMap& getHeaders() const; + QPID_CLIENT_EXTERN VariantMap& getHeaders(); + + QPID_CLIENT_EXTERN const std::string& getBytes() const; + QPID_CLIENT_EXTERN std::string& getBytes(); + QPID_CLIENT_EXTERN void setBytes(const std::string&); + QPID_CLIENT_EXTERN void setBytes(const char* chars, size_t count); + QPID_CLIENT_EXTERN const char* getRawContent() const; + QPID_CLIENT_EXTERN size_t getContentSize() const; + + + QPID_CLIENT_EXTERN MessageContent& getContent(); + QPID_CLIENT_EXTERN const MessageContent& getContent() const; + QPID_CLIENT_EXTERN void setContent(const std::string& s); + QPID_CLIENT_EXTERN void setContent(const Variant::Map&); + QPID_CLIENT_EXTERN void setContent(const Variant::List&); + + QPID_CLIENT_EXTERN void encode(Codec&); + QPID_CLIENT_EXTERN void decode(Codec&); + + //TODO: move this out of the public API + QPID_CLIENT_EXTERN void setInternalId(void*); + QPID_CLIENT_EXTERN void* getInternalId(); + private: + MessageImpl* impl; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_MESSAGE_H*/ diff --git a/qpid/cpp/include/qpid/messaging/MessageContent.h b/qpid/cpp/include/qpid/messaging/MessageContent.h new file mode 100644 index 0000000000..7c3a636c07 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/MessageContent.h @@ -0,0 +1,90 @@ +#ifndef QPID_MESSAGING_MESSAGECONTENT_H +#define QPID_MESSAGING_MESSAGECONTENT_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/messaging/Variant.h" +#include <string> +#include "qpid/client/ClientImportExport.h" + +namespace qpid { +namespace messaging { + +/** + * + */ +class MessageContent +{ + public: + QPID_CLIENT_EXTERN virtual ~MessageContent() {} + + virtual const std::string& asString() const = 0; + virtual std::string& asString() = 0; + + virtual const char* asChars() const = 0; + virtual size_t size() const = 0; + + virtual const Variant::Map& asMap() const = 0; + virtual Variant::Map& asMap() = 0; + virtual bool isMap() const = 0; + + virtual const Variant::List& asList() const = 0; + virtual Variant::List& asList() = 0; + virtual bool isList() const = 0; + + virtual void clear() = 0; + + virtual Variant& operator[](const std::string&) = 0; + + + virtual std::ostream& print(std::ostream& out) const = 0; + + + //operator<< for variety of types... (is this a good idea?) + virtual MessageContent& operator<<(const std::string&) = 0; + virtual MessageContent& operator<<(const char*) = 0; + virtual MessageContent& operator<<(bool) = 0; + virtual MessageContent& operator<<(int8_t) = 0; + virtual MessageContent& operator<<(int16_t) = 0; + virtual MessageContent& operator<<(int32_t) = 0; + virtual MessageContent& operator<<(int64_t) = 0; + virtual MessageContent& operator<<(uint8_t) = 0; + virtual MessageContent& operator<<(uint16_t) = 0; + virtual MessageContent& operator<<(uint32_t) = 0; + virtual MessageContent& operator<<(uint64_t) = 0; + virtual MessageContent& operator<<(double) = 0; + virtual MessageContent& operator<<(float) = 0; + + //assignment from string, map and list + virtual MessageContent& operator=(const std::string&) = 0; + virtual MessageContent& operator=(const char*) = 0; + virtual MessageContent& operator=(const Variant::Map&) = 0; + virtual MessageContent& operator=(const Variant::List&) = 0; + + private: +}; + +QPID_CLIENT_EXTERN std::ostream& operator<<(std::ostream& out, const MessageContent& content); + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_MESSAGECONTENT_H*/ diff --git a/qpid/cpp/include/qpid/messaging/MessageListener.h b/qpid/cpp/include/qpid/messaging/MessageListener.h new file mode 100644 index 0000000000..72811e7b9c --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/MessageListener.h @@ -0,0 +1,49 @@ +#ifndef QPID_MESSAGING_MESSAGELISTENER_H +#define QPID_MESSAGING_MESSAGELISTENER_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" + +namespace qpid { +namespace messaging { + +class Message; + +/** + * To use a push style interface for receiving messages, applications + * provide implementations of this interface and pass an implementing + * instance to MessageSource::subscribe(). + * + * Messages arriving for that subscription will then be passed to the + * implementation via the received() method. + */ +class MessageListener +{ + public: + QPID_CLIENT_EXTERN virtual ~MessageListener() {} + virtual void received(Message&) = 0; + private: +}; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_MESSAGELISTENER_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Receiver.h b/qpid/cpp/include/qpid/messaging/Receiver.h new file mode 100644 index 0000000000..e51e1093d1 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Receiver.h @@ -0,0 +1,115 @@ +#ifndef QPID_MESSAGING_RECEIVER_H +#define QPID_MESSAGING_RECEIVER_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/Exception.h" +#include "qpid/client/ClientImportExport.h" +#include "qpid/client/Handle.h" +#include "qpid/sys/Time.h" + +namespace qpid { +namespace client { + +template <class> class PrivateImplRef; + +} + +namespace messaging { + +class Message; +class MessageListener; +class ReceiverImpl; + +/** + * A pull style interface for message retrieval. + */ +class Receiver : public qpid::client::Handle<ReceiverImpl> +{ + public: + struct NoMessageAvailable : qpid::Exception {}; + + QPID_CLIENT_EXTERN Receiver(ReceiverImpl* impl = 0); + QPID_CLIENT_EXTERN Receiver(const Receiver&); + QPID_CLIENT_EXTERN ~Receiver(); + QPID_CLIENT_EXTERN Receiver& operator=(const Receiver&); + /** + * Retrieves a message from this receivers local queue, or waits + * for upto the specified timeout for a message to become + * available. Returns false if there is no message to give after + * waiting for the specified timeout. + */ + QPID_CLIENT_EXTERN bool get(Message& message, qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + /** + * Retrieves a message from this receivers local queue, or waits + * for upto the specified timeout for a message to become + * available. Throws NoMessageAvailable if there is no + * message to give after waiting for the specified timeout. + */ + QPID_CLIENT_EXTERN Message get(qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + /** + * Retrieves a message for this receivers subscription or waits + * for upto the specified timeout for one to become + * available. Unlike get() this method will check with the server + * that there is no message for the subscription this receiver is + * serving before returning false. + */ + QPID_CLIENT_EXTERN bool fetch(Message& message, qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + /** + * Retrieves a message for this receivers subscription or waits + * for upto the specified timeout for one to become + * available. Unlike get() this method will check with the server + * that there is no message for the subscription this receiver is + * serving before throwing an exception. + */ + QPID_CLIENT_EXTERN Message fetch(qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + + /** + * Enables the message flow for this receiver + */ + QPID_CLIENT_EXTERN void start(); + /** + * Stops the message flow for this receiver (without actually + * cancelling the subscription). + */ + QPID_CLIENT_EXTERN void stop(); + /** + * Sets the capacity for the receiver. The capacity determines how + * many incoming messages can be held in the receiver before being + * requested by a client via fetch() (or pushed to a listener). + */ + QPID_CLIENT_EXTERN void setCapacity(uint32_t); + + /** + * Cancels this receiver + */ + QPID_CLIENT_EXTERN void cancel(); + + /** + * Set a message listener for receiving messages asynchronously. + */ + QPID_CLIENT_EXTERN void setListener(MessageListener* listener); + private: + friend class qpid::client::PrivateImplRef<Receiver>; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_RECEIVER_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Sender.h b/qpid/cpp/include/qpid/messaging/Sender.h new file mode 100644 index 0000000000..657c4b8cfe --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Sender.h @@ -0,0 +1,57 @@ +#ifndef QPID_MESSAGING_SENDER_H +#define QPID_MESSAGING_SENDER_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" + +namespace qpid { +namespace client { + +template <class> class PrivateImplRef; + +} + +namespace messaging { + +class Message; +class SenderImpl; + +/** + * Interface through which messages are sent. + */ +class Sender : public qpid::client::Handle<SenderImpl> +{ + public: + QPID_CLIENT_EXTERN Sender(SenderImpl* impl = 0); + QPID_CLIENT_EXTERN Sender(const Sender&); + QPID_CLIENT_EXTERN ~Sender(); + QPID_CLIENT_EXTERN Sender& operator=(const Sender&); + + QPID_CLIENT_EXTERN void send(Message& message); + QPID_CLIENT_EXTERN void cancel(); + private: + friend class qpid::client::PrivateImplRef<Sender>; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SENDER_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Session.h b/qpid/cpp/include/qpid/messaging/Session.h new file mode 100644 index 0000000000..3a354c009f --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Session.h @@ -0,0 +1,99 @@ +#ifndef QPID_MESSAGING_SESSION_H +#define QPID_MESSAGING_SESSION_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/sys/Time.h" +#include "Variant.h" + +namespace qpid { +namespace client { + +template <class> class PrivateImplRef; + +} + +namespace messaging { + +class Address; +class Filter; +class Message; +class MessageListener; +class Sender; +class Receiver; +class SessionImpl; +class Subscription; + +/** + * A session represents a distinct 'conversation' which can involve + * sending and receiving messages from different sources and sinks. + */ +class Session : public qpid::client::Handle<SessionImpl> +{ + public: + QPID_CLIENT_EXTERN Session(SessionImpl* impl = 0); + QPID_CLIENT_EXTERN Session(const Session&); + QPID_CLIENT_EXTERN ~Session(); + QPID_CLIENT_EXTERN Session& operator=(const Session&); + + QPID_CLIENT_EXTERN void close(); + + QPID_CLIENT_EXTERN void commit(); + QPID_CLIENT_EXTERN void rollback(); + + /** + * Acknowledges all outstanding messages that have been received + * by the application on this session. + */ + QPID_CLIENT_EXTERN void acknowledge(); + /** + * Rejects the specified message. This will prevent the message + * being redelivered. + */ + QPID_CLIENT_EXTERN void reject(Message&); + + QPID_CLIENT_EXTERN void sync(); + QPID_CLIENT_EXTERN void flush(); + + QPID_CLIENT_EXTERN bool fetch(Message& message, qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + QPID_CLIENT_EXTERN Message fetch(qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + QPID_CLIENT_EXTERN bool dispatch(qpid::sys::Duration timeout=qpid::sys::TIME_INFINITE); + + QPID_CLIENT_EXTERN Sender createSender(const Address& address, const VariantMap& options = VariantMap()); + QPID_CLIENT_EXTERN Sender createSender(const std::string& address, const VariantMap& options = VariantMap()); + + QPID_CLIENT_EXTERN Receiver createReceiver(const Address& address, const VariantMap& options = VariantMap()); + QPID_CLIENT_EXTERN Receiver createReceiver(const Address& address, const Filter& filter, const VariantMap& options = VariantMap()); + QPID_CLIENT_EXTERN Receiver createReceiver(const std::string& address, const VariantMap& options = VariantMap()); + QPID_CLIENT_EXTERN Receiver createReceiver(const std::string& address, const Filter& filter, const VariantMap& options = VariantMap()); + + QPID_CLIENT_EXTERN Address createTempQueue(const std::string& baseName = std::string()); + + QPID_CLIENT_EXTERN void* getLastConfirmedSent(); + QPID_CLIENT_EXTERN void* getLastConfirmedAcknowledged(); + private: + friend class qpid::client::PrivateImplRef<Session>; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SESSION_H*/ diff --git a/qpid/cpp/include/qpid/messaging/Variant.h b/qpid/cpp/include/qpid/messaging/Variant.h new file mode 100644 index 0000000000..ac000244c2 --- /dev/null +++ b/qpid/cpp/include/qpid/messaging/Variant.h @@ -0,0 +1,167 @@ +#ifndef QPID_MESSAGING_VARIANT_H +#define QPID_MESSAGING_VARIANT_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 <list> +#include <map> +#include <ostream> +#include <string> +#include "qpid/Exception.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/client/ClientImportExport.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +/** + * Thrown when an illegal conversion of a variant is attempted. + */ +struct InvalidConversion : public qpid::Exception +{ + InvalidConversion(const std::string& msg); +}; + +enum VariantType { + VOID = 0, + BOOL, + UINT8, + UINT16, + UINT32, + UINT64, + INT8, + INT16, + INT32, + INT64, + FLOAT, + DOUBLE, + STRING, + MAP, + LIST +}; + +class VariantImpl; + +/** + * Represents a value of variable type. + */ +class Variant +{ + public: + typedef std::map<std::string, Variant> Map; + typedef std::list<Variant> List; + + QPID_CLIENT_EXTERN Variant(); + QPID_CLIENT_EXTERN Variant(bool); + QPID_CLIENT_EXTERN Variant(uint8_t); + QPID_CLIENT_EXTERN Variant(uint16_t); + QPID_CLIENT_EXTERN Variant(uint32_t); + QPID_CLIENT_EXTERN Variant(uint64_t); + QPID_CLIENT_EXTERN Variant(int8_t); + QPID_CLIENT_EXTERN Variant(int16_t); + QPID_CLIENT_EXTERN Variant(int32_t); + QPID_CLIENT_EXTERN Variant(int64_t); + QPID_CLIENT_EXTERN Variant(float); + QPID_CLIENT_EXTERN Variant(double); + QPID_CLIENT_EXTERN Variant(const std::string&); + QPID_CLIENT_EXTERN Variant(const char*); + QPID_CLIENT_EXTERN Variant(const Map&); + QPID_CLIENT_EXTERN Variant(const List&); + QPID_CLIENT_EXTERN Variant(const Variant&); + + QPID_CLIENT_EXTERN ~Variant(); + + QPID_CLIENT_EXTERN VariantType getType() const; + + QPID_CLIENT_EXTERN Variant& operator=(bool); + QPID_CLIENT_EXTERN Variant& operator=(uint8_t); + QPID_CLIENT_EXTERN Variant& operator=(uint16_t); + QPID_CLIENT_EXTERN Variant& operator=(uint32_t); + QPID_CLIENT_EXTERN Variant& operator=(uint64_t); + QPID_CLIENT_EXTERN Variant& operator=(int8_t); + QPID_CLIENT_EXTERN Variant& operator=(int16_t); + QPID_CLIENT_EXTERN Variant& operator=(int32_t); + QPID_CLIENT_EXTERN Variant& operator=(int64_t); + QPID_CLIENT_EXTERN Variant& operator=(float); + QPID_CLIENT_EXTERN Variant& operator=(double); + QPID_CLIENT_EXTERN Variant& operator=(const std::string&); + QPID_CLIENT_EXTERN Variant& operator=(const char*); + QPID_CLIENT_EXTERN Variant& operator=(const Map&); + QPID_CLIENT_EXTERN Variant& operator=(const List&); + QPID_CLIENT_EXTERN Variant& operator=(const Variant&); + + QPID_CLIENT_EXTERN bool asBool() const; + QPID_CLIENT_EXTERN uint8_t asUint8() const; + QPID_CLIENT_EXTERN uint16_t asUint16() const; + QPID_CLIENT_EXTERN uint32_t asUint32() const; + QPID_CLIENT_EXTERN uint64_t asUint64() const; + QPID_CLIENT_EXTERN int8_t asInt8() const; + QPID_CLIENT_EXTERN int16_t asInt16() const; + QPID_CLIENT_EXTERN int32_t asInt32() const; + QPID_CLIENT_EXTERN int64_t asInt64() const; + QPID_CLIENT_EXTERN float asFloat() const; + QPID_CLIENT_EXTERN double asDouble() const; + QPID_CLIENT_EXTERN std::string asString() const; + + QPID_CLIENT_EXTERN operator bool() const; + QPID_CLIENT_EXTERN operator uint8_t() const; + QPID_CLIENT_EXTERN operator uint16_t() const; + QPID_CLIENT_EXTERN operator uint32_t() const; + QPID_CLIENT_EXTERN operator uint64_t() const; + QPID_CLIENT_EXTERN operator int8_t() const; + QPID_CLIENT_EXTERN operator int16_t() const; + QPID_CLIENT_EXTERN operator int32_t() const; + QPID_CLIENT_EXTERN operator int64_t() const; + QPID_CLIENT_EXTERN operator float() const; + QPID_CLIENT_EXTERN operator double() const; + QPID_CLIENT_EXTERN operator const char*() const; + + QPID_CLIENT_EXTERN const Map& asMap() const; + QPID_CLIENT_EXTERN Map& asMap(); + QPID_CLIENT_EXTERN const List& asList() const; + QPID_CLIENT_EXTERN List& asList(); + /** + * Unlike asString(), getString() will not do any conversions and + * will throw InvalidConversion if the type is not STRING. + */ + QPID_CLIENT_EXTERN const std::string& getString() const; + QPID_CLIENT_EXTERN std::string& getString(); + + QPID_CLIENT_EXTERN void setEncoding(const std::string&); + QPID_CLIENT_EXTERN const std::string& getEncoding() const; + + QPID_CLIENT_EXTERN void reset(); + private: + VariantImpl* impl; +}; + +QPID_CLIENT_EXTERN std::ostream& operator<<(std::ostream& out, const Variant& value); +QPID_CLIENT_EXTERN std::ostream& operator<<(std::ostream& out, const Variant::Map& map); +QPID_CLIENT_EXTERN std::ostream& operator<<(std::ostream& out, const Variant::List& list); + +typedef Variant::Map VariantMap; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_VARIANT_H*/ diff --git a/qpid/cpp/src/CMakeLists.txt b/qpid/cpp/src/CMakeLists.txt index a1d01ceca4..7456401618 100644 --- a/qpid/cpp/src/CMakeLists.txt +++ b/qpid/cpp/src/CMakeLists.txt @@ -451,6 +451,7 @@ set (libqpidcommon_SOURCES qpid/framing/FieldValue.cpp qpid/framing/FrameSet.cpp qpid/framing/FrameDecoder.cpp + qpid/framing/List.cpp qpid/framing/ProtocolInitiation.cpp qpid/framing/ProtocolVersion.cpp qpid/framing/SendContent.cpp @@ -522,6 +523,35 @@ set (libqpidclient_SOURCES qpid/client/SubscriptionImpl.cpp qpid/client/SubscriptionManager.cpp qpid/client/SubscriptionManagerImpl.cpp + qpid/messaging/Address.cpp + qpid/messaging/Connection.cpp + qpid/messaging/ConnectionImpl.h + qpid/messaging/Filter.cpp + qpid/messaging/Message.cpp + qpid/messaging/Receiver.cpp + qpid/messaging/ReceiverImpl.h + qpid/messaging/Session.cpp + qpid/messaging/SessionImpl.h + qpid/messaging/Sender.cpp + qpid/messaging/SenderImpl.h + qpid/messaging/Variant.cpp + qpid/client/amqp0_10/AddressResolution.h + qpid/client/amqp0_10/AddressResolution.cpp + qpid/client/amqp0_10/Codecs.cpp + qpid/client/amqp0_10/CompletionTracker.h + qpid/client/amqp0_10/CompletionTracker.cpp + qpid/client/amqp0_10/ConnectionImpl.h + qpid/client/amqp0_10/ConnectionImpl.cpp + qpid/client/amqp0_10/IncomingMessages.h + qpid/client/amqp0_10/IncomingMessages.cpp + qpid/client/amqp0_10/MessageSink.h + qpid/client/amqp0_10/MessageSource.h + qpid/client/amqp0_10/ReceiverImpl.h + qpid/client/amqp0_10/ReceiverImpl.cpp + qpid/client/amqp0_10/SessionImpl.h + qpid/client/amqp0_10/SessionImpl.cpp + qpid/client/amqp0_10/SenderImpl.h + qpid/client/amqp0_10/SenderImpl.cpp ) add_library (qpidclient SHARED ${libqpidclient_SOURCES}) target_link_libraries (qpidclient qpidcommon) @@ -615,10 +645,10 @@ target_link_libraries (qpidd qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRA # qpid/agent/ManagementAgent.h \ # qpid/agent/ManagementAgentImpl.h set (qmfagent_SOURCES - ../include/qpid/agent/ManagementAgent.h - qpid/agent/ManagementAgentImpl.cpp - qpid/agent/ManagementAgentImpl.h - qmf/Agent.cpp + qmf/AgentEngine.cpp + qmf/AgentEngine.h + qpid/agent/ManagementAgentImpl.cpp + qpid/agent/ManagementAgentImpl.h ) add_library (qmfagent SHARED ${qmfagent_SOURCES}) target_link_libraries (qmfagent qmfcommon) @@ -626,14 +656,31 @@ set_target_properties (qmfagent PROPERTIES VERSION ${qpidc_version}) set (qmfcommon_SOURCES - qmf/Agent.cpp - qmf/ResilientConnection.cpp + qmf/ConnectionSettingsImpl.cpp + qmf/ConnectionSettingsImpl.h + qmf/ConsoleEngine.h + qmf/Event.h + qmf/Message.h qmf/MessageImpl.cpp - qmf/SchemaImpl.cpp - qmf/ValueImpl.cpp + qmf/MessageImpl.h + qmf/Object.h + qmf/ObjectId.h qmf/ObjectIdImpl.cpp + qmf/ObjectIdImpl.h qmf/ObjectImpl.cpp + qmf/ObjectImpl.h + qmf/Query.h qmf/QueryImpl.cpp + qmf/QueryImpl.h + qmf/ResilientConnection.cpp + qmf/ResilientConnection.h + qmf/Schema.h + qmf/SchemaImpl.cpp + qmf/SchemaImpl.h + qmf/Typecode.h + qmf/Value.h + qmf/ValueImpl.cpp + qmf/ValueImpl.h ) add_library (qmfcommon SHARED ${qmfcommon_SOURCES}) target_link_libraries (qmfcommon qpidclient) diff --git a/qpid/cpp/src/Makefile.am b/qpid/cpp/src/Makefile.am index 0eff526203..05b5efc5b5 100644 --- a/qpid/cpp/src/Makefile.am +++ b/qpid/cpp/src/Makefile.am @@ -378,6 +378,7 @@ libqpidcommon_la_SOURCES += \ qpid/framing/InputHandler.h \ qpid/framing/Invoker.h \ qpid/framing/IsInSequenceSet.h \ + qpid/framing/List.cpp \ qpid/framing/MethodBodyFactory.h \ qpid/framing/MethodContent.h \ qpid/framing/ModelMethod.h \ @@ -680,7 +681,36 @@ libqpidclient_la_SOURCES = \ qpid/client/SubscriptionImpl.h \ qpid/client/SubscriptionManager.cpp \ qpid/client/SubscriptionManagerImpl.cpp \ - qpid/client/SubscriptionManagerImpl.h + qpid/client/SubscriptionManagerImpl.h \ + qpid/messaging/Address.cpp \ + qpid/messaging/Connection.cpp \ + qpid/messaging/Filter.cpp \ + qpid/messaging/Message.cpp \ + qpid/messaging/Sender.cpp \ + qpid/messaging/Receiver.cpp \ + qpid/messaging/Session.cpp \ + qpid/messaging/Variant.cpp \ + qpid/messaging/ConnectionImpl.h \ + qpid/messaging/SenderImpl.h \ + qpid/messaging/ReceiverImpl.h \ + qpid/messaging/SessionImpl.h \ + qpid/client/amqp0_10/AddressResolution.h \ + qpid/client/amqp0_10/AddressResolution.cpp \ + qpid/client/amqp0_10/Codecs.cpp \ + qpid/client/amqp0_10/ConnectionImpl.h \ + qpid/client/amqp0_10/ConnectionImpl.cpp \ + qpid/client/amqp0_10/CompletionTracker.h \ + qpid/client/amqp0_10/CompletionTracker.cpp \ + qpid/client/amqp0_10/IncomingMessages.h \ + qpid/client/amqp0_10/IncomingMessages.cpp \ + qpid/client/amqp0_10/MessageSink.h \ + qpid/client/amqp0_10/MessageSource.h \ + qpid/client/amqp0_10/ReceiverImpl.h \ + qpid/client/amqp0_10/ReceiverImpl.cpp \ + qpid/client/amqp0_10/SessionImpl.h \ + qpid/client/amqp0_10/SessionImpl.cpp \ + qpid/client/amqp0_10/SenderImpl.h \ + qpid/client/amqp0_10/SenderImpl.cpp # NOTE: only public header files (which should be in ../include) # should go in this list. Private headers should go in the SOURCES @@ -723,6 +753,7 @@ nobase_include_HEADERS += \ ../include/qpid/framing/Buffer.h \ ../include/qpid/framing/FieldTable.h \ ../include/qpid/framing/FieldValue.h \ + ../include/qpid/framing/List.h \ ../include/qpid/framing/ProtocolVersion.h \ ../include/qpid/framing/SequenceNumber.h \ ../include/qpid/framing/SequenceSet.h \ @@ -749,7 +780,19 @@ nobase_include_HEADERS += \ ../include/qpid/sys/SystemInfo.h \ ../include/qpid/sys/Thread.h \ ../include/qpid/sys/Time.h \ - ../include/qpid/sys/uuid.h + ../include/qpid/sys/uuid.h \ + ../include/qpid/messaging/Address.h \ + ../include/qpid/messaging/Connection.h \ + ../include/qpid/messaging/Codec.h \ + ../include/qpid/messaging/Filter.h \ + ../include/qpid/messaging/Message.h \ + ../include/qpid/messaging/MessageContent.h \ + ../include/qpid/messaging/MessageListener.h \ + ../include/qpid/messaging/Sender.h \ + ../include/qpid/messaging/Receiver.h \ + ../include/qpid/messaging/Session.h \ + ../include/qpid/messaging/Variant.h \ + ../include/qpid/client/amqp0_10/Codecs.h # Force build of qpidd during dist phase so help2man will work. dist-hook: $(BUILT_SOURCES) diff --git a/qpid/cpp/src/qmf.mk b/qpid/cpp/src/qmf.mk index 62393cdcfb..9d24709b6e 100644 --- a/qpid/cpp/src/qmf.mk +++ b/qpid/cpp/src/qmf.mk @@ -18,7 +18,7 @@ # # -# qmf agent library makefile fragment, to be included in Makefile.am +# qmf library makefile fragment, to be included in Makefile.am # lib_LTLIBRARIES += \ libqmfcommon.la \ @@ -27,13 +27,17 @@ lib_LTLIBRARIES += \ # Public header files nobase_include_HEADERS += \ ../include/qpid/agent/ManagementAgent.h \ - ../include/qpid/agent/QmfAgentImportExport.h - + ../include/qpid/agent/QmfAgentImportExport.h \ + ../include/qmf/Agent.h \ + ../include/qmf/Connection.h \ + ../include/qmf/QmfImportExport.h \ + ../include/qmf/ConnectionSettings.h \ + ../include/qmf/AgentObject.h libqmfcommon_la_SOURCES = \ - qmf/Agent.cpp \ - qmf/Agent.h \ - qmf/Console.h \ + qmf/ConnectionSettingsImpl.cpp \ + qmf/ConnectionSettingsImpl.h \ + qmf/ConsoleEngine.h \ qmf/Event.h \ qmf/Message.h \ qmf/MessageImpl.cpp \ @@ -58,9 +62,9 @@ libqmfcommon_la_SOURCES = \ qmf/ValueImpl.h libqmfagent_la_SOURCES = \ - ../include/qpid/agent/ManagementAgent.h \ + qmf/AgentEngine.cpp \ + qmf/AgentEngine.h \ qpid/agent/ManagementAgentImpl.cpp \ - qpid/agent/ManagementAgentImpl.h \ - qmf/Agent.cpp + qpid/agent/ManagementAgentImpl.h -libqmfagent_la_LIBADD = libqpidclient.la +libqmfagent_la_LIBADD = libqpidclient.la libqmfcommon.la diff --git a/qpid/cpp/src/qmf/Agent.cpp b/qpid/cpp/src/qmf/AgentEngine.cpp index 6d59ae2750..bef8b3d102 100644 --- a/qpid/cpp/src/qmf/Agent.cpp +++ b/qpid/cpp/src/qmf/AgentEngine.cpp @@ -17,7 +17,7 @@ * under the License. */ -#include "qmf/Agent.h" +#include "qmf/AgentEngine.h" #include "qmf/MessageImpl.h" #include "qmf/SchemaImpl.h" #include "qmf/Typecode.h" @@ -77,13 +77,13 @@ namespace qmf { AgentQueryContext() : schemaMethod(0) {} }; - class AgentImpl { + class AgentEngineImpl { public: - AgentImpl(char* label, bool internalStore); - ~AgentImpl(); + AgentEngineImpl(char* label, bool internalStore); + ~AgentEngineImpl(); - void setStoreDir(char* path); - void setTransferDir(char* path); + void setStoreDir(const char* path); + void setTransferDir(const char* path); void handleRcvMessage(Message& message); bool getXmtMessage(Message& item); void popXmt(); @@ -200,9 +200,9 @@ namespace qmf { }; } -const char* AgentImpl::QMF_EXCHANGE = "qpid.management"; -const char* AgentImpl::DIR_EXCHANGE = "amq.direct"; -const char* AgentImpl::BROKER_KEY = "broker"; +const char* AgentEngineImpl::QMF_EXCHANGE = "qpid.management"; +const char* AgentEngineImpl::DIR_EXCHANGE = "amq.direct"; +const char* AgentEngineImpl::BROKER_KEY = "broker"; #define STRING_REF(s) {if (!s.empty()) item.s = const_cast<char*>(s.c_str());} @@ -228,7 +228,7 @@ AgentEvent AgentEventImpl::copy() return item; } -AgentImpl::AgentImpl(char* _label, bool i) : +AgentEngineImpl::AgentEngineImpl(char* _label, bool i) : label(_label), queueName("qmfa-"), internalStore(i), nextTransientId(1), requestedBrokerBank(0), requestedAgentBank(0), assignedBrokerBank(0), assignedAgentBank(0), @@ -237,11 +237,11 @@ AgentImpl::AgentImpl(char* _label, bool i) : queueName += label; } -AgentImpl::~AgentImpl() +AgentEngineImpl::~AgentEngineImpl() { } -void AgentImpl::setStoreDir(char* path) +void AgentEngineImpl::setStoreDir(const char* path) { Mutex::ScopedLock _lock(lock); if (path) @@ -250,7 +250,7 @@ void AgentImpl::setStoreDir(char* path) storeDir.clear(); } -void AgentImpl::setTransferDir(char* path) +void AgentEngineImpl::setTransferDir(const char* path) { Mutex::ScopedLock _lock(lock); if (path) @@ -259,7 +259,7 @@ void AgentImpl::setTransferDir(char* path) transferDir.clear(); } -void AgentImpl::handleRcvMessage(Message& message) +void AgentEngineImpl::handleRcvMessage(Message& message) { Buffer inBuffer(message.body, message.length); uint8_t opcode; @@ -277,7 +277,7 @@ void AgentImpl::handleRcvMessage(Message& message) } } -bool AgentImpl::getXmtMessage(Message& item) +bool AgentEngineImpl::getXmtMessage(Message& item) { Mutex::ScopedLock _lock(lock); if (xmtQueue.empty()) @@ -286,14 +286,14 @@ bool AgentImpl::getXmtMessage(Message& item) return true; } -void AgentImpl::popXmt() +void AgentEngineImpl::popXmt() { Mutex::ScopedLock _lock(lock); if (!xmtQueue.empty()) xmtQueue.pop_front(); } -bool AgentImpl::getEvent(AgentEvent& event) +bool AgentEngineImpl::getEvent(AgentEvent& event) { Mutex::ScopedLock _lock(lock); if (eventQueue.empty()) @@ -302,14 +302,14 @@ bool AgentImpl::getEvent(AgentEvent& event) return true; } -void AgentImpl::popEvent() +void AgentEngineImpl::popEvent() { Mutex::ScopedLock _lock(lock); if (!eventQueue.empty()) eventQueue.pop_front(); } -void AgentImpl::newSession() +void AgentEngineImpl::newSession() { Mutex::ScopedLock _lock(lock); eventQueue.clear(); @@ -319,7 +319,7 @@ void AgentImpl::newSession() eventQueue.push_back(eventSetupComplete()); } -void AgentImpl::startProtocol() +void AgentEngineImpl::startProtocol() { Mutex::ScopedLock _lock(lock); char rawbuffer[512]; @@ -335,7 +335,7 @@ void AgentImpl::startProtocol() " reqAgent=" << requestedAgentBank); } -void AgentImpl::heartbeat() +void AgentEngineImpl::heartbeat() { Mutex::ScopedLock _lock(lock); Buffer buffer(outputBuffer, MA_BUFFER_SIZE); @@ -348,7 +348,7 @@ void AgentImpl::heartbeat() QPID_LOG(trace, "SENT HeartbeatIndication"); } -void AgentImpl::methodResponse(uint32_t sequence, uint32_t status, char* text, +void AgentEngineImpl::methodResponse(uint32_t sequence, uint32_t status, char* text, const Value& argMap) { Mutex::ScopedLock _lock(lock); @@ -381,7 +381,7 @@ void AgentImpl::methodResponse(uint32_t sequence, uint32_t status, char* text, QPID_LOG(trace, "SENT MethodResponse"); } -void AgentImpl::queryResponse(uint32_t sequence, Object& object, bool prop, bool stat) +void AgentEngineImpl::queryResponse(uint32_t sequence, Object& object, bool prop, bool stat) { Mutex::ScopedLock _lock(lock); map<uint32_t, AgentQueryContext::Ptr>::iterator iter = contextMap.find(sequence); @@ -403,7 +403,7 @@ void AgentImpl::queryResponse(uint32_t sequence, Object& object, bool prop, bool QPID_LOG(trace, "SENT ContentIndication"); } -void AgentImpl::queryComplete(uint32_t sequence) +void AgentEngineImpl::queryComplete(uint32_t sequence) { Mutex::ScopedLock _lock(lock); map<uint32_t, AgentQueryContext::Ptr>::iterator iter = contextMap.find(sequence); @@ -415,7 +415,7 @@ void AgentImpl::queryComplete(uint32_t sequence) sendCommandCompleteLH(context->exchange, context->key, context->sequence, 0, "OK"); } -void AgentImpl::registerClass(SchemaObjectClass* cls) +void AgentEngineImpl::registerClass(SchemaObjectClass* cls) { Mutex::ScopedLock _lock(lock); SchemaObjectClassImpl* impl = cls->impl; @@ -433,7 +433,7 @@ void AgentImpl::registerClass(SchemaObjectClass* cls) // TODO: Indicate this schema if connected. } -void AgentImpl::registerClass(SchemaEventClass* cls) +void AgentEngineImpl::registerClass(SchemaEventClass* cls) { Mutex::ScopedLock _lock(lock); SchemaEventClassImpl* impl = cls->impl; @@ -451,13 +451,13 @@ void AgentImpl::registerClass(SchemaEventClass* cls) // TODO: Indicate this schema if connected. } -const ObjectId* AgentImpl::addObject(Object&, uint64_t) +const ObjectId* AgentEngineImpl::addObject(Object&, uint64_t) { Mutex::ScopedLock _lock(lock); return 0; } -const ObjectId* AgentImpl::allocObjectId(uint64_t persistId) +const ObjectId* AgentEngineImpl::allocObjectId(uint64_t persistId) { Mutex::ScopedLock _lock(lock); uint16_t sequence = persistId ? 0 : bootSequence; @@ -467,17 +467,17 @@ const ObjectId* AgentImpl::allocObjectId(uint64_t persistId) return oid->envelope; } -const ObjectId* AgentImpl::allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi) +const ObjectId* AgentEngineImpl::allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi) { return allocObjectId(((uint64_t) persistIdHi) << 32 | (uint64_t) persistIdLo); } -void AgentImpl::raiseEvent(Event&) +void AgentEngineImpl::raiseEvent(Event&) { Mutex::ScopedLock _lock(lock); } -void AgentImpl::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) +void AgentEngineImpl::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) { buf.putOctet('A'); buf.putOctet('M'); @@ -486,7 +486,7 @@ void AgentImpl::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) buf.putLong (seq); } -bool AgentImpl::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) +bool AgentEngineImpl::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) { if (buf.getSize() < 8) return false; @@ -501,7 +501,7 @@ bool AgentImpl::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) return h1 == 'A' && h2 == 'M' && h3 == '3'; } -AgentEventImpl::Ptr AgentImpl::eventDeclareQueue(const string& name) +AgentEventImpl::Ptr AgentEngineImpl::eventDeclareQueue(const string& name) { AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::DECLARE_QUEUE)); event->name = name; @@ -509,8 +509,8 @@ AgentEventImpl::Ptr AgentImpl::eventDeclareQueue(const string& name) return event; } -AgentEventImpl::Ptr AgentImpl::eventBind(const string& exchange, const string& queue, - const string& key) +AgentEventImpl::Ptr AgentEngineImpl::eventBind(const string& exchange, const string& queue, + const string& key) { AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::BIND)); event->name = queue; @@ -520,14 +520,14 @@ AgentEventImpl::Ptr AgentImpl::eventBind(const string& exchange, const string& q return event; } -AgentEventImpl::Ptr AgentImpl::eventSetupComplete() +AgentEventImpl::Ptr AgentEngineImpl::eventSetupComplete() { AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::SETUP_COMPLETE)); return event; } -AgentEventImpl::Ptr AgentImpl::eventQuery(uint32_t num, const string& userId, const string& package, - const string& cls, boost::shared_ptr<ObjectId> oid) +AgentEventImpl::Ptr AgentEngineImpl::eventQuery(uint32_t num, const string& userId, const string& package, + const string& cls, boost::shared_ptr<ObjectId> oid) { AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::GET_QUERY)); event->sequence = num; @@ -538,9 +538,9 @@ AgentEventImpl::Ptr AgentImpl::eventQuery(uint32_t num, const string& userId, co return event; } -AgentEventImpl::Ptr AgentImpl::eventMethod(uint32_t num, const string& userId, const string& method, - boost::shared_ptr<ObjectId> oid, boost::shared_ptr<Value> argMap, - SchemaObjectClass* objectClass) +AgentEventImpl::Ptr AgentEngineImpl::eventMethod(uint32_t num, const string& userId, const string& method, + boost::shared_ptr<ObjectId> oid, boost::shared_ptr<Value> argMap, + SchemaObjectClass* objectClass) { AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::METHOD_CALL)); event->sequence = num; @@ -552,7 +552,7 @@ AgentEventImpl::Ptr AgentImpl::eventMethod(uint32_t num, const string& userId, c return event; } -void AgentImpl::sendBufferLH(Buffer& buf, const string& destination, const string& routingKey) +void AgentEngineImpl::sendBufferLH(Buffer& buf, const string& destination, const string& routingKey) { uint32_t length = buf.getPosition(); MessageImpl::Ptr message(new MessageImpl); @@ -567,7 +567,7 @@ void AgentImpl::sendBufferLH(Buffer& buf, const string& destination, const strin xmtQueue.push_back(message); } -void AgentImpl::sendPackageIndicationLH(const string& packageName) +void AgentEngineImpl::sendPackageIndicationLH(const string& packageName) { Buffer buffer(outputBuffer, MA_BUFFER_SIZE); encodeHeader(buffer, 'p'); @@ -576,7 +576,7 @@ void AgentImpl::sendPackageIndicationLH(const string& packageName) QPID_LOG(trace, "SENT PackageIndication: package_name=" << packageName); } -void AgentImpl::sendClassIndicationLH(ClassKind kind, const string& packageName, const SchemaClassKey& key) +void AgentEngineImpl::sendClassIndicationLH(ClassKind kind, const string& packageName, const SchemaClassKey& key) { Buffer buffer(outputBuffer, MA_BUFFER_SIZE); encodeHeader(buffer, 'q'); @@ -588,8 +588,8 @@ void AgentImpl::sendClassIndicationLH(ClassKind kind, const string& packageName, QPID_LOG(trace, "SENT ClassIndication: package_name=" << packageName << " class_name=" << key.name); } -void AgentImpl::sendCommandCompleteLH(const string& exchange, const string& replyToKey, - uint32_t sequence, uint32_t code, const string& text) +void AgentEngineImpl::sendCommandCompleteLH(const string& exchange, const string& replyToKey, + uint32_t sequence, uint32_t code, const string& text) { Buffer buffer(outputBuffer, MA_BUFFER_SIZE); encodeHeader(buffer, 'z', sequence); @@ -599,7 +599,7 @@ void AgentImpl::sendCommandCompleteLH(const string& exchange, const string& repl QPID_LOG(trace, "SENT CommandComplete: seq=" << sequence << " code=" << code << " text=" << text); } -void AgentImpl::sendMethodErrorLH(uint32_t sequence, const string& key, uint32_t code, const string& text) +void AgentEngineImpl::sendMethodErrorLH(uint32_t sequence, const string& key, uint32_t code, const string& text) { Buffer buffer(outputBuffer, MA_BUFFER_SIZE); encodeHeader(buffer, 'm', sequence); @@ -625,7 +625,7 @@ void AgentImpl::sendMethodErrorLH(uint32_t sequence, const string& key, uint32_t QPID_LOG(trace, "SENT MethodResponse: errorCode=" << code << " text=" << fulltext); } -void AgentImpl::handleAttachResponse(Buffer& inBuffer) +void AgentEngineImpl::handleAttachResponse(Buffer& inBuffer) { Mutex::ScopedLock _lock(lock); @@ -672,18 +672,18 @@ void AgentImpl::handleAttachResponse(Buffer& inBuffer) } } -void AgentImpl::handlePackageRequest(Buffer&) +void AgentEngineImpl::handlePackageRequest(Buffer&) { Mutex::ScopedLock _lock(lock); } -void AgentImpl::handleClassQuery(Buffer&) +void AgentEngineImpl::handleClassQuery(Buffer&) { Mutex::ScopedLock _lock(lock); } -void AgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, - const string& replyExchange, const string& replyKey) +void AgentEngineImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, + const string& replyExchange, const string& replyKey) { Mutex::ScopedLock _lock(lock); string rExchange(replyExchange); @@ -731,7 +731,7 @@ void AgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, sendCommandCompleteLH(rExchange, rKey, sequence, 1, "class not found"); } -void AgentImpl::handleGetQuery(Buffer& inBuffer, uint32_t sequence, const string& replyTo, const string& userId) +void AgentEngineImpl::handleGetQuery(Buffer& inBuffer, uint32_t sequence, const string& replyTo, const string& userId) { Mutex::ScopedLock _lock(lock); FieldTable ft; @@ -783,7 +783,7 @@ void AgentImpl::handleGetQuery(Buffer& inBuffer, uint32_t sequence, const string eventQueue.push_back(eventQuery(contextNum, userId, pname, cname, oid)); } -void AgentImpl::handleMethodRequest(Buffer& buffer, uint32_t sequence, const string& replyTo, const string& userId) +void AgentEngineImpl::handleMethodRequest(Buffer& buffer, uint32_t sequence, const string& replyTo, const string& userId) { Mutex::ScopedLock _lock(lock); string pname; @@ -842,7 +842,7 @@ void AgentImpl::handleMethodRequest(Buffer& buffer, uint32_t sequence, const str eventQueue.push_back(eventMethod(contextNum, userId, method, oid, argMap, schema->envelope)); } -void AgentImpl::handleConsoleAddedIndication() +void AgentEngineImpl::handleConsoleAddedIndication() { Mutex::ScopedLock _lock(lock); } @@ -851,107 +851,107 @@ void AgentImpl::handleConsoleAddedIndication() // Wrappers //================================================================== -Agent::Agent(char* label, bool internalStore) +AgentEngine::AgentEngine(char* label, bool internalStore) { - impl = new AgentImpl(label, internalStore); + impl = new AgentEngineImpl(label, internalStore); } -Agent::~Agent() +AgentEngine::~AgentEngine() { delete impl; } -void Agent::setStoreDir(char* path) +void AgentEngine::setStoreDir(const char* path) { impl->setStoreDir(path); } -void Agent::setTransferDir(char* path) +void AgentEngine::setTransferDir(const char* path) { impl->setTransferDir(path); } -void Agent::handleRcvMessage(Message& message) +void AgentEngine::handleRcvMessage(Message& message) { impl->handleRcvMessage(message); } -bool Agent::getXmtMessage(Message& item) +bool AgentEngine::getXmtMessage(Message& item) { return impl->getXmtMessage(item); } -void Agent::popXmt() +void AgentEngine::popXmt() { impl->popXmt(); } -bool Agent::getEvent(AgentEvent& event) +bool AgentEngine::getEvent(AgentEvent& event) { return impl->getEvent(event); } -void Agent::popEvent() +void AgentEngine::popEvent() { impl->popEvent(); } -void Agent::newSession() +void AgentEngine::newSession() { impl->newSession(); } -void Agent::startProtocol() +void AgentEngine::startProtocol() { impl->startProtocol(); } -void Agent::heartbeat() +void AgentEngine::heartbeat() { impl->heartbeat(); } -void Agent::methodResponse(uint32_t sequence, uint32_t status, char* text, const Value& arguments) +void AgentEngine::methodResponse(uint32_t sequence, uint32_t status, char* text, const Value& arguments) { impl->methodResponse(sequence, status, text, arguments); } -void Agent::queryResponse(uint32_t sequence, Object& object, bool prop, bool stat) +void AgentEngine::queryResponse(uint32_t sequence, Object& object, bool prop, bool stat) { impl->queryResponse(sequence, object, prop, stat); } -void Agent::queryComplete(uint32_t sequence) +void AgentEngine::queryComplete(uint32_t sequence) { impl->queryComplete(sequence); } -void Agent::registerClass(SchemaObjectClass* cls) +void AgentEngine::registerClass(SchemaObjectClass* cls) { impl->registerClass(cls); } -void Agent::registerClass(SchemaEventClass* cls) +void AgentEngine::registerClass(SchemaEventClass* cls) { impl->registerClass(cls); } -const ObjectId* Agent::addObject(Object& obj, uint64_t persistId) +const ObjectId* AgentEngine::addObject(Object& obj, uint64_t persistId) { return impl->addObject(obj, persistId); } -const ObjectId* Agent::allocObjectId(uint64_t persistId) +const ObjectId* AgentEngine::allocObjectId(uint64_t persistId) { return impl->allocObjectId(persistId); } -const ObjectId* Agent::allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi) +const ObjectId* AgentEngine::allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi) { return impl->allocObjectId(persistIdLo, persistIdHi); } -void Agent::raiseEvent(Event& event) +void AgentEngine::raiseEvent(Event& event) { impl->raiseEvent(event); } diff --git a/qpid/cpp/src/qmf/Agent.h b/qpid/cpp/src/qmf/AgentEngine.h index d8f784e9d8..d18a104e96 100644 --- a/qpid/cpp/src/qmf/Agent.h +++ b/qpid/cpp/src/qmf/AgentEngine.h @@ -1,5 +1,5 @@ -#ifndef _QmfAgent_ -#define _QmfAgent_ +#ifndef _QmfAgentEngine_ +#define _QmfAgentEngine_ /* * Licensed to the Apache Software Foundation (ASF) under one @@ -64,15 +64,15 @@ namespace qmf { SchemaObjectClass* objectClass; // (METHOD_CALL) }; - class AgentImpl; + class AgentEngineImpl; /** - * Agent - Protocol engine for the QMF agent + * AgentEngine - Protocol engine for the QMF agent */ - class Agent { + class AgentEngine { public: - Agent(char* label, bool internalStore=true); - ~Agent(); + AgentEngine(char* label, bool internalStore=true); + ~AgentEngine(); /** * Configure the directory path for storing persistent data. @@ -80,7 +80,7 @@ namespace qmf { * created, written, and read. If NULL, no persistent storage will be * attempted. */ - void setStoreDir(char* path); + void setStoreDir(const char* path); /** * Configure the directory path for files transferred over QMF. @@ -88,7 +88,7 @@ namespace qmf { * created, deleted, written, and read. If NULL, file transfers shall not * be permitted. */ - void setTransferDir(char* path); + void setTransferDir(const char* path); /** * Pass messages received from the AMQP session to the Agent engine. @@ -182,11 +182,12 @@ namespace qmf { *@return The objectId of the managed object. */ const ObjectId* addObject(Object& obj, uint64_t persistId); + const ObjectId* addObject(Object& obj, uint32_t persistIdLo, uint32_t persistIdHi); /** - * Allocate an objecc-id for an object that will be managed by the application. + * Allocate an object-id for an object that will be managed by the application. *@param persistId A unique non-zero value if the object-id is to be persistent. - @return The objectId structure for the allocated ID. + *@return The objectId structure for the allocated ID. */ const ObjectId* allocObjectId(uint64_t persistId); const ObjectId* allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi); @@ -198,7 +199,7 @@ namespace qmf { void raiseEvent(Event& event); private: - AgentImpl* impl; + AgentEngineImpl* impl; }; } diff --git a/qpid/cpp/src/qmf/ConnectionSettingsImpl.cpp b/qpid/cpp/src/qmf/ConnectionSettingsImpl.cpp new file mode 100644 index 0000000000..034ab18395 --- /dev/null +++ b/qpid/cpp/src/qmf/ConnectionSettingsImpl.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 "qmf/ConnectionSettingsImpl.h" +#include "qmf/Typecode.h" + +using namespace std; +using namespace qmf; +using namespace qpid; + +const string attrProtocol("protocol"); +const string attrHost("host"); +const string attrPort("port"); +const string attrVirtualhost("virtualhost"); +const string attrUsername("username"); +const string attrPassword("password"); +const string attrMechanism("mechanism"); +const string attrLocale("locale"); +const string attrHeartbeat("heartbeat"); +const string attrMaxChannels("maxChannels"); +const string attrMaxFrameSize("maxFrameSize"); +const string attrBounds("bounds"); +const string attrTcpNoDelay("tcpNoDelay"); +const string attrService("service"); +const string attrMinSsf("minSsf"); +const string attrMaxSsf("maxSsf"); +const string attrRetryDelayMin("retryDelayMin"); +const string attrRetryDelayMax("retryDelayMax"); +const string attrRetryDelayFactor("retryDelayFactor"); + +ConnectionSettingsImpl::ConnectionSettingsImpl(ConnectionSettings* e) : + envelope(e), retryDelayMin(1), retryDelayMax(64), retryDelayFactor(2) +{ +} + +ConnectionSettingsImpl::ConnectionSettingsImpl(ConnectionSettings* e, const string& /*url*/) : + envelope(e), retryDelayMin(1), retryDelayMax(64), retryDelayFactor(2) +{ + // TODO: Parse the URL +} + +void ConnectionSettingsImpl::setAttr(const string& key, const Value& value) +{ + if (key == attrProtocol) clientSettings.protocol = value.asString(); + else if (key == attrHost) clientSettings.host = value.asString(); + else if (key == attrPort) clientSettings.port = value.asUint(); + else if (key == attrVirtualhost) clientSettings.virtualhost = value.asString(); + else if (key == attrUsername) clientSettings.username = value.asString(); + else if (key == attrPassword) clientSettings.password = value.asString(); + else if (key == attrMechanism) clientSettings.mechanism = value.asString(); + else if (key == attrLocale) clientSettings.locale = value.asString(); + else if (key == attrHeartbeat) clientSettings.heartbeat = value.asUint(); + else if (key == attrMaxChannels) clientSettings.maxChannels = value.asUint(); + else if (key == attrMaxFrameSize) clientSettings.maxFrameSize = value.asUint(); + else if (key == attrBounds) clientSettings.bounds = value.asUint(); + else if (key == attrTcpNoDelay) clientSettings.tcpNoDelay = value.asBool(); + else if (key == attrService) clientSettings.service = value.asString(); + else if (key == attrMinSsf) clientSettings.minSsf = value.asUint(); + else if (key == attrMaxSsf) clientSettings.maxSsf = value.asUint(); + + else if (key == attrRetryDelayMin) retryDelayMin = value.asUint(); + else if (key == attrRetryDelayMax) retryDelayMax = value.asUint(); + else if (key == attrRetryDelayFactor) retryDelayFactor = value.asUint(); +} + +Value ConnectionSettingsImpl::getAttr(const string& key) const +{ + Value strval(TYPE_LSTR); + Value intval(TYPE_UINT32); + Value boolval(TYPE_BOOL); + + if (key == attrProtocol) { + strval.setString(clientSettings.protocol.c_str()); + return strval; + } + + if (key == attrHost) { + strval.setString(clientSettings.host.c_str()); + return strval; + } + + if (key == attrPort) { + intval.setUint(clientSettings.port); + return intval; + } + + if (key == attrVirtualhost) { + strval.setString(clientSettings.virtualhost.c_str()); + return strval; + } + + if (key == attrUsername) { + strval.setString(clientSettings.username.c_str()); + return strval; + } + + if (key == attrPassword) { + strval.setString(clientSettings.password.c_str()); + return strval; + } + + if (key == attrMechanism) { + strval.setString(clientSettings.mechanism.c_str()); + return strval; + } + + if (key == attrLocale) { + strval.setString(clientSettings.locale.c_str()); + return strval; + } + + if (key == attrHeartbeat) { + intval.setUint(clientSettings.heartbeat); + return intval; + } + + if (key == attrMaxChannels) { + intval.setUint(clientSettings.maxChannels); + return intval; + } + + if (key == attrMaxFrameSize) { + intval.setUint(clientSettings.maxFrameSize); + return intval; + } + + if (key == attrBounds) { + intval.setUint(clientSettings.bounds); + return intval; + } + + if (key == attrTcpNoDelay) { + boolval.setBool(clientSettings.tcpNoDelay); + return boolval; + } + + if (key == attrService) { + strval.setString(clientSettings.service.c_str()); + return strval; + } + + if (key == attrMinSsf) { + intval.setUint(clientSettings.minSsf); + return intval; + } + + if (key == attrMaxSsf) { + intval.setUint(clientSettings.maxSsf); + return intval; + } + + if (key == attrRetryDelayMin) { + intval.setUint(retryDelayMin); + return intval; + } + + if (key == attrRetryDelayMax) { + intval.setUint(retryDelayMax); + return intval; + } + + if (key == attrRetryDelayFactor) { + intval.setUint(retryDelayFactor); + return intval; + } + + return strval; +} + +const string& ConnectionSettingsImpl::getAttrString() const +{ + // TODO: build and return attribute string + return attrString; +} + +void ConnectionSettingsImpl::transportTcp(uint16_t port) +{ + clientSettings.protocol = "tcp"; + clientSettings.port = port; +} + +void ConnectionSettingsImpl::transportSsl(uint16_t port) +{ + clientSettings.protocol = "ssl"; + clientSettings.port = port; +} + +void ConnectionSettingsImpl::transportRdma(uint16_t port) +{ + clientSettings.protocol = "rdma"; + clientSettings.port = port; +} + +void ConnectionSettingsImpl::authAnonymous(const string& username) +{ + clientSettings.mechanism = "ANONYMOUS"; + clientSettings.username = username; +} + +void ConnectionSettingsImpl::authPlain(const string& username, const string& password) +{ + clientSettings.mechanism = "PLAIN"; + clientSettings.username = username; + clientSettings.password = password; +} + +void ConnectionSettingsImpl::authGssapi(const string& serviceName, uint32_t minSsf, uint32_t maxSsf) +{ + clientSettings.mechanism = "GSSAPI"; + clientSettings.service = serviceName; + clientSettings.minSsf = minSsf; + clientSettings.maxSsf = maxSsf; +} + +void ConnectionSettingsImpl::setRetry(int delayMin, int delayMax, int delayFactor) +{ + retryDelayMin = delayMin; + retryDelayMax = delayMax; + retryDelayFactor = delayFactor; +} + +const client::ConnectionSettings& ConnectionSettingsImpl::getClientSettings() const +{ + return clientSettings; +} + +void ConnectionSettingsImpl::getRetrySettings(int* min, int* max, int* factor) const +{ + *min = retryDelayMin; + *max = retryDelayMax; + *factor = retryDelayFactor; +} + +//================================================================== +// Wrappers +//================================================================== + +ConnectionSettings::ConnectionSettings(const ConnectionSettings& from) +{ + impl = new ConnectionSettingsImpl(*from.impl); +} + +ConnectionSettings::ConnectionSettings() +{ + impl = new ConnectionSettingsImpl(this); +} + +ConnectionSettings::ConnectionSettings(const char* url) +{ + impl = new ConnectionSettingsImpl(this, url); +} + +ConnectionSettings::~ConnectionSettings() +{ + delete impl; +} + +void ConnectionSettings::setAttr(const char* key, const Value& value) +{ + impl->setAttr(key, value); +} + +Value ConnectionSettings::getAttr(const char* key) const +{ + return impl->getAttr(key); +} + +const char* ConnectionSettings::getAttrString() const +{ + return impl->getAttrString().c_str(); +} + +void ConnectionSettings::transportTcp(uint16_t port) +{ + impl->transportTcp(port); +} + +void ConnectionSettings::transportSsl(uint16_t port) +{ + impl->transportSsl(port); +} + +void ConnectionSettings::transportRdma(uint16_t port) +{ + impl->transportRdma(port); +} + +void ConnectionSettings::authAnonymous(const char* username) +{ + impl->authAnonymous(username); +} + +void ConnectionSettings::authPlain(const char* username, const char* password) +{ + impl->authPlain(username, password); +} + +void ConnectionSettings::authGssapi(const char* serviceName, uint32_t minSsf, uint32_t maxSsf) +{ + impl->authGssapi(serviceName, minSsf, maxSsf); +} + +void ConnectionSettings::setRetry(int delayMin, int delayMax, int delayFactor) +{ + impl->setRetry(delayMin, delayMax, delayFactor); +} + diff --git a/qpid/cpp/src/qmf/ConnectionSettingsImpl.h b/qpid/cpp/src/qmf/ConnectionSettingsImpl.h new file mode 100644 index 0000000000..a177233cf3 --- /dev/null +++ b/qpid/cpp/src/qmf/ConnectionSettingsImpl.h @@ -0,0 +1,60 @@ +#ifndef _QmfConnectionSettingsImpl_ +#define _QmfConnectionSettingsImpl_ + +/* + * 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 "qmf/ConnectionSettings.h" +#include "qmf/Value.h" +#include "qpid/client/ConnectionSettings.h" +#include <string> +#include <map> + +namespace qmf { + + class ConnectionSettingsImpl { + ConnectionSettings* envelope; + qpid::client::ConnectionSettings clientSettings; + mutable std::string attrString; + int retryDelayMin; + int retryDelayMax; + int retryDelayFactor; + + public: + ConnectionSettingsImpl(ConnectionSettings* e); + ConnectionSettingsImpl(ConnectionSettings* e, const std::string& url); + ~ConnectionSettingsImpl() {} + void setAttr(const std::string& key, const Value& value); + Value getAttr(const std::string& key) const; + const std::string& getAttrString() const; + void transportTcp(uint16_t port); + void transportSsl(uint16_t port); + void transportRdma(uint16_t port); + void authAnonymous(const std::string& username); + void authPlain(const std::string& username, const std::string& password); + void authGssapi(const std::string& serviceName, uint32_t minSsf, uint32_t maxSsf); + void setRetry(int delayMin, int delayMax, int delayFactor); + + const qpid::client::ConnectionSettings& getClientSettings() const; + void getRetrySettings(int* delayMin, int* delayMax, int* delayFactor) const; + }; + +} + +#endif diff --git a/qpid/cpp/src/qmf/Console.h b/qpid/cpp/src/qmf/ConsoleEngine.h index de7949e1de..823e281b14 100644 --- a/qpid/cpp/src/qmf/Console.h +++ b/qpid/cpp/src/qmf/ConsoleEngine.h @@ -1,5 +1,5 @@ -#ifndef _QmfConsole_ -#define _QmfConsole_ +#ifndef _QmfConsoleEngine_ +#define _QmfConsoleEngine_ /* * Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,6 @@ */ #include <qmf/ManagedConnection.h> -#include <qmf/Agent.h> #include <qmf/Broker.h> #include <qmf/Package.h> #include <qmf/SchemaClassTable.h> @@ -50,10 +49,10 @@ namespace qmf { getTimeout(20) {} }; - class Console { + class ConsoleEngine { public: - Console(ConsoleHandler* handler = 0, ConsoleSettings settings = ConsoleSettings()); - ~Console(); + ConsoleEngine(ConsoleHandler* handler = 0, ConsoleSettings settings = ConsoleSettings()); + ~ConsoleEngine(); Broker* addConnection(ManagedConnection& connection); void delConnection(Broker* broker); @@ -66,6 +65,7 @@ namespace qmf { void bindClass(const SchemaClass& otype); void bindClass(const std::string& packageName, const std::string& className); + /* void getAgents(std::set<Agent>& agents, Broker* = 0); void getObjects(std::vector<Object>& objects, const std::string& typeName, const std::string& packageName = "", @@ -75,6 +75,7 @@ namespace qmf { const std::map<std::string, std::string>& query, Broker* broker = 0, Agent* agent = 0); + */ }; } diff --git a/qpid/cpp/src/qmf/Object.h b/qpid/cpp/src/qmf/Object.h index 8caab8d6dc..eb92cbbe45 100644 --- a/qpid/cpp/src/qmf/Object.h +++ b/qpid/cpp/src/qmf/Object.h @@ -31,7 +31,7 @@ namespace qmf { public: Object(const SchemaObjectClass* type); Object(ObjectImpl* impl); - ~Object(); + virtual ~Object(); void destroy(); const ObjectId* getObjectId() const; diff --git a/qpid/cpp/src/qmf/ObjectId.h b/qpid/cpp/src/qmf/ObjectId.h index 1ceae20bd8..ffd1b6978b 100644 --- a/qpid/cpp/src/qmf/ObjectId.h +++ b/qpid/cpp/src/qmf/ObjectId.h @@ -39,7 +39,6 @@ namespace qmf { bool isDurable() const; bool operator==(const ObjectId& other) const; - bool operator!=(const ObjectId& other) const; bool operator<(const ObjectId& other) const; bool operator>(const ObjectId& other) const; bool operator<=(const ObjectId& other) const; diff --git a/qpid/cpp/src/qmf/ObjectIdImpl.cpp b/qpid/cpp/src/qmf/ObjectIdImpl.cpp index efa8e7119b..75661fdb47 100644 --- a/qpid/cpp/src/qmf/ObjectIdImpl.cpp +++ b/qpid/cpp/src/qmf/ObjectIdImpl.cpp @@ -166,11 +166,6 @@ bool ObjectId::operator==(const ObjectId& other) const return *impl == *other.impl; } -bool ObjectId::operator!=(const ObjectId& other) const -{ - return !(*impl == *other.impl); -} - bool ObjectId::operator<(const ObjectId& other) const { return *impl < *other.impl; diff --git a/qpid/cpp/src/qmf/ResilientConnection.cpp b/qpid/cpp/src/qmf/ResilientConnection.cpp index 610306f896..3531e104b0 100644 --- a/qpid/cpp/src/qmf/ResilientConnection.cpp +++ b/qpid/cpp/src/qmf/ResilientConnection.cpp @@ -19,6 +19,8 @@ #include "qmf/ResilientConnection.h" #include "qmf/MessageImpl.h" +#include "qmf/ConnectionSettingsImpl.h" +#include <qpid/client/Connection.h> #include <qpid/client/Session.h> #include <qpid/client/MessageListener.h> #include <qpid/client/SubscriptionManager.h> @@ -77,8 +79,7 @@ namespace qmf { class ResilientConnectionImpl : public qpid::sys::Runnable { public: - ResilientConnectionImpl(ConnectionSettings& settings, - int dmin, int dmax, int dfactor); + ResilientConnectionImpl(ConnectionSettings& settings); ~ResilientConnectionImpl(); bool isConnected() const; @@ -174,12 +175,11 @@ void RCSession::received(qpid::client::Message& msg) connImpl.EnqueueEvent(ResilientConnectionEvent::RECV, userContext, qmsg); } -ResilientConnectionImpl::ResilientConnectionImpl(ConnectionSettings& _settings, - int dmin, int dmax, int dfactor) : - notifyFd(-1), connected(false), shutdown(false), settings(_settings), - delayMin(dmin), delayMax(dmax), delayFactor(dfactor), connThread(*this) +ResilientConnectionImpl::ResilientConnectionImpl(ConnectionSettings& _settings) : + notifyFd(-1), connected(false), shutdown(false), settings(_settings), connThread(*this) { connection.registerFailureCallback(boost::bind(&ResilientConnectionImpl::failure, this)); + settings.impl->getRetrySettings(&delayMin, &delayMax, &delayFactor); } ResilientConnectionImpl::~ResilientConnectionImpl() @@ -318,7 +318,7 @@ void ResilientConnectionImpl::run() while (true) { try { - connection.open(settings); + connection.open(settings.impl->getClientSettings()); { Mutex::ScopedLock _lock(lock); connected = true; @@ -396,10 +396,9 @@ void ResilientConnectionImpl::EnqueueEvent(ResilientConnectionEvent::EventKind k // Wrappers //================================================================== -ResilientConnection::ResilientConnection(ConnectionSettings& settings, - int delayMin, int delayMax, int delayFactor) +ResilientConnection::ResilientConnection(ConnectionSettings& settings) { - impl = new ResilientConnectionImpl(settings, delayMin, delayMax, delayFactor); + impl = new ResilientConnectionImpl(settings); } ResilientConnection::~ResilientConnection() diff --git a/qpid/cpp/src/qmf/ResilientConnection.h b/qpid/cpp/src/qmf/ResilientConnection.h index bb565e27ae..6e05541253 100644 --- a/qpid/cpp/src/qmf/ResilientConnection.h +++ b/qpid/cpp/src/qmf/ResilientConnection.h @@ -21,8 +21,7 @@ */ #include <qmf/Message.h> -#include <qpid/client/Connection.h> -#include <qpid/client/ConnectionSettings.h> +#include <qmf/ConnectionSettings.h> #include <string> namespace qmf { @@ -68,10 +67,7 @@ namespace qmf { *@param delayMax Maximum delay (in seconds) between retries. *@param delayFactor Factor to multiply retry delay by after each failure. */ - ResilientConnection(qpid::client::ConnectionSettings& settings, - int delayMin = 1, - int delayMax = 128, - int delayFactor = 2); + ResilientConnection(ConnectionSettings& settings); ~ResilientConnection(); /** diff --git a/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp b/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp index 7f69c73ef7..4a6590fb5f 100644 --- a/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp +++ b/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp @@ -78,7 +78,7 @@ ManagementAgent* ManagementAgent::Singleton::getInstance() const string ManagementAgentImpl::storeMagicNumber("MA02"); ManagementAgentImpl::ManagementAgentImpl() : - extThread(false), + interval(10), extThread(false), initialized(false), connected(false), lastFailure("never connected"), clientWasAdded(true), requestedBrokerBank(0), requestedAgentBank(0), assignedBrokerBank(0), assignedAgentBank(0), bootSequence(0), diff --git a/qpid/cpp/src/qpid/broker/Daemon.cpp b/qpid/cpp/src/qpid/broker/Daemon.cpp index aa8dd8855b..21848e23de 100644 --- a/qpid/cpp/src/qpid/broker/Daemon.cpp +++ b/qpid/cpp/src/qpid/broker/Daemon.cpp @@ -85,7 +85,7 @@ void Daemon::fork() child(); } catch (const exception& e) { - QPID_LOG(critical, "Daemon startup failed: " << e.what()); + QPID_LOG(critical, "Unexpected error: " << e.what()); uint16_t port = 0; int unused_ret; //Supress warning about ignoring return value. unused_ret = write(pipeFds[1], &port, sizeof(uint16_t)); diff --git a/qpid/cpp/src/qpid/broker/Exchange.cpp b/qpid/cpp/src/qpid/broker/Exchange.cpp index 17fc0e23ca..b1fc1295f3 100644 --- a/qpid/cpp/src/qpid/broker/Exchange.cpp +++ b/qpid/cpp/src/qpid/broker/Exchange.cpp @@ -91,7 +91,9 @@ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtExchange = new _qmf::Exchange (agent, this, parent, _name, durable); + mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange->set_durable(durable); + mgmtExchange->set_autoDelete(false); agent->addObject (mgmtExchange); } } @@ -109,7 +111,9 @@ Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::Fiel ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtExchange = new _qmf::Exchange (agent, this, parent, _name, durable); + mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange->set_durable(durable); + mgmtExchange->set_autoDelete(false); mgmtExchange->set_arguments(args); if (!durable) { if (name.empty()) { @@ -139,6 +143,17 @@ Exchange::~Exchange () mgmtExchange->resourceDestroy (); } +void Exchange::setAlternate(Exchange::shared_ptr _alternate) +{ + alternate = _alternate; + if (mgmtExchange != 0) { + if (alternate.get() != 0) + mgmtExchange->set_altExchange(alternate->GetManagementObject()->getObjectId()); + else + mgmtExchange->clr_altExchange(); + } +} + void Exchange::setPersistenceId(uint64_t id) const { if (mgmtExchange != 0 && persistenceId == 0) diff --git a/qpid/cpp/src/qpid/broker/Exchange.h b/qpid/cpp/src/qpid/broker/Exchange.h index ca8525d55e..c1e878200f 100644 --- a/qpid/cpp/src/qpid/broker/Exchange.h +++ b/qpid/cpp/src/qpid/broker/Exchange.h @@ -132,7 +132,7 @@ public: qpid::framing::FieldTable& getArgs() { return args; } Exchange::shared_ptr getAlternate() { return alternate; } - void setAlternate(Exchange::shared_ptr _alternate) { alternate = _alternate; } + void setAlternate(Exchange::shared_ptr _alternate); void incAlternateUsers() { alternateUsers++; } void decAlternateUsers() { alternateUsers--; } bool inUseAsAlternate() { return alternateUsers > 0; } diff --git a/qpid/cpp/src/qpid/client/Demux.h b/qpid/cpp/src/qpid/client/Demux.h index 7304e799bb..31dc3f9c06 100644 --- a/qpid/cpp/src/qpid/client/Demux.h +++ b/qpid/cpp/src/qpid/client/Demux.h @@ -25,6 +25,7 @@ #include "qpid/framing/FrameSet.h" #include "qpid/sys/Mutex.h" #include "qpid/sys/BlockingQueue.h" +#include "qpid/client/ClientImportExport.h" #ifndef _Demux_ #define _Demux_ @@ -49,17 +50,17 @@ public: typedef sys::BlockingQueue<framing::FrameSet::shared_ptr> Queue; typedef boost::shared_ptr<Queue> QueuePtr; - Demux(); - ~Demux(); + QPID_CLIENT_EXTERN Demux(); + QPID_CLIENT_EXTERN ~Demux(); - void handle(framing::FrameSet::shared_ptr); - void close(const sys::ExceptionHolder& ex); - void open(); - - QueuePtr add(const std::string& name, Condition); - void remove(const std::string& name); - QueuePtr get(const std::string& name); - QueuePtr getDefault(); + QPID_CLIENT_EXTERN void handle(framing::FrameSet::shared_ptr); + QPID_CLIENT_EXTERN void close(const sys::ExceptionHolder& ex); + QPID_CLIENT_EXTERN void open(); + + QPID_CLIENT_EXTERN QueuePtr add(const std::string& name, Condition); + QPID_CLIENT_EXTERN void remove(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr get(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr getDefault(); private: struct Record diff --git a/qpid/cpp/src/qpid/client/Results.cpp b/qpid/cpp/src/qpid/client/Results.cpp index acb2684727..0de3e8bd04 100644 --- a/qpid/cpp/src/qpid/client/Results.cpp +++ b/qpid/cpp/src/qpid/client/Results.cpp @@ -24,7 +24,6 @@ #include "qpid/framing/SequenceSet.h" using namespace qpid::framing; -using namespace boost; namespace qpid { namespace client { diff --git a/qpid/cpp/src/qpid/client/SessionImpl.cpp b/qpid/cpp/src/qpid/client/SessionImpl.cpp index 43b62ec6dc..8ead44a172 100644 --- a/qpid/cpp/src/qpid/client/SessionImpl.cpp +++ b/qpid/cpp/src/qpid/client/SessionImpl.cpp @@ -202,6 +202,16 @@ bool SessionImpl::isCompleteUpTo(const SequenceNumber& id) return f.result; } +framing::SequenceNumber SessionImpl::getCompleteUpTo() +{ + SequenceNumber firstIncomplete; + { + Lock l(state); + firstIncomplete = incompleteIn.front(); + } + return --firstIncomplete; +} + struct MarkCompleted { const SequenceNumber& id; @@ -319,7 +329,7 @@ struct MethodContentAdaptor : MethodContent } -Future SessionImpl::send(const AMQBody& command, const FrameSet& content) { +Future SessionImpl::send(const AMQBody& command, const FrameSet& content, bool reframe) { Acquire a(sendLock); SequenceNumber id = nextOut++; { @@ -337,7 +347,7 @@ Future SessionImpl::send(const AMQBody& command, const FrameSet& content) { frame.setEof(false); handleOut(frame); - if (content.isComplete()) { + if (reframe) { MethodContentAdaptor c(content); sendContent(c); } else { diff --git a/qpid/cpp/src/qpid/client/SessionImpl.h b/qpid/cpp/src/qpid/client/SessionImpl.h index cae1148e9f..49d268c44d 100644 --- a/qpid/cpp/src/qpid/client/SessionImpl.h +++ b/qpid/cpp/src/qpid/client/SessionImpl.h @@ -25,6 +25,7 @@ #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" @@ -86,7 +87,15 @@ public: Future send(const framing::AMQBody& command); Future send(const framing::AMQBody& command, const framing::MethodContent& content); - Future send(const framing::AMQBody& command, const framing::FrameSet& 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(); @@ -94,6 +103,7 @@ public: 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(); 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..6ff9c2397a --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -0,0 +1,464 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Filter.h" +#include "qpid/messaging/Message.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/ReplyTo.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::Exception; +using qpid::messaging::Address; +using qpid::messaging::Filter; +using qpid::messaging::Variant; +using qpid::framing::FieldTable; +using qpid::framing::ReplyTo; +using namespace qpid::framing::message; + + +namespace{ +const Variant EMPTY_VARIANT; +const FieldTable EMPTY_FIELD_TABLE; +const std::string EMPTY_STRING; + +//option names +const std::string BROWSE("browse"); +const std::string EXCLUSIVE("exclusive"); +const std::string MODE("mode"); +const std::string NAME("name"); +const std::string UNACKNOWLEDGED("unacknowledged"); + +const std::string QUEUE_ADDRESS("queue"); +const std::string TOPIC_ADDRESS("topic"); +const std::string TOPIC_ADDRESS_AND_SUBJECT("topic+"); +const std::string DIVIDER("/"); + +const std::string SIMPLE_SUBSCRIPTION("simple"); +const std::string RELIABLE_SUBSCRIPTION("reliable"); +const std::string DURABLE_SUBSCRIPTION("durable"); +} + +class QueueSource : public MessageSource +{ + public: + QueueSource(const std::string& name, AcceptMode=ACCEPT_MODE_EXPLICIT, AcquireMode=ACQUIRE_MODE_PRE_ACQUIRED, + bool exclusive = false, const FieldTable& options = EMPTY_FIELD_TABLE); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + const std::string name; + const AcceptMode acceptMode; + const AcquireMode acquireMode; + const bool exclusive; + const FieldTable options; +}; + +class Subscription : public MessageSource +{ + public: + enum SubscriptionMode {SIMPLE, RELIABLE, DURABLE}; + + Subscription(const std::string& name, SubscriptionMode mode = SIMPLE, + const FieldTable& queueOptions = EMPTY_FIELD_TABLE, const FieldTable& subscriptionOptions = EMPTY_FIELD_TABLE); + void add(const std::string& exchange, const std::string& key, const FieldTable& options = EMPTY_FIELD_TABLE); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + + static SubscriptionMode getMode(const std::string& mode); + private: + struct Binding + { + Binding(const std::string& exchange, const std::string& key, const FieldTable& options = EMPTY_FIELD_TABLE); + + std::string exchange; + std::string key; + FieldTable options; + }; + + typedef std::vector<Binding> Bindings; + + const std::string name; + const bool autoDelete; + const bool durable; + const FieldTable queueOptions; + const FieldTable subscriptionOptions; + Bindings bindings; + std::string queue; +}; + +class Exchange : public MessageSink +{ + public: + Exchange(const std::string& name, const std::string& defaultSubject = EMPTY_STRING, + bool passive = true, const std::string& type = EMPTY_STRING, bool durable = false, + const FieldTable& options = EMPTY_FIELD_TABLE); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, qpid::messaging::Message& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: + const std::string name; + const std::string defaultSubject; + const bool passive; + const std::string type; + const bool durable; + const FieldTable options; +}; + +class QueueSink : public MessageSink +{ + public: + QueueSink(const std::string& name, bool passive=true, bool exclusive=false, + bool autoDelete=false, bool durable=false, const FieldTable& options = EMPTY_FIELD_TABLE); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, qpid::messaging::Message& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: + const std::string name; + const bool passive; + const bool exclusive; + const bool autoDelete; + const bool durable; + const FieldTable options; +}; + + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address); +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address, std::string& subject); + +const Variant& getOption(const std::string& key, const Variant::Map& options) +{ + Variant::Map::const_iterator i = options.find(key); + if (i == options.end()) return EMPTY_VARIANT; + else return i->second; +} + +std::auto_ptr<MessageSource> AddressResolution::resolveSource(qpid::client::Session session, + const Address& address, + const Filter* filter, + const Variant::Map& options) +{ + //TODO: handle case where there exists a queue and an exchange of + //the same name (hence an unqualified address is ambiguous) + + //TODO: make sure specified address type gives sane error message + //if it does npt match the configuration on server + + if (isQueue(session, address)) { + //TODO: support auto-created queue as source, if requested by specific option + + AcceptMode accept = getOption(UNACKNOWLEDGED, options).asBool() ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT; + AcquireMode acquire = getOption(BROWSE, options).asBool() ? ACQUIRE_MODE_NOT_ACQUIRED : ACQUIRE_MODE_PRE_ACQUIRED; + bool exclusive = getOption(EXCLUSIVE, options).asBool(); + FieldTable arguments; + //TODO: extract subscribe arguments from options (e.g. either + //filter out already processed keys and send the rest, or have + //a nested map) + + std::auto_ptr<MessageSource> source = + std::auto_ptr<MessageSource>(new QueueSource(address.value, accept, acquire, exclusive, arguments)); + return source; + } else { + //TODO: extract queue options (e.g. no-local) and subscription options (e.g. less important) + std::auto_ptr<Subscription> bindings = + std::auto_ptr<Subscription>(new Subscription(getOption(NAME, options).asString(), + Subscription::getMode(getOption(MODE, options).asString()))); + + qpid::framing::ExchangeQueryResult result = session.exchangeQuery(address.value); + if (result.getNotFound()) { + throw qpid::framing::NotFoundException(QPID_MSG("Address not known: " << address)); + } else if (result.getType() == "topic") { + if (filter) { + if (filter->type != Filter::WILDCARD) { + throw qpid::framing::NotImplementedException( + QPID_MSG("Filters of type " << filter->type << " not supported by address " << address)); + + } + for (std::vector<std::string>::const_iterator i = filter->patterns.begin(); i != filter->patterns.end(); i++) { + bindings->add(address.value, *i, qpid::framing::FieldTable()); + } + } else { + //default is to receive all messages + bindings->add(address.value, "*", qpid::framing::FieldTable()); + } + } else if (result.getType() == "fanout") { + if (filter) { + throw qpid::framing::NotImplementedException(QPID_MSG("Filters are not supported by address " << address)); + } + bindings->add(address.value, address.value, qpid::framing::FieldTable()); + } else if (result.getType() == "direct") { + //TODO: ???? + } else { + //TODO: xml and headers exchanges + throw qpid::framing::NotImplementedException(QPID_MSG("Address type not recognised for " << address)); + } + std::auto_ptr<MessageSource> source = std::auto_ptr<MessageSource>(bindings.release()); + return source; + } +} + + +std::auto_ptr<MessageSink> AddressResolution::resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address, + const qpid::messaging::Variant::Map& /*options*/) +{ + std::auto_ptr<MessageSink> sink; + if (isQueue(session, address)) { + //TODO: support for auto-created queues as sink + sink = std::auto_ptr<MessageSink>(new QueueSink(address.value)); + } else { + std::string subject; + if (isTopic(session, address, subject)) { + //TODO: support for auto-created exchanges as sink + sink = std::auto_ptr<MessageSink>(new Exchange(address.value, subject)); + } else { + if (address.type.empty()) { + throw qpid::framing::NotFoundException(QPID_MSG("Address not known: " << address)); + } else { + throw qpid::framing::NotImplementedException(QPID_MSG("Address type not recognised: " << address.type)); + } + } + } + return sink; +} + +QueueSource::QueueSource(const std::string& _name, AcceptMode _acceptMode, AcquireMode _acquireMode, bool _exclusive, const FieldTable& _options) : + name(_name), acceptMode(_acceptMode), acquireMode(_acquireMode), exclusive(_exclusive), options(_options) {} + +void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageSubscribe(arg::queue=name, + arg::destination=destination, + arg::acceptMode=acceptMode, + arg::acquireMode=acquireMode, + arg::exclusive=exclusive, + arg::arguments=options); +} + +void QueueSource::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageCancel(destination); +} + +Subscription::Subscription(const std::string& _name, SubscriptionMode mode, const FieldTable& qOptions, const FieldTable& sOptions) + : name(_name), autoDelete(mode == SIMPLE), durable(mode == DURABLE), + queueOptions(qOptions), subscriptionOptions(sOptions) {} + +void Subscription::add(const std::string& exchange, const std::string& key, const FieldTable& options) +{ + bindings.push_back(Binding(exchange, key, options)); +} + +void Subscription::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + if (name.empty()) { + //TODO: use same scheme as JMS client for subscription queue name generation? + queue = session.getId().getName() + destination; + } else { + queue = name; + } + session.queueDeclare(arg::queue=queue, arg::exclusive=true, + arg::autoDelete=autoDelete, arg::durable=durable, arg::arguments=queueOptions); + for (Bindings::const_iterator i = bindings.begin(); i != bindings.end(); ++i) { + session.exchangeBind(arg::queue=queue, arg::exchange=i->exchange, arg::bindingKey=i->key, arg::arguments=i->options); + } + AcceptMode accept = autoDelete ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT; + session.messageSubscribe(arg::queue=queue, arg::destination=destination, + arg::exclusive=true, arg::acceptMode=accept, arg::arguments=subscriptionOptions); +} + +void Subscription::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + session.messageCancel(destination); + session.queueDelete(arg::queue=queue); +} + +Subscription::Binding::Binding(const std::string& e, const std::string& k, const FieldTable& o): + exchange(e), key(k), options(o) {} + +Subscription::SubscriptionMode Subscription::getMode(const std::string& s) +{ + if (s.empty() || s == SIMPLE_SUBSCRIPTION) return SIMPLE; + else if (s == RELIABLE_SUBSCRIPTION) return RELIABLE; + else if (s == DURABLE_SUBSCRIPTION) return DURABLE; + else throw Exception(QPID_MSG("Unrecognised subscription mode: " << s)); +} + +void convert(qpid::messaging::Message& from, qpid::client::Message& to); + +Exchange::Exchange(const std::string& _name, const std::string& _defaultSubject, + bool _passive, const std::string& _type, bool _durable, const FieldTable& _options) : + name(_name), defaultSubject(_defaultSubject), passive(_passive), type(_type), durable(_durable), options(_options) {} + +void Exchange::declare(qpid::client::AsyncSession& session, const std::string&) +{ + //TODO: should this really by synchronous? want to get error if not valid... + if (passive) { + sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true); + } else { + sync(session).exchangeDeclare(arg::exchange=name, arg::type=type, arg::durable=durable, arg::arguments=options); + } +} + +void Exchange::send(qpid::client::AsyncSession& session, const std::string&, qpid::messaging::Message& m) +{ + qpid::client::Message message; + convert(m, message); + if (message.getDeliveryProperties().getRoutingKey().empty() && !defaultSubject.empty()) { + message.getDeliveryProperties().setRoutingKey(defaultSubject); + } + session.messageTransfer(arg::destination=name, arg::content=message); +} + +void Exchange::cancel(qpid::client::AsyncSession&, const std::string&) {} + +QueueSink::QueueSink(const std::string& _name, bool _passive, bool _exclusive, + bool _autoDelete, bool _durable, const FieldTable& _options) : + name(_name), passive(_passive), exclusive(_exclusive), + autoDelete(_autoDelete), durable(_durable), options(_options) {} + +void QueueSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + //TODO: should this really by synchronous? + if (passive) { + sync(session).queueDeclare(arg::queue=name, arg::passive=true); + } else { + sync(session).queueDeclare(arg::queue=name, arg::exclusive=exclusive, arg::durable=durable, + arg::autoDelete=autoDelete, arg::arguments=options); + } +} +void QueueSink::send(qpid::client::AsyncSession& session, const std::string&, qpid::messaging::Message& m) +{ + qpid::client::Message message; + convert(m, message); + message.getDeliveryProperties().setRoutingKey(name); + session.messageTransfer(arg::content=message); +} + +void QueueSink::cancel(qpid::client::AsyncSession&, const std::string&) {} + +template <class T> void encode(qpid::messaging::Message& from) +{ + T codec; + from.encode(codec); + from.setContentType(T::contentType); +} + +void translate(const Variant::Map& from, FieldTable& to);//implementation in Codecs.cpp + +void convert(qpid::messaging::Message& from, qpid::client::Message& to) +{ + //TODO: need to avoid copying as much as possible + if (from.getContent().isList()) encode<ListCodec>(from); + if (from.getContent().isMap()) encode<MapCodec>(from); + to.setData(from.getBytes()); + to.getDeliveryProperties().setRoutingKey(from.getSubject()); + //TODO: set other delivery properties + to.getMessageProperties().setContentType(from.getContentType()); + const Address& address = from.getReplyTo(); + if (!address.value.empty()) { + to.getMessageProperties().setReplyTo(AddressResolution::convert(address)); + } + translate(from.getHeaders(), to.getMessageProperties().getApplicationHeaders()); + //TODO: set other message properties +} + +Address AddressResolution::convert(const qpid::framing::ReplyTo& rt) +{ + if (rt.getExchange().empty()) { + if (rt.getRoutingKey().empty()) { + return Address();//empty address + } else { + return Address(rt.getRoutingKey(), QUEUE_ADDRESS); + } + } else { + if (rt.getRoutingKey().empty()) { + return Address(rt.getExchange(), TOPIC_ADDRESS); + } else { + return Address(rt.getExchange() + DIVIDER + rt.getRoutingKey(), TOPIC_ADDRESS_AND_SUBJECT); + } + } +} + +qpid::framing::ReplyTo AddressResolution::convert(const Address& address) +{ + if (address.type == QUEUE_ADDRESS || address.type.empty()) { + return ReplyTo(EMPTY_STRING, address.value); + } else if (address.type == TOPIC_ADDRESS) { + return ReplyTo(address.value, EMPTY_STRING); + } else if (address.type == TOPIC_ADDRESS_AND_SUBJECT) { + //need to split the value + string::size_type i = address.value.find(DIVIDER); + if (i != string::npos) { + std::string exchange = address.value.substr(0, i); + std::string routingKey; + if (i+1 < address.value.size()) { + routingKey = address.value.substr(i+1); + } + return ReplyTo(exchange, routingKey); + } else { + return ReplyTo(address.value, EMPTY_STRING); + } + } else { + QPID_LOG(notice, "Unrecognised type for reply-to: " << address.type); + //treat as queue + return ReplyTo(EMPTY_STRING, address.value); + } +} + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address) +{ + return address.type == QUEUE_ADDRESS || + (address.type.empty() && session.queueQuery(address.value).getQueue() == address.value); +} + +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address, std::string& subject) +{ + if (address.type.empty()) { + return !session.exchangeQuery(address.value).getNotFound(); + } else if (address.type == TOPIC_ADDRESS) { + return true; + } else if (address.type == TOPIC_ADDRESS_AND_SUBJECT) { + string::size_type i = address.value.find(DIVIDER); + if (i != string::npos) { + std::string exchange = address.value.substr(0, i); + if (i+1 < address.value.size()) { + subject = address.value.substr(i+1); + } + } + return true; + } else { + return false; + } +} + + +}}} // 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..87758abe6d --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h @@ -0,0 +1,68 @@ +#ifndef QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H +#define QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/Variant.h" +#include "qpid/client/Session.h" + +namespace qpid { + +namespace framing{ +class ReplyTo; +} + +namespace messaging { +class Address; +class Filter; +} + +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, + const qpid::messaging::Filter* filter, + const qpid::messaging::Variant::Map& options); + + std::auto_ptr<MessageSink> resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address, + const qpid::messaging::Variant::Map& options); + + static qpid::messaging::Address convert(const qpid::framing::ReplyTo&); + static qpid::framing::ReplyTo convert(const qpid::messaging::Address&); + + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/Codecs.cpp b/qpid/cpp/src/qpid/client/amqp0_10/Codecs.cpp new file mode 100644 index 0000000000..9aee3118fe --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/Codecs.cpp @@ -0,0 +1,299 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" +#include <algorithm> +#include <functional> + +using namespace qpid::framing; +using namespace qpid::messaging; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +namespace { +const std::string iso885915("iso-8859-15"); +const std::string utf8("utf8"); +const std::string utf16("utf16"); +const std::string amqp0_10_binary("amqp0-10:binary"); +const std::string amqp0_10_bit("amqp0-10:bit"); +const std::string amqp0_10_datetime("amqp0-10:datetime"); +const std::string amqp0_10_struct("amqp0-10:struct"); +} + +template <class T, class U, class F> void convert(const T& from, U& to, F f) +{ + std::transform(from.begin(), from.end(), std::inserter(to, to.begin()), f); +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in); +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in); +Variant toVariant(boost::shared_ptr<FieldValue> in); +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in); + +template <class T, class U, class F> void translate(boost::shared_ptr<FieldValue> in, U& u, F f) +{ + T t; + getEncodedValue<T>(in, t); + convert(t, u, f); +} + +template <class T, class U, class F> T* toFieldValueCollection(const U& u, F f) +{ + typename T::ValueType t; + convert(u, t, f); + return new T(t); +} + +FieldTableValue* toFieldTableValue(const Variant::Map& map) +{ + FieldTable ft; + convert(map, ft, &toFieldTableEntry); + return new FieldTableValue(ft); +} + +ListValue* toListValue(const Variant::List& list) +{ + List l; + convert(list, l, &toFieldValue); + return new ListValue(l); +} + +void setEncodingFor(Variant& out, uint8_t code) +{ + switch(code){ + case 0x80: + case 0x90: + case 0xa0: + out.setEncoding(amqp0_10_binary); + break; + case 0x84: + case 0x94: + out.setEncoding(iso885915); + break; + case 0x85: + case 0x95: + out.setEncoding(utf8); + break; + case 0x86: + case 0x96: + out.setEncoding(utf16); + break; + case 0xab: + out.setEncoding(amqp0_10_struct); + break; + default: + //do nothing + break; + } +} + +Variant toVariant(boost::shared_ptr<FieldValue> in) +{ + Variant out; + //based on AMQP 0-10 typecode, pick most appropriate variant type + switch (in->getType()) { + //Fixed Width types: + case 0x01: out.setEncoding(amqp0_10_binary); + case 0x02: out = in->getIntegerValue<int8_t, 1>(); break; + case 0x03: out = in->getIntegerValue<uint8_t, 1>(); break; + case 0x04: break; //TODO: iso-8859-15 char + case 0x08: out = in->getIntegerValue<bool, 1>(); break; + case 0x010: out.setEncoding(amqp0_10_binary); + case 0x011: out = in->getIntegerValue<int16_t, 2>(); break; + case 0x012: out = in->getIntegerValue<uint16_t, 2>(); break; + case 0x020: out.setEncoding(amqp0_10_binary); + case 0x021: out = in->getIntegerValue<int32_t, 4>(); break; + case 0x022: out = in->getIntegerValue<uint32_t, 4>(); break; + case 0x023: out = in->get<float>(); break; + case 0x027: break; //TODO: utf-32 char + case 0x030: out.setEncoding(amqp0_10_binary); + case 0x031: out = in->getIntegerValue<int64_t, 8>(); break; + case 0x038: out.setEncoding(amqp0_10_datetime); //treat datetime as uint64_t, but set encoding + case 0x032: out = in->getIntegerValue<uint64_t, 8>(); break; + case 0x033:out = in->get<double>(); break; + + //TODO: figure out whether and how to map values with codes 0x40-0xd8 + + case 0xf0: break;//void, which is the default value for Variant + case 0xf1: out.setEncoding(amqp0_10_bit); break;//treat 'bit' as void, which is the default value for Variant + + //Variable Width types: + //strings: + case 0x80: + case 0x84: + case 0x85: + case 0x86: + case 0x90: + case 0x94: + case 0x95: + case 0x96: + case 0xa0: + case 0xab: + setEncodingFor(out, in->getType()); + out = in->get<std::string>(); + break; + + case 0xa8: + out = Variant::Map(); + translate<FieldTable>(in, out.asMap(), &toVariantMapEntry); + break; + + case 0xa9: + out = Variant::List(); + translate<List>(in, out.asList(), &toVariant); + break; + case 0xaa: //convert amqp0-10 array into variant list + out = Variant::List(); + translate<Array>(in, out.asList(), &toVariant); + break; + + default: + //error? + break; + } + return out; +} + +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in) +{ + boost::shared_ptr<FieldValue> out; + switch (in.getType()) { + case VOID: out = boost::shared_ptr<FieldValue>(new VoidValue()); break; + case BOOL: out = boost::shared_ptr<FieldValue>(new BoolValue(in.asBool())); break; + case UINT8: out = boost::shared_ptr<FieldValue>(new Unsigned8Value(in.asUint8())); break; + case UINT16: out = boost::shared_ptr<FieldValue>(new Unsigned16Value(in.asUint16())); break; + case UINT32: out = boost::shared_ptr<FieldValue>(new Unsigned32Value(in.asUint32())); break; + case UINT64: out = boost::shared_ptr<FieldValue>(new Unsigned64Value(in.asUint64())); break; + case INT8: out = boost::shared_ptr<FieldValue>(new Integer8Value(in.asInt8())); break; + case INT16: out = boost::shared_ptr<FieldValue>(new Integer16Value(in.asInt16())); break; + case INT32: out = boost::shared_ptr<FieldValue>(new Integer32Value(in.asInt32())); break; + case INT64: out = boost::shared_ptr<FieldValue>(new Integer64Value(in.asInt64())); break; + case FLOAT: out = boost::shared_ptr<FieldValue>(new FloatValue(in.asFloat())); break; + case DOUBLE: out = boost::shared_ptr<FieldValue>(new DoubleValue(in.asDouble())); break; + //TODO: check encoding (and length?) when deciding what AMQP type to treat string as + case STRING: out = boost::shared_ptr<FieldValue>(new Str16Value(in.asString())); break; + case MAP: + //out = boost::shared_ptr<FieldValue>(toFieldValueCollection<FieldTableValue>(in.asMap(), &toFieldTableEntry)); + out = boost::shared_ptr<FieldValue>(toFieldTableValue(in.asMap())); + break; + case LIST: + //out = boost::shared_ptr<FieldValue>(toFieldValueCollection<ListValue>(in.asList(), &toFieldValue)); + out = boost::shared_ptr<FieldValue>(toListValue(in.asList())); + break; + } + return out; +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in) +{ + return Variant::Map::value_type(in.first, toVariant(in.second)); +} + +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in) +{ + return FieldTable::value_type(in.first, toFieldValue(in.second)); +} + +struct EncodeBuffer +{ + char* data; + Buffer buffer; + + EncodeBuffer(size_t size) : data(new char[size]), buffer(data, size) {} + ~EncodeBuffer() { delete[] data; } + + template <class T> void encode(T& t) { t.encode(buffer); } + + void getData(std::string& s) { + s.assign(data, buffer.getSize()); + } +}; + +struct DecodeBuffer +{ + Buffer buffer; + + DecodeBuffer(const std::string& s) : buffer(const_cast<char*>(s.data()), s.size()) {} + + template <class T> void decode(T& t) { t.decode(buffer); } + +}; + +template <class T, class U, class F> void _encode(const U& value, std::string& data, F f) +{ + T t; + convert(value, t, f); + EncodeBuffer buffer(t.encodedSize()); + buffer.encode(t); + buffer.getData(data); +} + +template <class T, class U, class F> void _decode(const std::string& data, U& value, F f) +{ + T t; + DecodeBuffer buffer(data); + buffer.decode(t); + convert(t, value, f); +} + +void MapCodec::encode(const Variant& value, std::string& data) +{ + _encode<FieldTable>(value.asMap(), data, &toFieldTableEntry); +} + +void MapCodec::decode(const std::string& data, Variant& value) +{ + value = Variant::Map(); + _decode<FieldTable>(data, value.asMap(), &toVariantMapEntry); +} + +void ListCodec::encode(const Variant& value, std::string& data) +{ + _encode<List>(value.asList(), data, &toFieldValue); +} + +void ListCodec::decode(const std::string& data, Variant& value) +{ + value = Variant::List(); + _decode<List>(data, value.asList(), &toVariant); +} + +void translate(const Variant::Map& from, FieldTable& to) +{ + convert(from, to, &toFieldTableEntry); +} + +void translate(const FieldTable& from, Variant::Map& to) +{ + convert(from, to, &toVariantMapEntry); +} + +const std::string ListCodec::contentType("amqp0_10/list"); +const std::string MapCodec::contentType("amqp0_10/map"); + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/CompletionTracker.cpp b/qpid/cpp/src/qpid/client/amqp0_10/CompletionTracker.cpp new file mode 100644 index 0000000000..52b623b65c --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/CompletionTracker.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 "CompletionTracker.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::framing::SequenceNumber; + +void CompletionTracker::track(SequenceNumber command, void* token) +{ + tokens[command] = token; +} + +void CompletionTracker::completedTo(SequenceNumber command) +{ + Tokens::iterator i = tokens.lower_bound(command); + if (i != tokens.end()) { + lastCompleted = i->second; + tokens.erase(tokens.begin(), ++i); + } +} + +void* CompletionTracker::getLastCompletedToken() +{ + return lastCompleted; +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/CompletionTracker.h b/qpid/cpp/src/qpid/client/amqp0_10/CompletionTracker.h new file mode 100644 index 0000000000..6147c5682e --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/CompletionTracker.h @@ -0,0 +1,50 @@ +#ifndef QPID_CLIENT_AMQP0_10_COMPLETIONTRACKER_H +#define QPID_CLIENT_AMQP0_10_COMPLETIONTRACKER_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/framing/SequenceNumber.h" +#include <map> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Provides a mapping from command ids to application supplied + * 'tokens', and is used to determine when the sending or + * acknowledging of a specific message is complete. + */ +class CompletionTracker +{ + public: + void track(qpid::framing::SequenceNumber command, void* token); + void completedTo(qpid::framing::SequenceNumber command); + void* getLastCompletedToken(); + private: + typedef std::map<qpid::framing::SequenceNumber, void*> Tokens; + Tokens tokens; + void* lastCompleted; +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_COMPLETIONTRACKER_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..9f738731e2 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.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 "ConnectionImpl.h" +#include "SessionImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Variant; + +template <class T> void setIfFound(const Variant::Map& map, const std::string& key, T& value) +{ + Variant::Map::const_iterator i = map.find(key); + if (i != map.end()) { + value = (T) i->second; + } +} + +void convert(const Variant::Map& from, ConnectionSettings& to) +{ + setIfFound(from, "username", to.username); + setIfFound(from, "password", to.password); + setIfFound(from, "sasl-mechanism", to.mechanism); + setIfFound(from, "sasl-service", to.service); + setIfFound(from, "sasl-min-ssf", to.minSsf); + setIfFound(from, "sasl-max-ssf", to.maxSsf); + + setIfFound(from, "heartbeat", to.heartbeat); + setIfFound(from, "tcp-nodelay", to.tcpNoDelay); + + setIfFound(from, "locale", to.locale); + setIfFound(from, "max-channels", to.maxChannels); + setIfFound(from, "max-frame-size", to.maxFrameSize); + setIfFound(from, "bounds", to.bounds); +} + +ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) +{ + QPID_LOG(debug, "Opening connection to " << url << " with " << options); + Url u(url); + ConnectionSettings settings; + convert(options, settings); + connection.open(u, settings); +} + +void ConnectionImpl::close() +{ + connection.close(); +} + +qpid::messaging::Session ConnectionImpl::newSession() +{ + qpid::messaging::Session impl(new SessionImpl(connection.newSession())); + return impl; +} + +}}} // 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..120a8ab9d8 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -0,0 +1,43 @@ +#ifndef QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H +#define QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/ConnectionImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/Connection.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class ConnectionImpl : public qpid::messaging::ConnectionImpl +{ + public: + ConnectionImpl(const std::string& url, const qpid::messaging::Variant::Map& options); + void close(); + qpid::messaging::Session newSession(); + private: + qpid::client::Connection connection; +}; +}}} // 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..b69c1917e6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp @@ -0,0 +1,241 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/Codecs.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Variant.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using namespace qpid::framing; +using namespace qpid::framing::message; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::messaging::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; + } + } +}; +} + +IncomingMessages::IncomingMessages(qpid::client::AsyncSession s) : + session(s), + incoming(SessionBase_0_10Access(session).get()->getDemux().getDefault()) {} + +bool IncomingMessages::get(Handler& handler, Duration timeout) +{ + //search through received list for any transfer of interest: + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i++) + { + MessageTransfer transfer(*i, *this); + if (handler.accept(transfer)) { + received.erase(i); + return true; + } + } + //none found, check incoming: + return process(&handler, timeout); +} + +void IncomingMessages::accept() +{ + session.messageAccept(unaccepted); + unaccepted.clear(); +} + +void IncomingMessages::releaseAll() +{ + //first process any received messages... + while (!received.empty()) { + retrieve(received.front(), 0); + received.pop_front(); + } + //then pump out any available messages from incoming queue... + GetAny handler; + while (process(&handler, 0)) ; + //now release all messages + session.messageRelease(unaccepted); + unaccepted.clear(); +} + +void IncomingMessages::releasePending(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) ; + + //now remove all messages for this destination from received list, recording their ids... + MatchAndTrack match(destination); + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i = match(*i) ? received.erase(i) : ++i) ; + //now release those messages + session.messageRelease(match.ids); +} + +/** + * Get a frameset from session queue, waiting for up to the specified + * duration and returning true if this could be achieved, false + * otherwise. If a destination is supplied, only return a message for + * that destination. In this case messages from other destinations + * will be held on a received queue. + */ +bool IncomingMessages::process(Handler* handler, qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + MessageTransfer transfer(content, *this); + if (handler && handler->accept(transfer)) { + QPID_LOG(debug, "Delivered " << *content->getMethod()); + return true; + } else { + //received message for another destination, keep for later + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + received.push_back(content); + } + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + return false; +} + +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) { + unaccepted.add(command->getId()); + } + session.markCompleted(command->getId(), false, false); +} + +IncomingMessages::MessageTransfer::MessageTransfer(FrameSetPtr c, IncomingMessages& p) : content(c), parent(p) {} + +const std::string& IncomingMessages::MessageTransfer::getDestination() +{ + return content->as<MessageTransferBody>()->getDestination(); +} +void IncomingMessages::MessageTransfer::retrieve(qpid::messaging::Message* message) +{ + parent.retrieve(content, message); +} + +void translate(const FieldTable& from, Variant::Map& to);//implemented in Codecs.cpp + +void populateHeaders(qpid::messaging::Message& message, + const DeliveryProperties* deliveryProperties, + const MessageProperties* messageProperties) +{ + if (deliveryProperties) { + message.setSubject(deliveryProperties->getRoutingKey()); + //TODO: convert other delivery properties + } + if (messageProperties) { + message.setContentType(messageProperties->getContentType()); + if (messageProperties->hasReplyTo()) { + message.setReplyTo(AddressResolution::convert(messageProperties->getReplyTo())); + } + translate(messageProperties->getApplicationHeaders(), message.getHeaders()); + //TODO: convert other message properties + } +} + +void populateHeaders(qpid::messaging::Message& message, const AMQHeaderBody* headers) +{ + populateHeaders(message, headers->get<DeliveryProperties>(), headers->get<MessageProperties>()); +} + +void populate(qpid::messaging::Message& message, FrameSet& command) +{ + //need to be able to link the message back to the transfer it was delivered by + //e.g. for rejecting. TODO: hide this from API + uint32_t commandId = command.getId(); + message.setInternalId(reinterpret_cast<void*>(commandId)); + + command.getContent(message.getBytes()); + + populateHeaders(message, command.getHeaders()); + + //decode content if necessary + if (message.getContentType() == ListCodec::contentType) { + ListCodec codec; + message.decode(codec); + } else if (message.getContentType() == MapCodec::contentType) { + MapCodec codec; + message.decode(codec); + } +} + + +}}} // 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..c4346fd7d7 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h @@ -0,0 +1,91 @@ +#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" + +namespace qpid { + +namespace framing{ +class FrameSet; +} + +namespace messaging { +class Message; +} + +namespace client { +namespace amqp0_10 { + +/** + * + */ +class IncomingMessages +{ + public: + typedef boost::shared_ptr<qpid::framing::FrameSet> FrameSetPtr; + class MessageTransfer + { + public: + const std::string& getDestination(); + void retrieve(qpid::messaging::Message* message); + private: + FrameSetPtr content; + IncomingMessages& parent; + + MessageTransfer(FrameSetPtr, IncomingMessages&); + friend class IncomingMessages; + }; + + struct Handler + { + virtual ~Handler() {} + virtual bool accept(MessageTransfer& transfer) = 0; + }; + + IncomingMessages(qpid::client::AsyncSession session); + bool get(Handler& handler, qpid::sys::Duration timeout); + //bool get(qpid::messaging::Message& message, qpid::sys::Duration timeout); + //bool get(const std::string& destination, qpid::messaging::Message& message, qpid::sys::Duration timeout); + void accept(); + void releaseAll(); + void releasePending(const std::string& destination); + private: + typedef std::deque<FrameSetPtr> FrameSetQueue; + + qpid::client::AsyncSession session; + qpid::framing::SequenceSet unaccepted; + boost::shared_ptr< sys::BlockingQueue<FrameSetPtr> > incoming; + FrameSetQueue received; + + bool process(Handler*, 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..19d5e4ef82 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h @@ -0,0 +1,50 @@ +#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 { + +/** + * + */ +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, qpid::messaging::Message& 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/ReceiverImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp new file mode 100644 index 0000000000..e6ed4bfc4e --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "ReceiverImpl.h" +#include "MessageSource.h" +#include "SessionImpl.h" +#include "qpid/messaging/MessageListener.h" +#include "qpid/messaging/Receiver.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Receiver; + +void ReceiverImpl::received(qpid::messaging::Message&) +{ + //TODO: should this be configurable + if (capacity && --window <= capacity/2) { + session.sendCompletion(); + window = capacity; + } +} + +bool ReceiverImpl::get(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + return parent.get(*this, message, timeout); +} + +qpid::messaging::Message ReceiverImpl::get(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!get(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +bool ReceiverImpl::fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + if (capacity == 0 && !cancelled) { + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, 1); + if (!started) session.messageFlow(destination, CREDIT_UNIT_BYTE, byteCredit); + } + + if (get(message, timeout)) { + return true; + } else { + if (!cancelled) { + sync(session).messageFlush(destination); + start();//reallocate credit + } + return get(message, 0); + } +} + +qpid::messaging::Message ReceiverImpl::fetch(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +void ReceiverImpl::cancel() +{ + if (!cancelled) { + //TODO: should syncronicity be an optional argument to this call? + source->cancel(session, destination); + //need to be sure cancel is complete and all incoming + //framesets are processed before removing the receiver + parent.receiverCancelled(destination); + cancelled = true; + } +} + +void ReceiverImpl::start() +{ + if (!cancelled) { + started = true; + session.messageSetFlowMode(destination, capacity > 0); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, capacity); + session.messageFlow(destination, CREDIT_UNIT_BYTE, byteCredit); + window = capacity; + } +} + +void ReceiverImpl::stop() +{ + session.messageStop(destination); + started = false; +} + +void ReceiverImpl::subscribe() +{ + source->subscribe(session, destination); +} + +void ReceiverImpl::setSession(qpid::client::AsyncSession s) +{ + session = s; + if (!cancelled) { + subscribe(); + //if we were in started state before the session was changed, + //start again on this new session + //TODO: locking if receiver is to be threadsafe... + if (started) start(); + } +} + +void ReceiverImpl::setCapacity(uint32_t c) +{ + if (c != capacity) { + capacity = c; + if (!cancelled && started) { + stop(); + start(); + } + } +} + +void ReceiverImpl::setListener(qpid::messaging::MessageListener* l) { listener = l; } +qpid::messaging::MessageListener* ReceiverImpl::getListener() { return listener; } + +const std::string& ReceiverImpl::getName() const { return destination; } + +ReceiverImpl::ReceiverImpl(SessionImpl& p, const std::string& name, std::auto_ptr<MessageSource> s) : + parent(p), source(s), destination(name), byteCredit(0xFFFFFFFF), + capacity(0), started(false), cancelled(false), listener(0), window(0) {} + + +}}} // 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..b549242d35 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -0,0 +1,76 @@ +#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/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Time.h" +#include <memory> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class MessageSource; +class SessionImpl; + +/** + * A receiver implementation based on an AMQP 0-10 subscription. + */ +class ReceiverImpl : public qpid::messaging::ReceiverImpl +{ + public: + + ReceiverImpl(SessionImpl& parent, const std::string& name, std::auto_ptr<MessageSource> source); + + bool get(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message get(qpid::sys::Duration timeout); + bool fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message fetch(qpid::sys::Duration timeout); + void cancel(); + void start(); + void stop(); + void subscribe(); + void setSession(qpid::client::AsyncSession s); + const std::string& getName() const; + void setCapacity(uint32_t); + void setListener(qpid::messaging::MessageListener* listener); + qpid::messaging::MessageListener* getListener(); + void received(qpid::messaging::Message& message); + private: + SessionImpl& parent; + const std::auto_ptr<MessageSource> source; + const std::string destination; + const uint32_t byteCredit; + + uint32_t capacity; + qpid::client::AsyncSession session; + bool started; + bool cancelled; + qpid::messaging::MessageListener* listener; + uint32_t window; +}; + +}}} // 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..ac36eb1537 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -0,0 +1,49 @@ +/* + * + * 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" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name, std::auto_ptr<MessageSink> _sink) : + parent(_parent), name(_name), sink(_sink) {} + +void SenderImpl::send(qpid::messaging::Message& m) +{ + sink->send(session, name, m); +} + +void SenderImpl::cancel() +{ + sink->cancel(session, name); + parent.senderCancelled(name); +} + +void SenderImpl::setSession(qpid::client::AsyncSession s) +{ + session = s; + sink->declare(session, name); +} + +}}} // 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..e737450ba1 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -0,0 +1,58 @@ +#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/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/client/AsyncSession.h" +#include <memory> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class MessageSink; +class SessionImpl; + +/** + * + */ +class SenderImpl : public qpid::messaging::SenderImpl +{ + public: + SenderImpl(SessionImpl& parent, const std::string& name, std::auto_ptr<MessageSink> sink); + void send(qpid::messaging::Message&); + void cancel(); + void setSession(qpid::client::AsyncSession); + + private: + SessionImpl& parent; + const std::string name; + std::auto_ptr<MessageSink> sink; + + qpid::client::AsyncSession session; + std::string destination; + std::string routingKey; +}; +}}} // 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..9ea2a9f598 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp @@ -0,0 +1,287 @@ +/* + * + * 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/ReceiverImpl.h" +#include "qpid/client/amqp0_10/SenderImpl.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Filter.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageListener.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" +#include "qpid/framing/reply_exceptions.h" +#include <boost/format.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> + +using qpid::messaging::Filter; +using qpid::messaging::Sender; +using qpid::messaging::Receiver; +using qpid::messaging::VariantMap; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SessionImpl::SessionImpl(qpid::client::Session s) : session(s), incoming(session) {} + + +void SessionImpl::commit() +{ + qpid::sys::Mutex::ScopedLock l(lock); + incoming.accept(); + session.txCommit(); +} + +void SessionImpl::rollback() +{ + qpid::sys::Mutex::ScopedLock l(lock); + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) 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) i->second.start(); +} + +void SessionImpl::acknowledge() +{ + qpid::sys::Mutex::ScopedLock l(lock); + incoming.accept(); +} + +void SessionImpl::reject(qpid::messaging::Message& m) +{ + qpid::sys::Mutex::ScopedLock l(lock); + //TODO: how do I get the id of the original transfer command? think this through some more... + + // [tross] The following hack was added to get this code to compile on a 64-bit machine. + // It should be functionally equivalent to the original on a 32-bit architecture + // but is almost certainly not what was intended by the author. + uint64_t rawId(reinterpret_cast<uint64_t>(m.getInternalId())); + SequenceNumber id((uint32_t) ((rawId & 0xFFFFFFFF) ^ ((rawId >> 32) & 0xFFFFFFFF))); + + SequenceSet set; + set.add(id); + session.messageReject(set); +} + +void SessionImpl::close() +{ + session.close(); +} + +void translate(const VariantMap& options, SubscriptionSettings& settings) +{ + //TODO: fill this out + VariantMap::const_iterator i = options.find("auto_acknowledge"); + if (i != options.end()) { + settings.autoAck = i->second.asInt32(); + } +} + +template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t) +{ + return boost::dynamic_pointer_cast<S>(qpid::client::PrivateImplRef<T>::get(t)); +} + +template <class T> void getFreeKey(std::string& key, T& map) +{ + std::string name = key; + int count = 1; + for (typename T::const_iterator i = map.find(name); i != map.end(); i = map.find(name)) { + name = (boost::format("%1%_%2%") % key % ++count).str(); + } + key = name; +} + +Sender SessionImpl::createSender(const qpid::messaging::Address& address, const VariantMap& options) +{ + qpid::sys::Mutex::ScopedLock l(lock); + std::auto_ptr<MessageSink> sink = resolver.resolveSink(session, address, options); + std::string name = address; + getFreeKey(name, senders); + Sender sender(new SenderImpl(*this, name, sink)); + getImplPtr<Sender, SenderImpl>(sender)->setSession(session); + senders[name] = sender; + return sender; +} +Receiver SessionImpl::createReceiver(const qpid::messaging::Address& address, const VariantMap& options) +{ + return addReceiver(address, 0, options); +} +Receiver SessionImpl::createReceiver(const qpid::messaging::Address& address, const Filter& filter, const VariantMap& options) +{ + return addReceiver(address, &filter, options); +} + +Receiver SessionImpl::addReceiver(const qpid::messaging::Address& address, const Filter* filter, const VariantMap& options) +{ + qpid::sys::Mutex::ScopedLock l(lock); + std::auto_ptr<MessageSource> source = resolver.resolveSource(session, address, filter, options); + std::string name = address; + getFreeKey(name, receivers); + Receiver receiver(new ReceiverImpl(*this, name, source)); + getImplPtr<Receiver, ReceiverImpl>(receiver)->setSession(session); + receivers[name] = receiver; + return receiver; +} + +qpid::messaging::Address SessionImpl::createTempQueue(const std::string& baseName) +{ + std::string name = baseName + std::string("_") + session.getId().getName(); + session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true); + return qpid::messaging::Address(name); +} + +SessionImpl& SessionImpl::convert(qpid::messaging::Session& s) +{ + boost::intrusive_ptr<SessionImpl> impl = getImplPtr<qpid::messaging::Session, SessionImpl>(s); + if (!impl) { + throw qpid::Exception(QPID_MSG("Configuration error; require qpid::client::amqp0_10::SessionImpl")); + } + return *impl; +} + +namespace { + +struct IncomingMessageHandler : IncomingMessages::Handler +{ + typedef boost::function1<bool, IncomingMessages::MessageTransfer&> Callback; + Callback callback; + + IncomingMessageHandler(Callback c) : callback(c) {} + + bool accept(IncomingMessages::MessageTransfer& transfer) + { + return callback(transfer); + } +}; + +} + +bool SessionImpl::accept(ReceiverImpl* receiver, + qpid::messaging::Message* message, + bool isDispatch, + IncomingMessages::MessageTransfer& transfer) +{ + if (receiver->getName() == transfer.getDestination()) { + transfer.retrieve(message); + if (isDispatch) { + qpid::sys::Mutex::ScopedUnlock u(lock); + qpid::messaging::MessageListener* listener = receiver->getListener(); + if (listener) listener->received(*message); + } + receiver->received(*message); + return true; + } else { + return false; + } +} + +bool SessionImpl::acceptAny(qpid::messaging::Message* message, bool isDispatch, IncomingMessages::MessageTransfer& transfer) +{ + Receivers::iterator i = receivers.find(transfer.getDestination()); + if (i == receivers.end()) { + QPID_LOG(error, "Received message for unknown destination " << transfer.getDestination()); + return false; + } else { + boost::intrusive_ptr<ReceiverImpl> receiver = getImplPtr<Receiver, ReceiverImpl>(i->second); + return receiver && (!isDispatch || receiver->getListener()) && accept(receiver.get(), message, isDispatch, transfer); + } +} + +bool SessionImpl::getIncoming(IncomingMessages::Handler& handler, qpid::sys::Duration timeout) +{ + qpid::sys::Mutex::ScopedLock l(lock); + return incoming.get(handler, timeout); +} + +bool SessionImpl::dispatch(qpid::sys::Duration timeout) +{ + qpid::messaging::Message message; + IncomingMessageHandler handler(boost::bind(&SessionImpl::acceptAny, this, &message, true, _1)); + return getIncoming(handler, timeout); +} + +bool SessionImpl::get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + IncomingMessageHandler handler(boost::bind(&SessionImpl::accept, this, &receiver, &message, false, _1)); + return getIncoming(handler, timeout); +} + +bool SessionImpl::fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout) +{ + IncomingMessageHandler handler(boost::bind(&SessionImpl::acceptAny, this, &message, false, _1)); + return getIncoming(handler, timeout); +} + +qpid::messaging::Message SessionImpl::fetch(qpid::sys::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw Receiver::NoMessageAvailable(); + return result; +} + +void SessionImpl::receiverCancelled(const std::string& name) +{ + { + qpid::sys::Mutex::ScopedLock l(lock); + receivers.erase(name); + } + session.sync(); + incoming.releasePending(name); +} + +void SessionImpl::senderCancelled(const std::string& name) +{ + qpid::sys::Mutex::ScopedLock l(lock); + senders.erase(name); +} + +void SessionImpl::sync() +{ + session.sync(); +} + +void SessionImpl::flush() +{ + session.flush(); +} + +void* SessionImpl::getLastConfirmedSent() +{ + return 0; +} + +void* SessionImpl::getLastConfirmedAcknowledged() +{ + return 0; +} + +}}} // 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..6926fb0235 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h @@ -0,0 +1,107 @@ +#ifndef QPID_CLIENT_AMQP0_10_SESSIONIMPL_H +#define QPID_CLIENT_AMQP0_10_SESSIONIMPL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/SessionImpl.h" +#include "qpid/messaging/Variant.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/sys/Mutex.h" + +namespace qpid { + +namespace messaging { +class Address; +class Filter; +class Message; +class Receiver; +class Sender; +class Session; +} + +namespace client { +namespace amqp0_10 { + +class ReceiverImpl; +class SenderImpl; + +/** + * Implementation of the protocol independent Session interface using + * AMQP 0-10. + */ +class SessionImpl : public qpid::messaging::SessionImpl +{ + public: + SessionImpl(qpid::client::Session); + void commit(); + void rollback(); + void acknowledge(); + void reject(qpid::messaging::Message&); + void close(); + void sync(); + void flush(); + qpid::messaging::Address createTempQueue(const std::string& baseName); + qpid::messaging::Sender createSender(const qpid::messaging::Address& address, + const qpid::messaging::VariantMap& options); + qpid::messaging::Receiver createReceiver(const qpid::messaging::Address& address, + const qpid::messaging::VariantMap& options); + qpid::messaging::Receiver createReceiver(const qpid::messaging::Address& address, + const qpid::messaging::Filter& filter, + const qpid::messaging::VariantMap& options); + + void* getLastConfirmedSent(); + void* getLastConfirmedAcknowledged(); + + bool fetch(qpid::messaging::Message& message, qpid::sys::Duration timeout); + qpid::messaging::Message fetch(qpid::sys::Duration timeout); + bool dispatch(qpid::sys::Duration timeout); + + bool get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::sys::Duration timeout); + + void receiverCancelled(const std::string& name); + void senderCancelled(const std::string& name); + + static SessionImpl& convert(qpid::messaging::Session&); + + qpid::client::Session session; + private: + typedef std::map<std::string, qpid::messaging::Receiver> Receivers; + typedef std::map<std::string, qpid::messaging::Sender> Senders; + + qpid::sys::Mutex lock; + AddressResolution resolver; + IncomingMessages incoming; + Receivers receivers; + Senders senders; + + qpid::messaging::Receiver addReceiver(const qpid::messaging::Address& address, + const qpid::messaging::Filter* filter, + const qpid::messaging::VariantMap& options); + bool acceptAny(qpid::messaging::Message*, bool, IncomingMessages::MessageTransfer&); + bool accept(ReceiverImpl*, qpid::messaging::Message*, bool, IncomingMessages::MessageTransfer&); + bool getIncoming(IncomingMessages::Handler& handler, qpid::sys::Duration timeout); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SESSIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Cluster.cpp b/qpid/cpp/src/qpid/cluster/Cluster.cpp index 7bdc066767..e35d3e4175 100644 --- a/qpid/cpp/src/qpid/cluster/Cluster.cpp +++ b/qpid/cpp/src/qpid/cluster/Cluster.cpp @@ -323,11 +323,9 @@ void Cluster::deliver( { MemberId from(nodeid, pid); framing::Buffer buf(static_cast<char*>(msg), msg_len); - while (buf.available()) { - Event e(Event::decodeCopy(from, buf)); - LATENCY_TRACK(if (e.getConnectionId().getMember() == self) mcast.cpgLatency.finish()); - deliverEvent(e); - } + Event e(Event::decodeCopy(from, buf)); + LATENCY_TRACK(if (e.getConnectionId().getMember() == self) mcast.cpgLatency.finish()); + deliverEvent(e); } LATENCY_TRACK(sys::LatencyTracker<const char*> eventQueueLatencyTracker("EventQueue");) diff --git a/qpid/cpp/src/qpid/cluster/Event.cpp b/qpid/cpp/src/qpid/cluster/Event.cpp index 9fcf9f5ce1..30866d3154 100644 --- a/qpid/cpp/src/qpid/cluster/Event.cpp +++ b/qpid/cpp/src/qpid/cluster/Event.cpp @@ -72,7 +72,7 @@ Event Event::decodeCopy(const MemberId& m, framing::Buffer& buf) { if (buf.available() < e.size) throw Exception("Not enough data for multicast event"); e.store = RefCountedBuffer::create(e.size + HEADER_SIZE); - buf.getRawData((uint8_t*)(e.getData()), e.size); + memcpy(e.getData(), buf.getPointer() + buf.getPosition(), e.size); return e; } diff --git a/qpid/cpp/src/qpid/cluster/Multicaster.cpp b/qpid/cpp/src/qpid/cluster/Multicaster.cpp index 4123e11c92..7e97963318 100644 --- a/qpid/cpp/src/qpid/cluster/Multicaster.cpp +++ b/qpid/cpp/src/qpid/cluster/Multicaster.cpp @@ -24,14 +24,10 @@ #include "qpid/log/Statement.h" #include "qpid/framing/AMQBody.h" #include "qpid/framing/AMQFrame.h" -#include <boost/bind.hpp> -#include <algorithm> namespace qpid { namespace cluster { -static const int MCAST_IOV_MAX=63; // Limit imposed by CPG - Multicaster::Multicaster(Cpg& cpg_, const boost::shared_ptr<sys::Poller>& poller, boost::function<void()> onError_) : @@ -40,8 +36,7 @@ Multicaster::Multicaster(Cpg& cpg_, #endif onError(onError_), cpg(cpg_), queue(boost::bind(&Multicaster::sendMcast, this, _1), poller), - holding(true), - ioVector(MCAST_IOV_MAX) + holding(true) { queue.start(); } @@ -75,29 +70,26 @@ void Multicaster::mcast(const Event& e) { queue.push(e); } -Multicaster::PollableEventQueue::Batch::const_iterator Multicaster::sendMcast( - const PollableEventQueue::Batch& events) -{ - PollableEventQueue::Batch::const_iterator i = events.begin(); + +Multicaster::PollableEventQueue::Batch::const_iterator Multicaster::sendMcast(const PollableEventQueue::Batch& values) { try { - while (i < events.end()) { - size_t count = std::min(MCAST_IOV_MAX, int(events.end() - i)); - std::transform(i, i+count, ioVector.begin(), - boost::bind(&Event::toIovec, _1)); - if (!cpg.mcast(&ioVector.front(), count)) { - QPID_LOG(trace, "CPG flow control, will resend " - << events.end() - i << " events"); - break; + PollableEventQueue::Batch::const_iterator i = values.begin(); + while( i != values.end()) { + iovec iov = i->toIovec(); + if (!cpg.mcast(&iov, 1)) { + // cpg didn't send because of CPG flow control. + break; } - i += count; + ++i; } + return i; } catch (const std::exception& e) { QPID_LOG(critical, "Multicast error: " << e.what()); queue.stop(); onError(); + return values.end(); } - return i; } void Multicaster::release() { diff --git a/qpid/cpp/src/qpid/cluster/Multicaster.h b/qpid/cpp/src/qpid/cluster/Multicaster.h index 652900aa0f..f2ee5099bb 100644 --- a/qpid/cpp/src/qpid/cluster/Multicaster.h +++ b/qpid/cpp/src/qpid/cluster/Multicaster.h @@ -29,7 +29,6 @@ #include "qpid/sys/LatencyTracker.h" #include <boost/shared_ptr.hpp> #include <deque> -#include <vector> namespace qpid { @@ -73,7 +72,7 @@ class Multicaster PollableEventQueue queue; bool holding; PlainEventQueue holdingQueue; - std::vector< ::iovec> ioVector; + std::vector<struct ::iovec> ioVector; }; }} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/UpdateClient.cpp b/qpid/cpp/src/qpid/cluster/UpdateClient.cpp index ac418ffbb6..2e557f2ab6 100644 --- a/qpid/cpp/src/qpid/cluster/UpdateClient.cpp +++ b/qpid/cpp/src/qpid/cluster/UpdateClient.cpp @@ -213,7 +213,7 @@ class MessageUpdater { framing::MessageTransferBody transfer( framing::ProtocolVersion(), UpdateClient::UPDATE, message::ACCEPT_MODE_NONE, message::ACQUIRE_MODE_PRE_ACQUIRED); - sb.get()->send(transfer, message.payload->getFrames()); + sb.get()->send(transfer, message.payload->getFrames(), !message.payload->isContentReleased()); if (message.payload->isContentReleased()){ uint16_t maxFrameSize = sb.get()->getConnection()->getNegotiatedSettings().maxFrameSize; uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); diff --git a/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp b/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp index 1b813411f6..43dba5e09b 100644 --- a/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp +++ b/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp @@ -16,6 +16,28 @@ * */ +/**@file + + The watchdog plug-in will kill the qpidd broker process if it + becomes stuck for longer than a configured interval. + + If the watchdog plugin is loaded and the --watchdog-interval=N + option is set then the broker starts a watchdog process and signals + it every N/2 seconds. + + The watchdog process runs a very simple program that starts a timer + for N seconds, and resets the timer to N seconds whenever it is + signalled by the broker. If the timer ever reaches 0 the watchdog + kills the broker process (with kill -9) and exits. + + This is useful in a cluster setting because in some insttances + (e.g. while resolving an error) it's possible for a stuck process + to hang other cluster members that are waiting for it to send a + message. Using the watchdog, the stuck process is terminated and + removed fromt the cluster allowing other members to continue and + clients of the stuck process to fail over to other members. + +*/ #include "qpid/Plugin.h" #include "qpid/Options.h" #include "qpid/log/Statement.h" diff --git a/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp b/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp index 0e7f4f18fd..51c5ed4b3f 100644 --- a/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp +++ b/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp @@ -18,6 +18,9 @@ * under the License. * */ + +/** @file helper executable for WatchDogPlugin.cpp */ + #include <sys/types.h> #include <sys/time.h> #include <signal.h> diff --git a/qpid/cpp/src/qpid/framing/FieldTable.cpp b/qpid/cpp/src/qpid/framing/FieldTable.cpp index bc832e9db4..255a3b74a4 100644 --- a/qpid/cpp/src/qpid/framing/FieldTable.cpp +++ b/qpid/cpp/src/qpid/framing/FieldTable.cpp @@ -185,13 +185,8 @@ template <class T, int width, uint8_t typecode> bool getRawFixedWidthValue(FieldTable::ValuePtr vptr, T& value) { if (vptr && vptr->getType() == typecode) { - FixedWidthValue<width>* fwv = dynamic_cast< FixedWidthValue<width>* >(&vptr->getData()); - if (fwv) { - uint8_t* const octets = Endian::convertIfRequired(fwv->rawOctets(), width); - uint8_t* const target = reinterpret_cast<uint8_t*>(&value); - for (uint i = 0; i < width; ++i) target[i] = octets[i]; - return true; - } + value = vptr->get<T>(); + return true; } return false; } @@ -370,5 +365,16 @@ void FieldTable::erase(const std::string& name) values.erase(name); } +std::pair<FieldTable::ValueMap::iterator, bool> FieldTable::insert(const ValueMap::value_type& value) +{ + return values.insert(value); +} + +FieldTable::ValueMap::iterator FieldTable::insert(ValueMap::iterator position, const ValueMap::value_type& value) +{ + return values.insert(position, value); +} + + } } diff --git a/qpid/cpp/src/qpid/framing/FieldValue.cpp b/qpid/cpp/src/qpid/framing/FieldValue.cpp index 5f7248a751..5bac931b83 100644 --- a/qpid/cpp/src/qpid/framing/FieldValue.cpp +++ b/qpid/cpp/src/qpid/framing/FieldValue.cpp @@ -22,6 +22,7 @@ #include "qpid/framing/Array.h" #include "qpid/framing/Buffer.h" #include "qpid/framing/Endian.h" +#include "qpid/framing/List.h" #include "qpid/framing/reply_exceptions.h" namespace qpid { @@ -37,6 +38,8 @@ void FieldValue::setType(uint8_t type) typeOctet = type; if (typeOctet == 0xA8) { data.reset(new EncodedValue<FieldTable>()); + } else if (typeOctet == 0xA9) { + data.reset(new EncodedValue<List>()); } else if (typeOctet == 0xAA) { data.reset(new EncodedValue<Array>()); } else { @@ -164,10 +167,37 @@ FieldTableValue::FieldTableValue(const FieldTable& f) : FieldValue(0xa8, new Enc { } +ListValue::ListValue(const List& l) : FieldValue(0xa9, new EncodedValue<List>(l)) +{ +} + ArrayValue::ArrayValue(const Array& a) : FieldValue(0xaa, new EncodedValue<Array>(a)) { } +VoidValue::VoidValue() : FieldValue(0xf0, new FixedWidthValue<0>()) {} + +BoolValue::BoolValue(bool b) : + FieldValue(0x08, new FixedWidthValue<1>(b)) +{} + +Unsigned8Value::Unsigned8Value(uint8_t v) : + FieldValue(0x02, new FixedWidthValue<1>(v)) +{} +Unsigned16Value::Unsigned16Value(uint16_t v) : + FieldValue(0x12, new FixedWidthValue<2>(v)) +{} +Unsigned32Value::Unsigned32Value(uint32_t v) : + FieldValue(0x22, new FixedWidthValue<4>(v)) +{} + +Integer8Value::Integer8Value(int8_t v) : + FieldValue(0x01, new FixedWidthValue<1>(v)) +{} +Integer16Value::Integer16Value(int16_t v) : + FieldValue(0x11, new FixedWidthValue<2>(v)) +{} + void FieldValue::print(std::ostream& out) const { data->print(out); out << TypeCode(typeOctet) << '('; @@ -177,4 +207,9 @@ void FieldValue::print(std::ostream& out) const { out << ')'; } +uint8_t* FieldValue::convertIfRequired(uint8_t* const octets, int width) +{ + return Endian::convertIfRequired(octets, width); +} + }} diff --git a/qpid/cpp/src/qpid/framing/List.cpp b/qpid/cpp/src/qpid/framing/List.cpp new file mode 100644 index 0000000000..bde7dabbac --- /dev/null +++ b/qpid/cpp/src/qpid/framing/List.cpp @@ -0,0 +1,83 @@ +/* + * + * 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/List.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace framing { + +uint32_t List::encodedSize() const +{ + uint32_t len(4/*size*/ + 4/*count*/); + for(Values::const_iterator i = values.begin(); i != values.end(); ++i) { + len += (*i)->encodedSize(); + } + return len; +} + +void List::encode(Buffer& buffer) const +{ + buffer.putLong(encodedSize() - 4); + buffer.putLong(size()); + for (Values::const_iterator i = values.begin(); i!=values.end(); ++i) { + (*i)->encode(buffer); + } +} + +void List::decode(Buffer& buffer) +{ + values.clear(); + uint32_t size = buffer.getLong(); + uint32_t available = buffer.available(); + if (available < size) { + throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected " + << size << " bytes but only " << available << " available")); + } + if (size) { + uint32_t count = buffer.getLong(); + for (uint32_t i = 0; i < count; i++) { + ValuePtr value(new FieldValue); + value->decode(buffer); + values.push_back(value); + } + } +} + + +bool List::operator==(const List& other) const { + return values.size() == other.values.size() && + std::equal(values.begin(), values.end(), other.values.begin()); +} + +std::ostream& operator<<(std::ostream& out, const List& l) +{ + out << "{"; + for(List::Values::const_iterator i = l.values.begin(); i != l.values.end(); ++i) { + if (i != l.values.begin()) out << ", "; + (*i)->print(out); + } + return out << "}"; +} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/messaging/Address.cpp b/qpid/cpp/src/qpid/messaging/Address.cpp new file mode 100644 index 0000000000..ed35054a00 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Address.cpp @@ -0,0 +1,49 @@ +/* + * + * 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" + +namespace qpid { +namespace client { +} + +namespace messaging { + +Address::Address() {} +Address::Address(const std::string& address) : value(address) {} +Address::Address(const std::string& address, const std::string& t) : value(address), type(t) {} +Address::operator const std::string&() const { return value; } +const std::string& Address::toStr() const { return value; } +Address::operator bool() const { return !value.empty(); } +bool Address::operator !() const { return value.empty(); } + +const std::string TYPE_SEPARATOR(":"); + +std::ostream& operator<<(std::ostream& out, const Address& address) +{ + if (!address.type.empty()) { + out << address.type; + out << TYPE_SEPARATOR; + } + out << address.value; + return out; +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/Connection.cpp b/qpid/cpp/src/qpid/messaging/Connection.cpp new file mode 100644 index 0000000000..feb6566008 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Connection.cpp @@ -0,0 +1,90 @@ +/* + * + * 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/Connection.h" +#include "qpid/messaging/ConnectionImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/SessionImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Connection> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Connection Connection::open(const std::string& url, const Variant::Map& options) +{ + //only support amqp 0-10 at present + Connection connection(new qpid::client::amqp0_10::ConnectionImpl(url, options)); + return connection; +} + +Connection::Connection(ConnectionImpl* impl) { PI::ctor(*this, impl); } +Connection::Connection(const Connection& c) : qpid::client::Handle<ConnectionImpl>() { PI::copy(*this, c); } +Connection& Connection::operator=(const Connection& c) { return PI::assign(*this, c); } +Connection::~Connection() { PI::dtor(*this); } + +void Connection::close() { impl->close(); } +Session Connection::newSession() { return impl->newSession(); } + +InvalidOptionString::InvalidOptionString(const std::string& msg) : Exception(msg) {} + +void parseKeyValuePair(const std::string& in, Variant::Map& out) +{ + std::string::size_type i = in.find('='); + if (i == std::string::npos || i == in.size() || in.find('=', i+1) != std::string::npos) { + throw InvalidOptionString(QPID_MSG("Cannot parse name-value pair from " << in)); + } else { + out[in.substr(0, i)] = in.substr(i+1); + } +} + +void parseOptionString(const std::string& in, Variant::Map& out) +{ + std::string::size_type start = 0; + std::string::size_type i = in.find('&'); + while (i != std::string::npos) { + parseKeyValuePair(in.substr(start, i-start), out); + if (i < in.size()) { + start = i+1; + i = in.find('&', start); + } else { + i = std::string::npos; + } + } + parseKeyValuePair(in.substr(start), out); +} + +Variant::Map parseOptionString(const std::string& in) +{ + Variant::Map map; + parseOptionString(in, map); + return map; +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/ConnectionImpl.h b/qpid/cpp/src/qpid/messaging/ConnectionImpl.h new file mode 100644 index 0000000000..aa9e5b5fbe --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/ConnectionImpl.h @@ -0,0 +1,45 @@ +#ifndef QPID_MESSAGING_CONNECTIONIMPL_H +#define QPID_MESSAGING_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 <string> +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Session; + +class ConnectionImpl : public virtual qpid::RefCounted +{ + public: + virtual ~ConnectionImpl() {} + virtual void close() = 0; + virtual Session newSession() = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_CONNECTIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Filter.cpp b/qpid/cpp/src/qpid/messaging/Filter.cpp new file mode 100644 index 0000000000..b06cbdb373 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Filter.cpp @@ -0,0 +1,39 @@ +/* + * + * 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/Filter.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +Filter::Filter(std::string t, std::string pattern) : type(t) { patterns.push_back(pattern); } +Filter::Filter(std::string t, std::string pattern1, std::string pattern2) : type(t) +{ + patterns.push_back(pattern1); + patterns.push_back(pattern2); +} + +const std::string Filter::WILDCARD("WILDCARD"); +const std::string Filter::EXACT_MATCH("EXACT_MATCH"); + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/Message.cpp b/qpid/cpp/src/qpid/messaging/Message.cpp new file mode 100644 index 0000000000..e95a05db17 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Message.cpp @@ -0,0 +1,325 @@ +/* + * + * 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/Message.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Codec.h" +#include "qpid/messaging/MessageContent.h" +#include "qpid/messaging/Variant.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +namespace { +const std::string EMPTY_STRING = ""; +} + +struct MessageImpl : MessageContent +{ + Address replyTo; + std::string subject; + std::string contentType; + VariantMap headers; + + std::string bytes; + Variant content;//used only for LIST and MAP + VariantType type;//if LIST, MAP content holds the value; if VOID bytes holds the value + + void* internalId; + + MessageImpl(const std::string& c); + MessageImpl(const char* chars, size_t count); + + void setReplyTo(const Address& d); + const Address& getReplyTo() const; + + void setSubject(const std::string& s); + const std::string& getSubject() const; + + void setContentType(const std::string& s); + const std::string& getContentType() const; + + const VariantMap& getHeaders() const; + VariantMap& getHeaders(); + + void setBytes(const std::string& bytes); + void setBytes(const char* chars, size_t count); + const std::string& getBytes() const; + std::string& getBytes(); + + void setInternalId(void*); + void* getInternalId(); + + bool isVoid() const; + + const std::string& asString() const; + std::string& asString(); + + const char* asChars() const; + size_t size() const; + + const Variant::Map& asMap() const; + Variant::Map& asMap(); + bool isMap() const; + + const Variant::List& asList() const; + Variant::List& asList(); + bool isList() const; + + void clear(); + + void encode(Codec& codec); + void decode(Codec& codec); + + Variant& operator[](const std::string&); + + std::ostream& print(std::ostream& out) const; + + //operator<< for variety of types... + MessageContent& operator<<(const std::string&); + MessageContent& operator<<(const char*); + MessageContent& operator<<(bool); + MessageContent& operator<<(int8_t); + MessageContent& operator<<(int16_t); + MessageContent& operator<<(int32_t); + MessageContent& operator<<(int64_t); + MessageContent& operator<<(uint8_t); + MessageContent& operator<<(uint16_t); + MessageContent& operator<<(uint32_t); + MessageContent& operator<<(uint64_t); + MessageContent& operator<<(double); + MessageContent& operator<<(float); + + //assignment from string, map and list + MessageContent& operator=(const std::string&); + MessageContent& operator=(const char*); + MessageContent& operator=(const Variant::Map&); + MessageContent& operator=(const Variant::List&); + + template <class T> MessageContent& append(T& t); +}; + +MessageImpl::MessageImpl(const std::string& c) : bytes(c), type(VOID), internalId(0) {} +MessageImpl::MessageImpl(const char* chars, size_t count) : bytes(chars, count), type(VOID), internalId(0) {} + +void MessageImpl::setReplyTo(const Address& d) { replyTo = d; } +const Address& MessageImpl::getReplyTo() const { return replyTo; } + +void MessageImpl::setSubject(const std::string& s) { subject = s; } +const std::string& MessageImpl::getSubject() const { return subject; } + +void MessageImpl::setContentType(const std::string& s) { contentType = s; } +const std::string& MessageImpl::getContentType() const { return contentType; } + +const VariantMap& MessageImpl::getHeaders() const { return headers; } +VariantMap& MessageImpl::getHeaders() { return headers; } + +//should these methods be on MessageContent? +void MessageImpl::setBytes(const std::string& c) { clear(); bytes = c; } +void MessageImpl::setBytes(const char* chars, size_t count) { clear(); bytes.assign(chars, count); } +const std::string& MessageImpl::getBytes() const { return bytes; } +std::string& MessageImpl::getBytes() { return bytes; } + + +Variant& MessageImpl::operator[](const std::string& key) { return asMap()[key]; } + +std::ostream& MessageImpl::print(std::ostream& out) const +{ + if (type == MAP) { + return out << content.asMap(); + } else if (type == LIST) { + return out << content.asList(); + } else { + return out << bytes; + } +} + +template <class T> MessageContent& MessageImpl::append(T& t) +{ + if (type == VOID) { + //TODO: this is inefficient, probably want to hold on to the stream object + std::stringstream s; + s << bytes; + s << t; + bytes = s.str(); + } else if (type == LIST) { + content.asList().push_back(Variant(t)); + } else { + throw InvalidConversion("<< operator only valid on strings and lists"); + } + return *this; +} + +MessageContent& MessageImpl::operator<<(const std::string& v) { return append(v); } +MessageContent& MessageImpl::operator<<(const char* v) { return append(v); } +MessageContent& MessageImpl::operator<<(bool v) { return append(v); } +MessageContent& MessageImpl::operator<<(int8_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(int16_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(int32_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(int64_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(uint8_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(uint16_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(uint32_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(uint64_t v) { return append(v); } +MessageContent& MessageImpl::operator<<(double v) { return append(v); } +MessageContent& MessageImpl::operator<<(float v) { return append(v); } +MessageContent& MessageImpl::operator=(const std::string& s) +{ + type = VOID; + bytes = s; + return *this; +} +MessageContent& MessageImpl::operator=(const char* c) +{ + type = VOID; + bytes = c; + return *this; +} +MessageContent& MessageImpl::operator=(const Variant::Map& m) +{ + type = MAP; + content = m; + return *this; +} + +MessageContent& MessageImpl::operator=(const Variant::List& l) +{ + type = LIST; + content = l; + return *this; +} + +void MessageImpl::encode(Codec& codec) +{ + if (content.getType() != VOID) { + bytes = EMPTY_STRING; + codec.encode(content, bytes); + } +} + +void MessageImpl::decode(Codec& codec) +{ + codec.decode(bytes, content); + if (content.getType() == MAP) type = MAP; + else if (content.getType() == LIST) type = LIST; + else type = VOID;//TODO: what if codec set some type other than map or list?? +} + +void MessageImpl::setInternalId(void* i) { internalId = i; } +void* MessageImpl::getInternalId() { return internalId; } + +bool MessageImpl::isVoid() const { return type == VOID; } + +const std::string& MessageImpl::asString() const +{ + if (isVoid()) return getBytes(); + else return content.getString();//will throw an error +} +std::string& MessageImpl::asString() +{ + if (isVoid()) return getBytes(); + else return content.getString();//will throw an error +} + +const char* MessageImpl::asChars() const +{ + if (!isVoid()) throw InvalidConversion("Content is of structured type."); + return bytes.data(); +} +size_t MessageImpl::size() const +{ + return bytes.size(); +} + +const Variant::Map& MessageImpl::asMap() const { return content.asMap(); } +Variant::Map& MessageImpl::asMap() +{ + if (isVoid()) { + content = Variant::Map(); + type = MAP; + } + return content.asMap(); +} +bool MessageImpl::isMap() const { return type == MAP; } + +const Variant::List& MessageImpl::asList() const { return content.asList(); } +Variant::List& MessageImpl::asList() +{ + if (isVoid()) { + content = Variant::List(); + type = LIST; + } + return content.asList(); +} +bool MessageImpl::isList() const { return type == LIST; } + +void MessageImpl::clear() { bytes = EMPTY_STRING; content.reset(); type = VOID; } + + +Message::Message(const std::string& bytes) : impl(new MessageImpl(bytes)) {} +Message::Message(const char* bytes, size_t count) : impl(new MessageImpl(bytes, count)) {} + +Message::Message(const Message& m) : impl(new MessageImpl(m.getBytes())) {} +Message::~Message() { delete impl; } + +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } + +void Message::setReplyTo(const Address& d) { impl->setReplyTo(d); } +const Address& Message::getReplyTo() const { return impl->getReplyTo(); } + +void Message::setSubject(const std::string& s) { impl->setSubject(s); } +const std::string& Message::getSubject() const { return impl->getSubject(); } + +void Message::setContentType(const std::string& s) { impl->setContentType(s); } +const std::string& Message::getContentType() const { return impl->getContentType(); } + +const VariantMap& Message::getHeaders() const { return impl->getHeaders(); } +VariantMap& Message::getHeaders() { return impl->getHeaders(); } + +void Message::setBytes(const std::string& c) { impl->setBytes(c); } +void Message::setBytes(const char* chars, size_t count) { impl->setBytes(chars, count); } +const std::string& Message::getBytes() const { return impl->getBytes(); } +std::string& Message::getBytes() { return impl->getBytes(); } + +const char* Message::getRawContent() const { return impl->getBytes().data(); } +size_t Message::getContentSize() const { return impl->getBytes().size(); } + +MessageContent& Message::getContent() { return *impl; } +const MessageContent& Message::getContent() const { return *impl; } +void Message::setContent(const std::string& s) { *impl = s; } +void Message::setContent(const Variant::Map& m) { *impl = m; } +void Message::setContent(const Variant::List& l) { *impl = l; } + +void Message::encode(Codec& codec) { impl->encode(codec); } + +void Message::decode(Codec& codec) { impl->decode(codec); } + +void Message::setInternalId(void* i) { impl->setInternalId(i); } +void* Message::getInternalId() { return impl->getInternalId(); } + +std::ostream& operator<<(std::ostream& out, const MessageContent& content) +{ + return content.print(out); +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/Receiver.cpp b/qpid/cpp/src/qpid/messaging/Receiver.cpp new file mode 100644 index 0000000000..2e8b89d27f --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Receiver.cpp @@ -0,0 +1,51 @@ +/* + * + * 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/Receiver.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Receiver> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Receiver::Receiver(ReceiverImpl* impl) { PI::ctor(*this, impl); } +Receiver::Receiver(const Receiver& s) : qpid::client::Handle<ReceiverImpl>() { PI::copy(*this, s); } +Receiver::~Receiver() { PI::dtor(*this); } +Receiver& Receiver::operator=(const Receiver& s) { return PI::assign(*this, s); } +bool Receiver::get(Message& message, qpid::sys::Duration timeout) { return impl->get(message, timeout); } +Message Receiver::get(qpid::sys::Duration timeout) { return impl->get(timeout); } +bool Receiver::fetch(Message& message, qpid::sys::Duration timeout) { return impl->fetch(message, timeout); } +Message Receiver::fetch(qpid::sys::Duration timeout) { return impl->fetch(timeout); } +void Receiver::start() { impl->start(); } +void Receiver::stop() { impl->stop(); } +void Receiver::setCapacity(uint32_t c) { impl->setCapacity(c); } +void Receiver::cancel() { impl->cancel(); } +void Receiver::setListener(MessageListener* listener) { impl->setListener(listener); } + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/ReceiverImpl.h b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h new file mode 100644 index 0000000000..77697b730c --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h @@ -0,0 +1,52 @@ +#ifndef QPID_MESSAGING_RECEIVERIMPL_H +#define QPID_MESSAGING_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/RefCounted.h" +#include "qpid/sys/Time.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Message; +class MessageListener; + +class ReceiverImpl : public virtual qpid::RefCounted +{ + public: + virtual ~ReceiverImpl() {} + virtual bool get(Message& message, qpid::sys::Duration timeout) = 0; + virtual Message get(qpid::sys::Duration timeout) = 0; + virtual bool fetch(Message& message, qpid::sys::Duration timeout) = 0; + virtual Message fetch(qpid::sys::Duration timeout) = 0; + virtual void start() = 0; + virtual void stop() = 0; + virtual void setCapacity(uint32_t) = 0; + virtual void cancel() = 0; + virtual void setListener(MessageListener*) = 0; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_RECEIVERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Sender.cpp b/qpid/cpp/src/qpid/messaging/Sender.cpp new file mode 100644 index 0000000000..12a3a8eb0f --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Sender.cpp @@ -0,0 +1,44 @@ +/* + * + * 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/Sender.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Sender> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Sender::Sender(SenderImpl* impl) { PI::ctor(*this, impl); } +Sender::Sender(const Sender& s) : qpid::client::Handle<SenderImpl>() { PI::copy(*this, s); } +Sender::~Sender() { PI::dtor(*this); } +Sender& Sender::operator=(const Sender& s) { return PI::assign(*this, s); } +void Sender::send(Message& message) { impl->send(message); } +void Sender::cancel() { impl->cancel(); } + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/SenderImpl.h b/qpid/cpp/src/qpid/messaging/SenderImpl.h new file mode 100644 index 0000000000..3b61a37423 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/SenderImpl.h @@ -0,0 +1,44 @@ +#ifndef QPID_MESSAGING_SENDERIMPL_H +#define QPID_MESSAGING_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/RefCounted.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Message; + +class SenderImpl : public virtual qpid::RefCounted +{ + public: + virtual ~SenderImpl() {} + virtual void send(Message& message) = 0; + virtual void cancel() = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SENDERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Session.cpp b/qpid/cpp/src/qpid/messaging/Session.cpp new file mode 100644 index 0000000000..284b20dacc --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Session.cpp @@ -0,0 +1,117 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/Session.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Filter.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/SessionImpl.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<qpid::messaging::Session> PI; + +} + +namespace messaging { + +using qpid::client::PI; + +Session::Session(SessionImpl* impl) { PI::ctor(*this, impl); } +Session::Session(const Session& s) : qpid::client::Handle<SessionImpl>() { PI::copy(*this, s); } +Session::~Session() { PI::dtor(*this); } +Session& Session::operator=(const Session& s) { return PI::assign(*this, s); } +void Session::commit() { impl->commit(); } +void Session::rollback() { impl->rollback(); } +void Session::acknowledge() { impl->acknowledge(); } +void Session::reject(Message& m) { impl->reject(m); } +void Session::close() { impl->close(); } + +Sender Session::createSender(const Address& address, const VariantMap& options) +{ + return impl->createSender(address, options); +} +Receiver Session::createReceiver(const Address& address, const VariantMap& options) +{ + return impl->createReceiver(address, options); +} +Receiver Session::createReceiver(const Address& address, const Filter& filter, const VariantMap& options) +{ + return impl->createReceiver(address, filter, options); +} + +Sender Session::createSender(const std::string& address, const VariantMap& options) +{ + return impl->createSender(Address(address), options); +} +Receiver Session::createReceiver(const std::string& address, const VariantMap& options) +{ + return impl->createReceiver(Address(address), options); +} +Receiver Session::createReceiver(const std::string& address, const Filter& filter, const VariantMap& options) +{ + return impl->createReceiver(Address(address), filter, options); +} + +Address Session::createTempQueue(const std::string& baseName) +{ + return impl->createTempQueue(baseName); +} + +void Session::sync() +{ + impl->sync(); +} + +void Session::flush() +{ + impl->flush(); +} + +bool Session::fetch(Message& message, qpid::sys::Duration timeout) +{ + return impl->fetch(message, timeout); +} + +Message Session::fetch(qpid::sys::Duration timeout) +{ + return impl->fetch(timeout); +} + +bool Session::dispatch(qpid::sys::Duration timeout) +{ + return impl->dispatch(timeout); +} + +void* Session::getLastConfirmedSent() +{ + return impl->getLastConfirmedSent(); +} + +void* Session::getLastConfirmedAcknowledged() +{ + return impl->getLastConfirmedAcknowledged(); +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/SessionImpl.h b/qpid/cpp/src/qpid/messaging/SessionImpl.h new file mode 100644 index 0000000000..7a7ce731f8 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/SessionImpl.h @@ -0,0 +1,65 @@ +#ifndef QPID_MESSAGING_SESSIONIMPL_H +#define QPID_MESSAGING_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/RefCounted.h" +#include <string> +#include "qpid/messaging/Variant.h" +#include "qpid/sys/Time.h" + +namespace qpid { +namespace client { +} + +namespace messaging { + +class Address; +class Filter; +class Message; +class Sender; +class Receiver; + +class SessionImpl : public virtual qpid::RefCounted +{ + public: + virtual ~SessionImpl() {} + virtual void commit() = 0; + virtual void rollback() = 0; + virtual void acknowledge() = 0; + virtual void reject(Message&) = 0; + virtual void close() = 0; + virtual void sync() = 0; + virtual void flush() = 0; + virtual bool fetch(Message& message, qpid::sys::Duration timeout) = 0; + virtual Message fetch(qpid::sys::Duration timeout) = 0; + virtual bool dispatch(qpid::sys::Duration timeout) = 0; + virtual Address createTempQueue(const std::string& baseName) = 0; + virtual Sender createSender(const Address& address, const VariantMap& options) = 0; + virtual Receiver createReceiver(const Address& address, const VariantMap& options) = 0; + virtual Receiver createReceiver(const Address& address, const Filter& filter, const VariantMap& options) = 0; + virtual void* getLastConfirmedSent() = 0; + virtual void* getLastConfirmedAcknowledged() = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SESSIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Variant.cpp b/qpid/cpp/src/qpid/messaging/Variant.cpp new file mode 100644 index 0000000000..59770939e1 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Variant.cpp @@ -0,0 +1,603 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/Variant.h" +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace client { +} + +namespace messaging { + +InvalidConversion::InvalidConversion(const std::string& msg) : Exception(msg) {} + + +namespace { +std::string EMPTY; +} + +class VariantImpl +{ + public: + VariantImpl(); + VariantImpl(bool); + VariantImpl(uint8_t); + VariantImpl(uint16_t); + VariantImpl(uint32_t); + VariantImpl(uint64_t); + VariantImpl(int8_t); + VariantImpl(int16_t); + VariantImpl(int32_t); + VariantImpl(int64_t); + VariantImpl(float); + VariantImpl(double); + VariantImpl(const std::string&); + VariantImpl(const Variant::Map&); + VariantImpl(const Variant::List&); + ~VariantImpl(); + + VariantType getType() const; + + bool asBool() const; + uint8_t asUint8() const; + uint16_t asUint16() const; + uint32_t asUint32() const; + uint64_t asUint64() const; + int8_t asInt8() const; + int16_t asInt16() const; + int32_t asInt32() const; + int64_t asInt64() const; + float asFloat() const; + double asDouble() const; + std::string asString() const; + + const Variant::Map& asMap() const; + Variant::Map& asMap(); + const Variant::List& asList() const; + Variant::List& asList(); + + const std::string& getString() const; + std::string& getString(); + + void setEncoding(const std::string&); + const std::string& getEncoding() const; + + static VariantImpl* create(const Variant&); + private: + const VariantType type; + union { + bool b; + uint8_t ui8; + uint16_t ui16; + uint32_t ui32; + uint64_t ui64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + float f; + double d; + void* v;//variable width data + } value; + std::string encoding;//optional encoding for variable length data + + std::string getTypeName(VariantType type) const; + template<class T> T convertFromString() const + { + std::string* s = reinterpret_cast<std::string*>(value.v); + try { + return boost::lexical_cast<T>(*s); + } catch(const boost::bad_lexical_cast&) { + throw InvalidConversion(QPID_MSG("Cannot convert " << *s)); + } + } +}; + + +VariantImpl::VariantImpl() : type(VOID) { value.i64 = 0; } +VariantImpl::VariantImpl(bool b) : type(BOOL) { value.b = b; } +VariantImpl::VariantImpl(uint8_t i) : type(UINT8) { value.ui8 = i; } +VariantImpl::VariantImpl(uint16_t i) : type(UINT16) { value.ui16 = i; } +VariantImpl::VariantImpl(uint32_t i) : type(UINT32) { value.ui32 = i; } +VariantImpl::VariantImpl(uint64_t i) : type(UINT64) { value.ui64 = i; } +VariantImpl::VariantImpl(int8_t i) : type(INT8) { value.i8 = i; } +VariantImpl::VariantImpl(int16_t i) : type(INT16) { value.i16 = i; } +VariantImpl::VariantImpl(int32_t i) : type(INT32) { value.i32 = i; } +VariantImpl::VariantImpl(int64_t i) : type(INT64) { value.i64 = i; } +VariantImpl::VariantImpl(float f) : type(FLOAT) { value.f = f; } +VariantImpl::VariantImpl(double d) : type(DOUBLE) { value.d = d; } +VariantImpl::VariantImpl(const std::string& s) : type(STRING) { value.v = new std::string(s); } +VariantImpl::VariantImpl(const Variant::Map& m) : type(MAP) { value.v = new Variant::Map(m); } +VariantImpl::VariantImpl(const Variant::List& l) : type(LIST) { value.v = new Variant::List(l); } + +VariantImpl::~VariantImpl() { + switch (type) { + case STRING: + delete reinterpret_cast<std::string*>(value.v); + break; + case MAP: + delete reinterpret_cast<Variant::Map*>(value.v); + break; + case LIST: + delete reinterpret_cast<Variant::List*>(value.v); + break; + default: + break; + } +} + +VariantType VariantImpl::getType() const { return type; } + +namespace { + +bool same_char(char a, char b) +{ + return toupper(a) == toupper(b); +} + +bool caseInsensitiveMatch(const std::string& a, const std::string& b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), &same_char); +} + +const std::string TRUE("True"); +const std::string FALSE("False"); + +bool toBool(const std::string& s) +{ + if (caseInsensitiveMatch(s, TRUE)) return true; + if (caseInsensitiveMatch(s, FALSE)) return false; + try { return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) {} + throw InvalidConversion(QPID_MSG("Cannot convert " << s << " to bool")); +} + +} + +bool VariantImpl::asBool() const +{ + switch(type) { + case VOID: return false; + case BOOL: return value.b; + case UINT8: return value.ui8; + case UINT16: return value.ui16; + case UINT32: return value.ui32; + case UINT64: return value.ui64; + case INT8: return value.i8; + case INT16: return value.i16; + case INT32: return value.i32; + case INT64: return value.i64; + case STRING: return toBool(*reinterpret_cast<std::string*>(value.v)); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(BOOL))); + } +} +uint8_t VariantImpl::asUint8() const +{ + switch(type) { + case UINT8: return value.ui8; + case STRING: return convertFromString<uint8_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(UINT8))); + } +} +uint16_t VariantImpl::asUint16() const +{ + switch(type) { + case UINT8: return value.ui8; + case UINT16: return value.ui16; + case STRING: return convertFromString<uint16_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(UINT16))); + } +} +uint32_t VariantImpl::asUint32() const +{ + switch(type) { + case UINT8: return value.ui8; + case UINT16: return value.ui16; + case UINT32: return value.ui32; + case STRING: return convertFromString<uint32_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(UINT32))); + } +} +uint64_t VariantImpl::asUint64() const +{ + switch(type) { + case UINT8: return value.ui8; + case UINT16: return value.ui16; + case UINT32: return value.ui32; + case UINT64: return value.ui64; + case STRING: return convertFromString<uint64_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(UINT64))); + } +} +int8_t VariantImpl::asInt8() const +{ + switch(type) { + case INT8: return value.i8; + case STRING: return convertFromString<int8_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(INT8))); + } +} +int16_t VariantImpl::asInt16() const +{ + switch(type) { + case INT8: return value.i8; + case INT16: return value.i16; + case STRING: return convertFromString<int16_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(INT16))); + } +} +int32_t VariantImpl::asInt32() const +{ + switch(type) { + case INT8: return value.i8; + case INT16: return value.i16; + case INT32: return value.i32; + case STRING: return convertFromString<int32_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(INT32))); + } +} +int64_t VariantImpl::asInt64() const +{ + switch(type) { + case INT8: return value.i8; + case INT16: return value.i16; + case INT32: return value.i32; + case INT64: return value.i64; + case STRING: return convertFromString<int64_t>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(INT64))); + } +} +float VariantImpl::asFloat() const +{ + switch(type) { + case FLOAT: return value.f; + case STRING: return convertFromString<float>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(FLOAT))); + } +} +double VariantImpl::asDouble() const +{ + switch(type) { + case FLOAT: return value.f; + case DOUBLE: return value.d; + case STRING: return convertFromString<double>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(DOUBLE))); + } +} +std::string VariantImpl::asString() const +{ + switch(type) { + case VOID: return EMPTY; + case BOOL: return value.b ? TRUE : FALSE; + case UINT8: return boost::lexical_cast<std::string>((int) value.ui8); + case UINT16: return boost::lexical_cast<std::string>(value.ui16); + case UINT32: return boost::lexical_cast<std::string>(value.ui32); + case UINT64: return boost::lexical_cast<std::string>(value.ui64); + case INT8: return boost::lexical_cast<std::string>((int) value.i8); + case INT16: return boost::lexical_cast<std::string>(value.i16); + case INT32: return boost::lexical_cast<std::string>(value.i32); + case INT64: return boost::lexical_cast<std::string>(value.i64); + case DOUBLE: return boost::lexical_cast<std::string>(value.d); + case FLOAT: return boost::lexical_cast<std::string>(value.f); + case STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(STRING))); + } +} + +const Variant::Map& VariantImpl::asMap() const +{ + switch(type) { + case MAP: return *reinterpret_cast<Variant::Map*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(MAP))); + } +} + +Variant::Map& VariantImpl::asMap() +{ + switch(type) { + case MAP: return *reinterpret_cast<Variant::Map*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(MAP))); + } +} + +const Variant::List& VariantImpl::asList() const +{ + switch(type) { + case LIST: return *reinterpret_cast<Variant::List*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(LIST))); + } +} + +Variant::List& VariantImpl::asList() +{ + switch(type) { + case LIST: return *reinterpret_cast<Variant::List*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(LIST))); + } +} + +std::string& VariantImpl::getString() +{ + switch(type) { + case STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); + } +} + +const std::string& VariantImpl::getString() const +{ + switch(type) { + case STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); + } +} + +void VariantImpl::setEncoding(const std::string& s) { encoding = s; } +const std::string& VariantImpl::getEncoding() const { return encoding; } + +std::string VariantImpl::getTypeName(VariantType type) const +{ + switch (type) { + case VOID: return "void"; + case BOOL: return "bool"; + case UINT8: return "uint8"; + case UINT16: return "uint16"; + case UINT32: return "uint32"; + case UINT64: return "uint64"; + case INT8: return "int8"; + case INT16: return "int16"; + case INT32: return "int32"; + case INT64: return "int64"; + case FLOAT: return "float"; + case DOUBLE: return "double"; + case STRING: return "string"; + case MAP: return "map"; + case LIST: return "list"; + } + return "<unknown>";//should never happen +} + +VariantImpl* VariantImpl::create(const Variant& v) +{ + switch (v.getType()) { + case BOOL: return new VariantImpl(v.asBool()); + case UINT8: return new VariantImpl(v.asUint8()); + case UINT16: return new VariantImpl(v.asUint16()); + case UINT32: return new VariantImpl(v.asUint32()); + case UINT64: return new VariantImpl(v.asUint64()); + case INT8: return new VariantImpl(v.asInt8()); + case INT16: return new VariantImpl(v.asInt16()); + case INT32: return new VariantImpl(v.asInt32()); + case INT64: return new VariantImpl(v.asInt64()); + case FLOAT: return new VariantImpl(v.asFloat()); + case DOUBLE: return new VariantImpl(v.asDouble()); + case STRING: return new VariantImpl(v.asString()); + case MAP: return new VariantImpl(v.asMap()); + case LIST: return new VariantImpl(v.asList()); + default: return new VariantImpl(); + } +} + +Variant::Variant() : impl(new VariantImpl()) {} +Variant::Variant(bool b) : impl(new VariantImpl(b)) {} +Variant::Variant(uint8_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint16_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint32_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint64_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int8_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int16_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int32_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int64_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(float f) : impl(new VariantImpl(f)) {} +Variant::Variant(double d) : impl(new VariantImpl(d)) {} +Variant::Variant(const std::string& s) : impl(new VariantImpl(s)) {} +Variant::Variant(const char* s) : impl(new VariantImpl(std::string(s))) {} +Variant::Variant(const Map& m) : impl(new VariantImpl(m)) {} +Variant::Variant(const List& l) : impl(new VariantImpl(l)) {} +Variant::Variant(const Variant& v) : impl(VariantImpl::create(v)) {} + +Variant::~Variant() { if (impl) delete impl; } + +void Variant::reset() +{ + if (impl) delete impl; + impl = new VariantImpl(); +} + + +Variant& Variant::operator=(bool b) +{ + if (impl) delete impl; + impl = new VariantImpl(b); + return *this; +} + +Variant& Variant::operator=(uint8_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint16_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint32_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint64_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} + +Variant& Variant::operator=(int8_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int16_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int32_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int64_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} + +Variant& Variant::operator=(float f) +{ + if (impl) delete impl; + impl = new VariantImpl(f); + return *this; +} +Variant& Variant::operator=(double d) +{ + if (impl) delete impl; + impl = new VariantImpl(d); + return *this; +} + +Variant& Variant::operator=(const std::string& s) +{ + if (impl) delete impl; + impl = new VariantImpl(s); + return *this; +} + +Variant& Variant::operator=(const char* s) +{ + if (impl) delete impl; + impl = new VariantImpl(std::string(s)); + return *this; +} + +Variant& Variant::operator=(const Map& m) +{ + if (impl) delete impl; + impl = new VariantImpl(m); + return *this; +} + +Variant& Variant::operator=(const List& l) +{ + if (impl) delete impl; + impl = new VariantImpl(l); + return *this; +} + +Variant& Variant::operator=(const Variant& v) +{ + if (impl) delete impl; + impl = VariantImpl::create(v); + return *this; +} + +VariantType Variant::getType() const { return impl->getType(); } +bool Variant::asBool() const { return impl->asBool(); } +uint8_t Variant::asUint8() const { return impl->asUint8(); } +uint16_t Variant::asUint16() const { return impl->asUint16(); } +uint32_t Variant::asUint32() const { return impl->asUint32(); } +uint64_t Variant::asUint64() const { return impl->asUint64(); } +int8_t Variant::asInt8() const { return impl->asInt8(); } +int16_t Variant::asInt16() const { return impl->asInt16(); } +int32_t Variant::asInt32() const { return impl->asInt32(); } +int64_t Variant::asInt64() const { return impl->asInt64(); } +float Variant::asFloat() const { return impl->asFloat(); } +double Variant::asDouble() const { return impl->asDouble(); } +std::string Variant::asString() const { return impl->asString(); } +const Variant::Map& Variant::asMap() const { return impl->asMap(); } +Variant::Map& Variant::asMap() { return impl->asMap(); } +const Variant::List& Variant::asList() const { return impl->asList(); } +Variant::List& Variant::asList() { return impl->asList(); } +const std::string& Variant::getString() const { return impl->getString(); } +std::string& Variant::getString() { return impl->getString(); } +void Variant::setEncoding(const std::string& s) { impl->setEncoding(s); } +const std::string& Variant::getEncoding() const { return impl->getEncoding(); } + +Variant::operator bool() const { return asBool(); } +Variant::operator uint8_t() const { return asUint8(); } +Variant::operator uint16_t() const { return asUint16(); } +Variant::operator uint32_t() const { return asUint32(); } +Variant::operator uint64_t() const { return asUint64(); } +Variant::operator int8_t() const { return asInt8(); } +Variant::operator int16_t() const { return asInt16(); } +Variant::operator int32_t() const { return asInt32(); } +Variant::operator int64_t() const { return asInt64(); } +Variant::operator float() const { return asFloat(); } +Variant::operator double() const { return asDouble(); } +Variant::operator const char*() const { return asString().c_str(); } + +std::ostream& operator<<(std::ostream& out, const Variant::Map& map) +{ + for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) { + if (i != map.begin()) out << ", "; + out << i->first << ":" << i->second; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const Variant::List& list) +{ + for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + if (i != list.begin()) out << ", "; + out << *i; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const Variant& value) +{ + switch (value.getType()) { + case MAP: + out << "{" << value.asMap() << "}"; + break; + case LIST: + out << "[" << value.asList() << "]"; + break; + case VOID: + out << "<void>"; + break; + default: + out << value.asString(); + break; + } + return out; +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/sys/Timer.cpp b/qpid/cpp/src/qpid/sys/Timer.cpp index d569fb3bb7..3d2a900455 100644 --- a/qpid/cpp/src/qpid/sys/Timer.cpp +++ b/qpid/cpp/src/qpid/sys/Timer.cpp @@ -84,6 +84,7 @@ Timer::~Timer() stop(); } +// TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary void Timer::run() { Monitor::ScopedLock l(monitor); @@ -111,8 +112,8 @@ void Timer::run() // Warn on callback overrun AbsTime end(AbsTime::now()); Duration overrun(tasks.top()->nextFireTime, end); - bool late = delay > 1 * TIME_MSEC; - bool overran = overrun > 1 * TIME_MSEC; + bool late = delay > 10 * TIME_MSEC; + bool overran = overrun > 2 * TIME_MSEC; if (late) if (overran) { QPID_LOG(warning, diff --git a/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp b/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp index dcd2f71a65..8905b87838 100644 --- a/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp +++ b/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp @@ -45,19 +45,6 @@ namespace { typedef qpid::sys::ScopedLock<qpid::sys::Mutex> QLock; /* - * We keep per thread state to avoid locking overhead. The assumption is that - * on average all the connections are serviced by all the threads so the state - * recorded in each thread is about the same. If this turns out not to be the - * case we could rebalance the info occasionally. - */ -QPID_TSS int threadReadTotal = 0; -QPID_TSS int threadMaxRead = 0; -QPID_TSS int threadReadCount = 0; -QPID_TSS int threadWriteTotal = 0; -QPID_TSS int threadWriteCount = 0; -QPID_TSS int64_t threadMaxReadTimeNs = 2 * 1000000; // start at 2ms - -/* * The function pointers for AcceptEx and ConnectEx need to be looked up * at run time. Make sure this is done only once. */ @@ -642,12 +629,10 @@ void AsynchIO::close(void) { } void AsynchIO::readComplete(AsynchReadResult *result) { - ++threadReadCount; int status = result->getStatus(); size_t bytes = result->getTransferred(); if (status == 0 && bytes > 0) { bool restartRead = true; // May not if receiver doesn't want more - threadReadTotal += bytes; if (readCallback) restartRead = readCallback(*this, result->getBuff()); if (restartRead) @@ -674,10 +659,8 @@ void AsynchIO::writeComplete(AsynchWriteResult *result) { size_t bytes = result->getTransferred(); AsynchIO::BufferBase *buff = result->getBuff(); if (buff != 0) { - ++threadWriteCount; writeInProgress = false; if (status == 0 && bytes > 0) { - threadWriteTotal += bytes; if (bytes < result->getRequested()) // Still more to go; resubmit startWrite(buff); else diff --git a/qpid/cpp/src/qpidd.cpp b/qpid/cpp/src/qpidd.cpp index 9c3c6b0c4b..1839a62205 100644 --- a/qpid/cpp/src/qpidd.cpp +++ b/qpid/cpp/src/qpidd.cpp @@ -77,7 +77,7 @@ int main(int argc, char* argv[]) return broker.execute(options.get()); } catch(const exception& e) { - QPID_LOG(critical, "Broker start-up failed: " << e.what()); + QPID_LOG(critical, "Unexpected error: " << e.what()); } return 1; } diff --git a/qpid/cpp/src/tests/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt index 34f5d35a9a..56a4aaf2b0 100644 --- a/qpid/cpp/src/tests/CMakeLists.txt +++ b/qpid/cpp/src/tests/CMakeLists.txt @@ -95,6 +95,7 @@ set(unit_tests_to_build InlineAllocator InlineVector ClientSessionTest + MessagingSessionTest SequenceSet StringUtils IncompleteMessageList @@ -128,6 +129,7 @@ set(unit_tests_to_build ReplicationTest ClientMessageTest PollableCondition + Variant ${xml_tests} CACHE STRING "Which unit tests to build" ) diff --git a/qpid/cpp/src/tests/FieldTable.cpp b/qpid/cpp/src/tests/FieldTable.cpp index a02bbd5194..5b43871f6d 100644 --- a/qpid/cpp/src/tests/FieldTable.cpp +++ b/qpid/cpp/src/tests/FieldTable.cpp @@ -22,6 +22,7 @@ #include "qpid/framing/Array.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" #include "qpid/sys/alloca.h" #include "unit_test.h" @@ -86,7 +87,9 @@ QPID_AUTO_TEST_CASE(testAssignment) QPID_AUTO_TEST_CASE(testNestedValues) { - char buff[100]; + double d = 1.2345; + uint32_t u = 101; + char buff[1000]; { FieldTable a; FieldTable b; @@ -94,11 +97,17 @@ QPID_AUTO_TEST_CASE(testNestedValues) items.push_back("one"); items.push_back("two"); Array c(items); + List list; + list.push_back(List::ValuePtr(new Str16Value("red"))); + list.push_back(List::ValuePtr(new Unsigned32Value(u))); + list.push_back(List::ValuePtr(new Str8Value("yellow"))); + list.push_back(List::ValuePtr(new DoubleValue(d))); a.setString("id", "A"); b.setString("id", "B"); a.setTable("B", b); a.setArray("C", c); + a.set("my-list", FieldTable::ValuePtr(new ListValue(list))); Buffer wbuffer(buff, 100); @@ -119,6 +128,27 @@ QPID_AUTO_TEST_CASE(testNestedValues) BOOST_CHECK((uint) 2 == items.size()); BOOST_CHECK(string("one") == items[0]); BOOST_CHECK(string("two") == items[1]); + + List list; + BOOST_CHECK(a.get("my-list")->get<List>(list)); + List::const_iterator i = list.begin(); + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(std::string("red"), (*i)->get<std::string>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(u, (uint32_t) (*i)->get<int>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(std::string("yellow"), (*i)->get<std::string>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(d, (*i)->get<double>()); + + i++; + BOOST_CHECK(i == list.end()); } } diff --git a/qpid/cpp/src/tests/Makefile.am b/qpid/cpp/src/tests/Makefile.am index db4c8ba914..2e04c85b93 100644 --- a/qpid/cpp/src/tests/Makefile.am +++ b/qpid/cpp/src/tests/Makefile.am @@ -65,6 +65,7 @@ unit_test_LDADD=-lboost_unit_test_framework -lboost_regex \ $(lib_client) $(lib_broker) $(lib_console) unit_test_SOURCES= unit_test.cpp unit_test.h \ + MessagingSessionTests.cpp \ ClientSessionTest.cpp \ BrokerFixture.h SocketProxy.h \ exception_test.cpp \ @@ -111,7 +112,8 @@ unit_test_SOURCES= unit_test.cpp unit_test.h \ FrameDecoder.cpp \ ReplicationTest.cpp \ ClientMessageTest.cpp \ - PollableCondition.cpp + PollableCondition.cpp \ + Variant.cpp if HAVE_XML unit_test_SOURCES+= XmlClientSessionTest.cpp diff --git a/qpid/cpp/src/tests/MessagingSessionTests.cpp b/qpid/cpp/src/tests/MessagingSessionTests.cpp new file mode 100644 index 0000000000..6eebf717bf --- /dev/null +++ b/qpid/cpp/src/tests/MessagingSessionTests.cpp @@ -0,0 +1,325 @@ +/* + * + * 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 "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageListener.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Session.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/Time.h" +#include <boost/assign.hpp> +#include <boost/format.hpp> +#include <string> +#include <vector> + +QPID_AUTO_TEST_SUITE(MessagingSessionTests) + +using namespace qpid::messaging; +using namespace qpid; +using qpid::broker::Broker; + +struct BrokerAdmin +{ + qpid::client::Connection connection; + qpid::client::Session session; + + BrokerAdmin(uint16_t port) + { + connection.open("localhost", port); + session = connection.newSession(); + } + + void createQueue(const std::string& name) + { + session.queueDeclare(qpid::client::arg::queue=name); + } + + void deleteQueue(const std::string& name) + { + session.queueDelete(qpid::client::arg::queue=name); + } + + void createExchange(const std::string& name, const std::string& type) + { + session.exchangeDeclare(qpid::client::arg::exchange=name, qpid::client::arg::type=type); + } + + void deleteExchange(const std::string& name) + { + session.exchangeDelete(qpid::client::arg::exchange=name); + } + + ~BrokerAdmin() + { + session.close(); + connection.close(); + } +}; + +struct MessagingFixture : public BrokerFixture +{ + Connection connection; + Session session; + BrokerAdmin admin; + + MessagingFixture(Broker::Options opts = Broker::Options()) : + BrokerFixture(opts), + connection(Connection::open((boost::format("amqp:tcp:localhost:%1%") % (broker->getPort(Broker::TCP_TRANSPORT))).str())), + session(connection.newSession()), + admin(broker->getPort(Broker::TCP_TRANSPORT)) {} + + ~MessagingFixture() + { + session.close(); + connection.close(); + } +}; + +struct QueueFixture : MessagingFixture +{ + std::string queue; + + QueueFixture(const std::string& name = "test-queue") : queue(name) + { + admin.createQueue(queue); + } + + ~QueueFixture() + { + admin.deleteQueue(queue); + } + +}; + +struct TopicFixture : MessagingFixture +{ + std::string topic; + + TopicFixture(const std::string& name = "test-topic", const std::string& type="fanout") : topic(name) + { + admin.createExchange(topic, type); + } + + ~TopicFixture() + { + admin.deleteExchange(topic); + } + +}; + +struct MultiQueueFixture : MessagingFixture +{ + typedef std::vector<std::string>::const_iterator const_iterator; + std::vector<std::string> queues; + + MultiQueueFixture(const std::vector<std::string>& names = boost::assign::list_of<std::string>("q1")("q2")("q3")) : queues(names) + { + for (const_iterator i = queues.begin(); i != queues.end(); ++i) { + admin.createQueue(*i); + } + } + + ~MultiQueueFixture() + { + for (const_iterator i = queues.begin(); i != queues.end(); ++i) { + admin.deleteQueue(*i); + } + } + +}; + +struct MessageDataCollector : MessageListener +{ + std::vector<std::string> messageData; + + void received(Message& message) { + messageData.push_back(message.getBytes()); + } +}; + +std::vector<std::string> fetch(Receiver& receiver, int count, qpid::sys::Duration timeout=qpid::sys::TIME_SEC*5) +{ + std::vector<std::string> data; + Message message; + for (int i = 0; i < count && receiver.fetch(message, timeout); i++) { + data.push_back(message.getBytes()); + } + return data; +} + +QPID_AUTO_TEST_CASE(testSimpleSendReceive) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * qpid::sys::TIME_SEC); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getBytes(), out.getBytes()); +} + +QPID_AUTO_TEST_CASE(testSenderError) +{ + MessagingFixture fix; + //TODO: this is the wrong type for the exception; define explicit set in messaging namespace + BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress"), qpid::framing::NotFoundException); +} + +QPID_AUTO_TEST_CASE(testReceiverError) +{ + MessagingFixture fix; + //TODO: this is the wrong type for the exception; define explicit set in messaging namespace + BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress"), qpid::framing::NotFoundException); +} + +QPID_AUTO_TEST_CASE(testSimpleTopic) +{ + TopicFixture fix; + + Sender sender = fix.session.createSender(fix.topic); + Message msg("one"); + sender.send(msg); + Receiver sub1 = fix.session.createReceiver(fix.topic); + sub1.setCapacity(10u); + sub1.start(); + msg.setBytes("two"); + sender.send(msg); + Receiver sub2 = fix.session.createReceiver(fix.topic); + sub2.setCapacity(10u); + sub2.start(); + msg.setBytes("three"); + sender.send(msg); + Receiver sub3 = fix.session.createReceiver(fix.topic); + sub3.setCapacity(10u); + sub3.start(); + msg.setBytes("four"); + sender.send(msg); + BOOST_CHECK_EQUAL(fetch(sub2, 2), boost::assign::list_of<std::string>("three")("four")); + sub2.cancel(); + + msg.setBytes("five"); + sender.send(msg); + BOOST_CHECK_EQUAL(fetch(sub1, 4), boost::assign::list_of<std::string>("two")("three")("four")("five")); + BOOST_CHECK_EQUAL(fetch(sub3, 2), boost::assign::list_of<std::string>("four")("five")); + Message in; + BOOST_CHECK(!sub2.fetch(in, 0));//TODO: or should this raise an error? + + + //TODO: check pending messages... +} + +QPID_AUTO_TEST_CASE(testSessionFetch) +{ + MultiQueueFixture fix; + + for (uint i = 0; i < fix.queues.size(); i++) { + Receiver r = fix.session.createReceiver(fix.queues[i]); + r.setCapacity(10u); + r.start();//TODO: add Session::start + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Sender s = fix.session.createSender(fix.queues[i]); + Message msg((boost::format("Message_%1%") % (i+1)).str()); + s.send(msg); + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Message msg; + BOOST_CHECK(fix.session.fetch(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getBytes(), (boost::format("Message_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testSessionDispatch) +{ + MultiQueueFixture fix; + + MessageDataCollector collector; + for (uint i = 0; i < fix.queues.size(); i++) { + Receiver r = fix.session.createReceiver(fix.queues[i]); + r.setListener(&collector); + r.setCapacity(10u); + r.start();//TODO: add Session::start + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Sender s = fix.session.createSender(fix.queues[i]); + Message msg((boost::format("Message_%1%") % (i+1)).str()); + s.send(msg); + } + + while (fix.session.dispatch(qpid::sys::TIME_SEC)) ; + + BOOST_CHECK_EQUAL(collector.messageData, boost::assign::list_of<std::string>("Message_1")("Message_2")("Message_3")); +} + + +QPID_AUTO_TEST_CASE(testMapMessage) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + out.getContent().asMap()["abc"] = "def"; + out.getContent().asMap()["pi"] = 3.14f; + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(in.getBytes(), out.getBytes()); + BOOST_CHECK_EQUAL(in.getContent().asMap()["abc"].asString(), "def"); + BOOST_CHECK_EQUAL(in.getContent().asMap()["pi"].asFloat(), 3.14f); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testListMessage) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + out.getContent() = Variant::List(); + out.getContent() << "abc"; + out.getContent() << 1234; + out.getContent() << "def"; + out.getContent() << 56.789; + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(in.getBytes(), out.getBytes()); + Variant::List& list = in.getContent().asList(); + BOOST_CHECK_EQUAL(list.size(), out.getContent().asList().size()); + BOOST_CHECK_EQUAL(list.front().asString(), "abc"); + list.pop_front(); + BOOST_CHECK_EQUAL(list.front().asInt64(), 1234); + list.pop_front(); + BOOST_CHECK_EQUAL(list.front().asString(), "def"); + list.pop_front(); + BOOST_CHECK_EQUAL(list.front().asDouble(), 56.789); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/Variant.cpp b/qpid/cpp/src/tests/Variant.cpp new file mode 100644 index 0000000000..1bf2ed98ce --- /dev/null +++ b/qpid/cpp/src/tests/Variant.cpp @@ -0,0 +1,157 @@ +/* + * + * 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 <iostream> +#include "qpid/messaging/Variant.h" + +#include "unit_test.h" + +using namespace qpid::messaging; + +QPID_AUTO_TEST_SUITE(VariantSuite) + +QPID_AUTO_TEST_CASE(testConversions) +{ + Variant value; + + //string to float/double + value = "1.5"; + BOOST_CHECK_EQUAL((float) 1.5, value.asFloat()); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + + //float to string or double + value = 1.5f; + BOOST_CHECK_EQUAL((float) 1.5, value.asFloat()); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + BOOST_CHECK_EQUAL(std::string("1.5"), value.asString()); + + //double to string (conversion to float not valid) + value = 1.5; + BOOST_CHECK_THROW(value.asFloat(), InvalidConversion); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + BOOST_CHECK_EQUAL(std::string("1.5"), value.asString()); + + //uint8 to larger unsigned ints and string + value = (uint8_t) 7; + BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64()); + BOOST_CHECK_EQUAL(std::string("7"), value.asString()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + + value = (uint16_t) 8; + BOOST_CHECK_EQUAL(std::string("8"), value.asString()); + value = (uint32_t) 9; + BOOST_CHECK_EQUAL(std::string("9"), value.asString()); + + //uint32 to larger unsigned ints and string + value = (uint32_t) 9999999; + BOOST_CHECK_EQUAL((uint32_t) 9999999, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 9999999, value.asUint64()); + BOOST_CHECK_EQUAL(std::string("9999999"), value.asString()); + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + + value = "true"; + BOOST_CHECK(value.asBool()); + value = "false"; + BOOST_CHECK(!value.asBool()); + value = "1"; + BOOST_CHECK(value.asBool()); + value = "0"; + BOOST_CHECK(!value.asBool()); + value = "other"; + BOOST_CHECK_THROW(value.asBool(), InvalidConversion); +} + +QPID_AUTO_TEST_CASE(testAssignment) +{ + Variant value("abc"); + Variant other = value; + BOOST_CHECK_EQUAL(STRING, value.getType()); + BOOST_CHECK_EQUAL(other.getType(), value.getType()); + BOOST_CHECK_EQUAL(other.asString(), value.asString()); + + const uint32_t i(1000); + value = i; + BOOST_CHECK_EQUAL(UINT32, value.getType()); + BOOST_CHECK_EQUAL(STRING, other.getType()); +} + +QPID_AUTO_TEST_CASE(testList) +{ + const std::string s("abc"); + const float f(9.876); + const int16_t x(1000); + + Variant value = Variant::List(); + value.asList().push_back(Variant(s)); + value.asList().push_back(Variant(f)); + value.asList().push_back(Variant(x)); + BOOST_CHECK_EQUAL(3u, value.asList().size()); + Variant::List::const_iterator i = value.asList().begin(); + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(STRING, i->getType()); + BOOST_CHECK_EQUAL(s, i->asString()); + i++; + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(FLOAT, i->getType()); + BOOST_CHECK_EQUAL(f, i->asFloat()); + i++; + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(INT16, i->getType()); + BOOST_CHECK_EQUAL(x, i->asInt16()); + i++; + + BOOST_CHECK(i == value.asList().end()); +} + +QPID_AUTO_TEST_CASE(testMap) +{ + const std::string red("red"); + const float pi(3.14); + const int16_t x(1000); + + Variant value = Variant::Map(); + value.asMap()["colour"] = red; + value.asMap()["pi"] = pi; + value.asMap()["my-key"] = x; + BOOST_CHECK_EQUAL(3u, value.asMap().size()); + + BOOST_CHECK_EQUAL(STRING, value.asMap()["colour"].getType()); + BOOST_CHECK_EQUAL(red, value.asMap()["colour"].asString()); + + BOOST_CHECK_EQUAL(FLOAT, value.asMap()["pi"].getType()); + BOOST_CHECK_EQUAL(pi, value.asMap()["pi"].asFloat()); + + BOOST_CHECK_EQUAL(INT16, value.asMap()["my-key"].getType()); + BOOST_CHECK_EQUAL(x, value.asMap()["my-key"].asInt16()); + + value.asMap()["my-key"] = "now it's a string"; + BOOST_CHECK_EQUAL(STRING, value.asMap()["my-key"].getType()); + BOOST_CHECK_EQUAL(std::string("now it's a string"), value.asMap()["my-key"].asString()); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/cli_tests.py b/qpid/cpp/src/tests/cli_tests.py index 2af1a20a87..4309b66271 100755 --- a/qpid/cpp/src/tests/cli_tests.py +++ b/qpid/cpp/src/tests/cli_tests.py @@ -123,6 +123,28 @@ class CliTests(TestBase010): found = True self.assertEqual(found, False) + def test_qpid_config_altex(self): + self.startQmf(); + qmf = self.qmf + exName = "testalt" + altName = "amq.direct" + + ret = os.system(self.command(" add exchange topic %s --alternate-exchange=%s" % (exName, altName))) + self.assertEqual(ret, 0) + + exchanges = qmf.getObjects(_class="exchange") + found = False + for exchange in exchanges: + if exchange.name == altName: + self.assertEqual(exchange.altExchange, None) + + if exchange.name == exName: + found = True + if not exchange.altExchange: + self.fail("Alternate exchange not set") + self.assertEqual(exchange._altExchange_.name, altName) + self.assertEqual(found, True) + def test_qpid_route(self): self.startQmf(); qmf = self.qmf diff --git a/qpid/cpp/src/tests/federation.py b/qpid/cpp/src/tests/federation.py index daf831f3ed..174932adf8 100755 --- a/qpid/cpp/src/tests/federation.py +++ b/qpid/cpp/src/tests/federation.py @@ -32,6 +32,17 @@ class FederationTests(TestBase010): def remote_port(self): return int(self.defines["remote-port"]) + def verify_cleanup(self): + attempts = 0 + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + while total > 0: + attempts += 1 + if attempts >= 10: + self.fail("Bridges and links didn't clean up") + return + sleep(1) + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + def test_bridge_create_and_close(self): self.startQmf(); qmf = self.qmf @@ -51,9 +62,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_pull_from_exchange(self): session = self.session @@ -98,9 +107,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_push_to_exchange(self): session = self.session @@ -144,9 +151,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_pull_from_queue(self): session = self.session @@ -199,9 +204,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_tracing_automatic(self): remoteUrl = "%s:%d" % (self.remote_host(), self.remote_port()) @@ -312,9 +315,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_dynamic_fanout(self): session = self.session @@ -358,9 +359,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_dynamic_direct(self): @@ -405,10 +404,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) - + self.verify_cleanup() def test_dynamic_topic(self): session = self.session @@ -452,9 +448,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) + self.verify_cleanup() def test_dynamic_topic_reorigin(self): session = self.session @@ -512,11 +506,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) - - + self.verify_cleanup() def test_dynamic_direct_reorigin(self): session = self.session @@ -574,11 +564,7 @@ class FederationTests(TestBase010): result = link.close() self.assertEqual(result.status, 0) - sleep(3) - self.assertEqual(len(qmf.getObjects(_class="bridge")), 0) - self.assertEqual(len(qmf.getObjects(_class="link")), 0) - - + self.verify_cleanup() def getProperty(self, msg, name): for h in msg.headers: diff --git a/qpid/cpp/src/tests/perftest.cpp b/qpid/cpp/src/tests/perftest.cpp index f55528fcd5..d383e0eb80 100644 --- a/qpid/cpp/src/tests/perftest.cpp +++ b/qpid/cpp/src/tests/perftest.cpp @@ -75,6 +75,7 @@ struct Opts : public TestOptions { // Queue policy uint32_t queueMaxCount; uint64_t queueMaxSize; + std::string baseName; bool queueDurable; // Publisher @@ -106,8 +107,8 @@ struct Opts : public TestOptions { static const std::string helpText; Opts() : - TestOptions(helpText), - setup(false), control(false), publish(false), subscribe(false), + TestOptions(helpText), + setup(false), control(false), publish(false), subscribe(false), baseName("perftest"), pubs(1), count(500000), size(1024), confirm(true), durable(false), uniqueData(false), syncPub(false), subs(1), ack(0), qt(1),singleConnect(false), iterations(1), mode(SHARED), summary(false), @@ -144,6 +145,7 @@ struct Opts : public TestOptions { ("queue-max-count", optValue(queueMaxCount, "N"), "queue policy: count to trigger 'flow to disk'") ("queue-max-size", optValue(queueMaxSize, "N"), "queue policy: accumulated size to trigger 'flow to disk'") + ("base-name", optValue(baseName, "NAME"), "base name used for queues or topics") ("queue-durable", optValue(queueDurable, "N"), "Make queue durable (implied if durable set)") ("interval_sub", optValue(intervalSub, "ms"), ">=0 delay between msg consume") @@ -219,6 +221,13 @@ const std::string Opts::helpText= Opts opts; Connection globalConnection; +std::string fqn(const std::string& name) +{ + ostringstream fqn; + fqn << opts.baseName << "_" << name; + return fqn.str(); +} + struct Client : public Runnable { Connection* connection; Connection localConnection; @@ -257,18 +266,18 @@ struct Setup : public Client { } void run() { - queueInit("pub_start"); - queueInit("pub_done"); - queueInit("sub_ready"); - queueInit("sub_done"); - if (opts.iterations > 1) queueInit("sub_iteration"); + queueInit(fqn("pub_start")); + queueInit(fqn("pub_done")); + queueInit(fqn("sub_ready")); + queueInit(fqn("sub_done")); + if (opts.iterations > 1) queueInit(fqn("sub_iteration")); if (opts.mode==SHARED) { framing::FieldTable settings;//queue policy settings settings.setInt("qpid.max_count", opts.queueMaxCount); settings.setInt("qpid.max_size", opts.queueMaxSize); for (size_t i = 0; i < opts.qt; ++i) { ostringstream qname; - qname << "perftest" << i; + qname << opts.baseName << i; queueInit(qname.str(), opts.durable || opts.queueDurable, settings); } } @@ -384,13 +393,13 @@ struct Controller : public Client { void run() { // Controller try { // Wait for subscribers to be ready. - process(opts.totalSubs, "sub_ready", bind(expect, _1, "ready")); + process(opts.totalSubs, fqn("sub_ready"), bind(expect, _1, "ready")); LocalQueue pubDone; LocalQueue subDone; subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false); - subs.subscribe(pubDone, "pub_done"); - subs.subscribe(subDone, "sub_done"); + subs.subscribe(pubDone, fqn("pub_done")); + subs.subscribe(subDone, fqn("sub_done")); double txrateTotal(0); double mbytesTotal(0); @@ -399,16 +408,16 @@ struct Controller : public Client { for (size_t j = 0; j < opts.iterations; ++j) { AbsTime start=now(); - send(opts.totalPubs, "pub_start", "start"); // Start publishers + send(opts.totalPubs, fqn("pub_start"), "start"); // Start publishers if (j) { - send(opts.totalPubs, "sub_iteration", "next"); // Start subscribers on next iteration + send(opts.totalPubs, fqn("sub_iteration"), "next"); // Start subscribers on next iteration } Stats pubRates; Stats subRates; - process(opts.totalPubs, pubDone, "pub_done", boost::ref(pubRates)); - process(opts.totalSubs, subDone, "sub_done", boost::ref(subRates)); + process(opts.totalPubs, pubDone, fqn("pub_done"), boost::ref(pubRates)); + process(opts.totalSubs, subDone, fqn("sub_done"), boost::ref(subRates)); AbsTime end=now(); @@ -497,7 +506,7 @@ struct PublishThread : public Client { SubscriptionManager subs(session); LocalQueue lq; subs.setFlowControl(1, SubscriptionManager::UNLIMITED, true); - subs.subscribe(lq, "pub_start"); + subs.subscribe(lq, fqn("pub_start")); for (size_t j = 0; j < opts.iterations; ++j) { expect(lq.pop().getData(), "start"); @@ -533,7 +542,7 @@ struct PublishThread : public Client { double time=secs(start,end); // Send result to controller. - Message report(lexical_cast<string>(opts.count/time), "pub_done"); + Message report(lexical_cast<string>(opts.count/time), fqn("pub_done")); session.messageTransfer(arg::content=report, arg::acceptMode=1); if (opts.txPub){ sync(session).txCommit(); @@ -587,7 +596,7 @@ struct SubscribeThread : public Client { LocalQueue lq; Subscription subscription = subs.subscribe(lq, queue, settings); // Notify controller we are ready. - session.messageTransfer(arg::content=Message("ready", "sub_ready"), arg::acceptMode=1); + session.messageTransfer(arg::content=Message("ready", fqn("sub_ready")), arg::acceptMode=1); if (opts.txSub) { if (opts.commitAsync) session.txCommit(); else sync(session).txCommit(); @@ -595,13 +604,13 @@ struct SubscribeThread : public Client { LocalQueue iterationControl; if (opts.iterations > 1) { - subs.subscribe(iterationControl, "sub_iteration", SubscriptionSettings(FlowControl::messageCredit(0))); + subs.subscribe(iterationControl, fqn("sub_iteration"), SubscriptionSettings(FlowControl::messageCredit(0))); } for (size_t j = 0; j < opts.iterations; ++j) { if (j > 0) { //need to wait here until all subs are done - session.messageFlow("sub_iteration", 0, 1); + session.messageFlow(fqn("sub_iteration"), 0, 1); iterationControl.pop(); //need to allocate some more credit for subscription @@ -643,7 +652,7 @@ struct SubscribeThread : public Client { // Report to publisher. Message result(lexical_cast<string>(opts.subQuota/secs(start,end)), - "sub_done"); + fqn("sub_done")); session.messageTransfer(arg::content=result, arg::acceptMode=1); if (opts.txSub) sync(session).txCommit(); } @@ -680,7 +689,7 @@ int main(int argc, char** argv) { // Start pubs/subs for each queue/topic. for (size_t i = 0; i < opts.qt; ++i) { ostringstream key; - key << "perftest" << i; // Queue or topic name. + key << opts.baseName << i; // Queue or topic name. if (opts.publish) { size_t n = singleProcess ? opts.pubs : 1; for (size_t j = 0; j < n; ++j) { diff --git a/qpid/java/broker/bin/qpid-server.bat b/qpid/java/broker/bin/qpid-server.bat index 2687baa111..2687baa111 100755..100644 --- a/qpid/java/broker/bin/qpid-server.bat +++ b/qpid/java/broker/bin/qpid-server.bat diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java b/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java new file mode 100644 index 0000000000..4afc209ba7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java @@ -0,0 +1,319 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.log4j.xml; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.management.LoggingManagementMBean; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Substitute for the Log4J XMLWatchdog (as used by DOMConfigurator.configureAndWatch) + * + * Extends the default behaviour with a strict parser check on the XML file before allowing the reconfiguration to proceed, + * ensuring that any parser error or warning prevents initiation of a configuration update by Log4J, which aborts mid-update + * upon fatal errors from the parser and proceeds in the event of 'regular' parser errors and warnings, in all cases allowing + * startup to proceed with whatever half-baked configuration then exists. + */ +public class QpidLog4JConfigurator +{ + //lock to protect access to the configuration file + //shared with LoggingManagementMBean + public static final ReentrantLock LOCK = new ReentrantLock(); + private static Logger _logger; + private static DOMConfigurator domConfig = new DOMConfigurator(); + + private QpidLog4JConfigurator() + { + //no instances + } + + public static void configure(String filename) throws IOException, ParserConfigurationException, + SAXException, IllegalLoggerLevelException + { + try + { + LOCK.lock(); + + parseXMLConfigFile(filename); + checkLoggerLevels(filename); + + DOMConfigurator.configure(filename); + + if(_logger == null) + { + _logger = Logger.getLogger(QpidLog4JConfigurator.class); + } + } + finally + { + LOCK.unlock(); + } + } + + public static void configureAndWatch(String filename, long delay) throws IOException, ParserConfigurationException, + SAXException, IllegalLoggerLevelException + { + parseXMLConfigFile(filename); + checkLoggerLevels(filename); + + QpidLog4JXMLWatchdog watchdog = new QpidLog4JXMLWatchdog(filename); + watchdog.setDelay(delay); + watchdog.start(); + } + + private static void parseXMLConfigFile(String fileName) throws IOException, SAXException, + ParserConfigurationException + { + try + { + LOCK.lock(); + + //check file was specified, exists, and is readable + if(fileName == null) + { + throw new IOException("Provided log4j XML configuration filename was null"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + throw new IOException("The log4j XML configuration file does not exist: " + fileName); + } + else if (!configFile.canRead()) + { + throw new IOException("The log4j XML configuration file is not readable: " + fileName); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + docBuilder.parse(fileName); + } + finally + { + LOCK.unlock(); + } + } + + public static class QpidLog4JSaxErrorHandler implements ErrorHandler + { + public void error(SAXParseException e) throws SAXException + { + if(_logger != null) + { + _logger.warn(constructMessage("Error parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Error parsing XML file", e)); + } + } + + public void fatalError(SAXParseException e) throws SAXException + { + throw new SAXException(constructMessage("Fatal error parsing XML file", e)); + } + + public void warning(SAXParseException e) throws SAXException + { + if(_logger != null) + { + _logger.warn(constructMessage("Warning parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Warning parsing XML file", e)); + } + } + + private static String constructMessage(final String msg, final SAXParseException ex) + { + return new String(msg + ": Line " + ex.getLineNumber()+" column " +ex.getColumnNumber() + ": " + ex.getMessage()); + } + } + + private static class QpidLog4JXMLWatchdog extends XMLWatchdog + { + public QpidLog4JXMLWatchdog(String filename) + { + super(filename); + } + + public void doOnChange() + { + try + { + LOCK.lock(); + + try + { + parseXMLConfigFile(filename); + } + catch (Exception e) + { + //logger will be instantiated following first configuration success, which has been pre-validated + //and so the null check should never actually be required. + if(_logger != null) + { + _logger.warn("Parsing the log4j XML configuration file generated errors/warnings. " + + "The new configuration was not applied. Correct the issues to prompt " + + "another update attempt: " + e.getMessage()); + } + return; + } + + try + { + checkLoggerLevels(filename); + } + catch (Exception e) + { + //logger will be instantiated following first configuration success, which has been pre-validated + //and so the null check should never actually be required. + if(_logger != null) + { + _logger.warn("Errors were found when validating the logger level values in the " + + "log4j XML configuration file. The new configuration was not applied. " + + "Correct the issues to prompt another update attempt: " + e.getMessage()); + } + return; + } + + //everything checked was ok, let the normal update process proceed + super.doOnChange(); + + //a configuration has now been applied, enable logging for future attempts + if(_logger == null) + { + _logger = Logger.getLogger(QpidLog4JConfigurator.class); + } + + _logger.info("Applied log4j configuration from: " + filename); + } + finally + { + LOCK.unlock(); + } + + } + } + + protected static void checkLoggerLevels(String filename) throws IllegalLoggerLevelException, IOException + { + //check that the logger levels specified in the XML are actually valid + + try + { + LOCK.lock(); + + //get the Logger levels to check + Map<String, String> loggersLevels; + loggersLevels = LoggingManagementMBean.retrieveConfigFileLoggersLevels(filename); + //add the RootLogger to the list too + String rootLoggerlevelString = LoggingManagementMBean.retrieveConfigFileRootLoggerLevel(filename); + loggersLevels.put("Root", rootLoggerlevelString); + + + for (String loggerName : loggersLevels.keySet()) + { + String levelString = loggersLevels.get(loggerName); + + //let log4j replace any properties in the string + String log4jConfiguredString = domConfig.subst(levelString); + + if(log4jConfiguredString.equals("") & ! log4jConfiguredString.equals(levelString)) + { + //log4j has returned an empty string but this isnt what we gave it. + //There may have been an undefined property. Unlike an incorrect + //literal value, we will allow this case to proceed, but warn users. + + if(_logger != null) + { + _logger.warn("Unable to detect Level value from '" + levelString + +"' for logger '" + loggerName + "', Log4J will default this to DEBUG"); + } + else + { + System.err.println("Unable to detect Level value from '" + levelString + +"' for logger " + loggerName + ", Log4J will default this to DEBUG"); + } + + continue; + } + + checkLevel(loggerName,log4jConfiguredString); + } + } + finally + { + LOCK.unlock(); + } + } + + private static void checkLevel(String loggerName, String levelString) throws IllegalLoggerLevelException + { + if("null".equalsIgnoreCase(levelString) || "inherited".equalsIgnoreCase(levelString)) + { + //the string "null" signals to inherit from a parent logger + return; + } + + Level level = Level.toLevel(levelString); + + //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. + if (level.equals(Level.DEBUG) && !(levelString.equalsIgnoreCase("debug"))) + { + //received DEBUG but we did not ask for it, the Level request failed. + throw new IllegalLoggerLevelException("Level '" + levelString + "' specified for Logger '" + loggerName + "' is invalid"); + } + } + + public static class IllegalLoggerLevelException extends Exception + { + private static final long serialVersionUID = 1L; + + public IllegalLoggerLevelException(String msg) + { + super(msg); + } + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java index d94e9e7498..8a1437f3d6 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java @@ -27,9 +27,9 @@ import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.PosixParser; -import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; -import org.apache.log4j.xml.DOMConfigurator; +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.xml.QpidLog4JConfigurator; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.FixedSizeByteBufferAllocator; import org.apache.mina.common.IoAcceptor; @@ -62,9 +62,11 @@ import org.apache.qpid.server.transport.QpidAcceptor; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Properties; /** * Main entry point for AMQPD. @@ -73,8 +75,8 @@ import java.net.InetSocketAddress; @SuppressWarnings({"AccessStaticViaInstance"}) public class Main { - private static final Logger _logger = Logger.getLogger(Main.class); - public static final Logger _brokerLogger = Logger.getLogger("Qpid.Broker"); + private static Logger _logger; + private static Logger _brokerLogger; private static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; @@ -206,7 +208,7 @@ public class Main } catch (InitException e) { - System.out.println(e.getMessage()); + System.out.println("Initialisation Error : " + e.getMessage()); _brokerLogger.error("Initialisation Error : " + e.getMessage()); shutdown(1); } @@ -499,7 +501,18 @@ public class Main public static void main(String[] args) { - + //if the -Dlog4j.configuration property has not been set, enable the init override + //to stop Log4J wondering off and picking up the first log4j.xml/properties file it + //finds from the classpath when we get the first Loggers + if(System.getProperty("log4j.configuration") == null) + { + System.setProperty("log4j.defaultInitOverride", "true"); + } + + //now that the override status is know, we can instantiate the Loggers + _logger = Logger.getLogger(Main.class); + _brokerLogger = Logger.getLogger("Qpid.Broker"); + new Main(args); } @@ -531,7 +544,7 @@ public class Main return ip; } - private void configureLogging(File logConfigFile, int logWatchTime) + private void configureLogging(File logConfigFile, int logWatchTime) throws InitException, IOException { if (logConfigFile.exists() && logConfigFile.canRead()) { @@ -542,18 +555,43 @@ public class Main System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " + logWatchTime + " seconds"); // log4j expects the watch interval in milliseconds - DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000); + try + { + QpidLog4JConfigurator.configureAndWatch(logConfigFile.getPath(), logWatchTime * 1000); + } + catch (Exception e) + { + throw new InitException(e.getMessage(),e); + } } else { - DOMConfigurator.configure(logConfigFile.getAbsolutePath()); + try + { + QpidLog4JConfigurator.configure(logConfigFile.getPath()); + } + catch (Exception e) + { + throw new InitException(e.getMessage(),e); + } } } else { System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); - System.err.println("Using basic log4j configuration"); - BasicConfigurator.configure(); + System.err.println("Using the fallback internal log4j.properties configuration"); + + InputStream propsFile = this.getClass().getResourceAsStream("/log4j.properties"); + if(propsFile == null) + { + throw new IOException("Unable to load the fallback internal log4j.properties configuration file"); + } + else + { + Properties fallbackProps = new Properties(); + fallbackProps.load(propsFile); + PropertyConfigurator.configure(fallbackProps); + } } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java index 0a2d678073..12867b224d 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java @@ -206,11 +206,21 @@ public class HeadersExchange extends AbstractExchange for (int i = 0; i < bindings.length; i++) { String[] keyAndValue = bindings[i].split("="); - if (keyAndValue == null || keyAndValue.length < 2) + if (keyAndValue == null || keyAndValue.length == 0 || keyAndValue.length > 2) { throw new JMException("Format for headers binding should be \"<attribute1>=<value1>,<attribute2>=<value2>\" "); } - bindingMap.setString(keyAndValue[0], keyAndValue[1]); + + if(keyAndValue.length ==1) + { + //no value was given, only a key. Use an empty value + //to signal match on key presence alone + bindingMap.setString(keyAndValue[0], ""); + } + else + { + bindingMap.setString(keyAndValue[0], keyAndValue[1]); + } } _bindings.add(new Registration(new HeadersBinding(bindingMap), queue, new AMQShortString(binding))); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java index 3cebb1353b..3c47cdd094 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java @@ -20,20 +20,28 @@ */ package org.apache.qpid.server.logging.management; +import static org.apache.log4j.xml.QpidLog4JConfigurator.LOCK; + import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.qpid.management.common.mbeans.LoggingManagement; import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.util.FileUtils; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.xml.Log4jEntityResolver; +import org.apache.log4j.xml.QpidLog4JConfigurator; +import org.apache.log4j.xml.QpidLog4JConfigurator.QpidLog4JSaxErrorHandler; +import org.apache.log4j.xml.QpidLog4JConfigurator.IllegalLoggerLevelException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -70,10 +78,12 @@ public class LoggingManagementMBean extends AMQManagedObject implements LoggingM private static final Logger _logger = Logger.getLogger(LoggingManagementMBean.class); private String _log4jConfigFileName; private int _log4jLogWatchInterval; + private static final String INHERITED = "INHERITED"; private static final String[] LEVELS = new String[]{Level.ALL.toString(), Level.TRACE.toString(), Level.DEBUG.toString(), Level.INFO.toString(), Level.WARN.toString(), Level.ERROR.toString(), - Level.FATAL.toString(),Level.OFF.toString()}; + Level.FATAL.toString(),Level.OFF.toString(), + INHERITED}; static TabularType _loggerLevelTabularType; static CompositeType _loggerLevelCompositeType; @@ -210,27 +220,41 @@ public class LoggingManagementMBean extends AMQManagedObject implements LoggingM public synchronized boolean setRuntimeRootLoggerLevel(String level) { - Level newLevel; - try - { - newLevel = getLevel(level); - } - catch (Exception e) - { - return false; - } - - _logger.info("Setting RootLogger level to " + level); - - Logger log = Logger.getRootLogger(); - log.setLevel(newLevel); - - return true; + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + if(newLevel == null) + { + //A null Level reference implies inheritance. Setting the runtime RootLogger + //to null is catastrophic (and prevented by Log4J at startup and runtime anyway). + return false; + } + + _logger.info("Setting RootLogger level to " + level); + + Logger log = Logger.getRootLogger(); + log.setLevel(newLevel); + + return true; } //method to convert from a string to a log4j Level, throws exception if the given value is invalid private Level getLevel(String level) throws Exception { + if("null".equalsIgnoreCase(level) || INHERITED.equalsIgnoreCase(level)) + { + //the string "null" or "inherited" signals to inherit from a parent logger, + //using a null Level reference for the logger. + return null; + } + Level newLevel = Level.toLevel(level); //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. @@ -243,137 +267,161 @@ public class LoggingManagementMBean extends AMQManagedObject implements LoggingM return newLevel; } - //handler to catch errors signalled by the JAXP parser and throw an appropriate exception - private class SaxErrorHandler implements ErrorHandler + //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. + private static synchronized Document parseConfigFile(String fileName) throws IOException { - - public void error(SAXParseException e) throws SAXException - { - throw new SAXException("Error parsing XML file: " + e.getMessage()); - } - - public void fatalError(SAXParseException e) throws SAXException + try { - throw new SAXException("Fatal error parsing XML file: " + e.getMessage()); - } + LOCK.lock(); - public void warning(SAXParseException e) throws SAXException - { - throw new SAXException("Warning parsing XML file: " + e.getMessage()); - } - } + //check file was specified, exists, and is readable + if(fileName == null) + { + _logger.warn("Provided log4j XML configuration filename is null"); + throw new IOException("Provided log4j XML configuration filename is null"); + } - //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. - private synchronized Document parseConfigFile(String fileName) throws IOException - { - //check file was specified, exists, and is readable - if(fileName == null) - { - _logger.warn("No log4j XML configuration file has been set"); - throw new IOException("No log4j XML configuration file has been set"); - } + File configFile = new File(fileName); - File configFile = new File(fileName); + if (!configFile.exists()) + { + _logger.warn("The log4j XML configuration file could not be found: " + fileName); + throw new IOException("The log4j XML configuration file could not be found"); + } + else if (!configFile.canRead()) + { + _logger.warn("The log4j XML configuration file is not readable: " + fileName); + throw new IOException("The log4j XML configuration file is not readable"); + } - if (!configFile.exists()) - { - _logger.warn("Specified log4j XML configuration file does not exist: " + fileName); - throw new IOException("Specified log4j XML configuration file does not exist"); - } - else if (!configFile.canRead()) - { - _logger.warn("Specified log4j XML configuration file is not readable: " + fileName); - throw new IOException("Specified log4j XML configuration file is not readable"); - } + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document doc; - //parse it - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder; - Document doc; + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + try + { + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + doc = docBuilder.parse(fileName); + } + catch (ParserConfigurationException e) + { + _logger.warn("Unable to parse the log4j XML file due to possible configuration error: " + e); + //recommended that MBeans should use java.* and javax.* exceptions only + throw new IOException("Unable to parse the log4j XML file due to possible configuration error: " + e.getMessage()); + } + catch (SAXException e) + { + _logger.warn("The specified log4j XML file is invalid: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The specified log4j XML file is invalid: " + e.getMessage()); + } + catch (IOException e) + { + _logger.warn("Unable to parse the specified log4j XML file" + e); + throw new IOException("Unable to parse the specified log4j XML file: " + e.getMessage()); + } - ErrorHandler errHandler = new SaxErrorHandler(); - try - { - docFactory.setValidating(true); - docBuilder = docFactory.newDocumentBuilder(); - docBuilder.setErrorHandler(errHandler); - docBuilder.setEntityResolver(new Log4jEntityResolver()); - doc = docBuilder.parse(fileName); + return doc; } - catch (ParserConfigurationException e) + finally { - _logger.warn("Unable to parse the log4j XML file due to possible configuration error: " + e); - //recommended that MBeans should use java.* and javax.* exceptions only - throw new IOException("Unable to parse the log4j XML file due to possible configuration error: " + e.getMessage()); + LOCK.unlock(); } - catch (SAXException e) - { - _logger.warn("The specified log4j XML file is invalid: " + e); - //recommended that MBeans should use standard java.* and javax.* exceptions only - throw new IOException("The specified log4j XML file is invalid: " + e.getMessage()); - } - catch (IOException e) - { - _logger.warn("Unable to parse the specified log4j XML file" + e); - throw new IOException("Unable to parse the specified log4j XML file", e); - } - - return doc; } - private synchronized boolean writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException + private static synchronized boolean writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException { - File log4jConfigFile = new File(log4jConfigFileName); - - if (!log4jConfigFile.canWrite()) - { - _logger.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); - throw new IOException("Specified log4j XML configuration file is not writable"); - } - - Transformer transformer = null; try { - transformer = TransformerFactory.newInstance().newTransformer(); - } - catch (Exception e) - { - _logger.warn("Could not create an XML transformer: " +e); - return false; - } + LOCK.lock(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); - DOMSource source = new DOMSource(doc); - - File tmp; - try - { - tmp = File.createTempFile("LogManMBeanTemp", ".tmp"); - tmp.deleteOnExit(); - StreamResult result = new StreamResult(tmp); - transformer.transform(source, result); - } - catch (TransformerException e) - { - _logger.warn("Could not transform the XML into new file: " +e); - return false; - } - catch (IOException e) - { - _logger.warn("Could not create the new file: " +e); - return false; - } + File log4jConfigFile = new File(log4jConfigFileName); + + if (!log4jConfigFile.canWrite()) + { + _logger.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); + throw new IOException("Specified log4j XML configuration file is not writable"); + } - // Swap temp file in to replace existing configuration file. - File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); - if (old.exists()) + Transformer transformer = null; + try + { + transformer = TransformerFactory.newInstance().newTransformer(); + } + catch (Exception e) + { + _logger.warn("Could not create an XML transformer: " +e); + return false; + } + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); + DOMSource source = new DOMSource(doc); + + File tmp; + try + { + tmp = File.createTempFile("LogManMBeanTemp", ".tmp"); + tmp.deleteOnExit(); + StreamResult result = new StreamResult(tmp); + transformer.transform(source, result); + } + catch (TransformerException e) + { + _logger.warn("Could not transform the XML into new file: " +e); + throw new IOException("Could not transform the XML into new file: " +e); + } + catch (IOException e) + { + _logger.warn("Could not create the new log4j XML file: " +e); + throw new IOException("Could not create the new log4j XML file: " +e); + } + + // Swap temp file in to replace existing configuration file. + File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + try + { + if(!log4jConfigFile.renameTo(old)) + { + FileUtils.copyCheckedEx(log4jConfigFile, old); + } + } + catch (IOException e) + { + _logger.warn("Could not backup the existing log4j XML file: " +e); + throw new IOException("Could not backup the existing log4j XML file: " +e); + } + + try + { + if(!tmp.renameTo(log4jConfigFile)) + { + FileUtils.copyCheckedEx(tmp, log4jConfigFile); + } + } + catch (IOException e) + { + _logger.warn("Could not copy the new configuration into place: " +e); + throw new IOException("Could not copy the new configuration into place: " +e); + } + + return true; + } + finally { - old.delete(); + LOCK.unlock(); } - log4jConfigFile.renameTo(old); - return tmp.renameTo(log4jConfigFile); } @@ -389,177 +437,209 @@ public class LoggingManagementMBean extends AMQManagedObject implements LoggingM * and not the only possible child element. */ - - public synchronized TabularData viewConfigFileLoggerLevels() throws IOException + public static synchronized Map<String,String> retrieveConfigFileLoggersLevels(String fileName) throws IOException { - if (_loggerLevelTabularType == null) + try { - _logger.warn("TabluarData type not set up correctly"); - return null; - } - - _logger.info("Getting logger levels from log4j configuration file"); - - Document doc = parseConfigFile(_log4jConfigFileName); + LOCK.lock(); - TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + Document doc = parseConfigFile(fileName); - //retrieve the 'category' element nodes - NodeList categoryElements = doc.getElementsByTagName("category"); - - String categoryName; - String priority = null; - - for (int i = 0; i < categoryElements.getLength(); i++) - { - Element categoryElement = (Element) categoryElements.item(i); - categoryName = categoryElement.getAttribute("name"); + HashMap<String,String> loggerLevelList = new HashMap<String,String>(); - //retrieve the category's mandatory 'priority' or 'level' element's value. - //It may not be the only child node, so request by tag name. - NodeList priorityElements = categoryElement.getElementsByTagName("priority"); - NodeList levelElements = categoryElement.getElementsByTagName("level"); + //retrieve the 'category' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); - if (priorityElements.getLength() != 0) + String categoryName; + String priority = null; + + for (int i = 0; i < categoryElements.getLength(); i++) { - Element priorityElement = (Element) priorityElements.item(0); - priority = priorityElement.getAttribute("value").toUpperCase(); + Element categoryElement = (Element) categoryElements.item(i); + categoryName = categoryElement.getAttribute("name"); + + //retrieve the category's mandatory 'priority' or 'level' element's value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = categoryElement.getElementsByTagName("priority"); + NodeList levelElements = categoryElement.getElementsByTagName("level"); + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if (levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + else + { + //there is no exiting priority or level to view, move onto next category/logger + continue; + } + + loggerLevelList.put(categoryName, priority); } - else if (levelElements.getLength() != 0) + + //retrieve the 'logger' element nodes + NodeList loggerElements = doc.getElementsByTagName("logger"); + + String loggerName; + String level; + + for (int i = 0; i < loggerElements.getLength(); i++) { + Element loggerElement = (Element) loggerElements.item(i); + loggerName = loggerElement.getAttribute("name"); + + //retrieve the logger's mandatory 'level' element's value + //It may not be the only child node, so request by tag name. + NodeList levelElements = loggerElement.getElementsByTagName("level"); + Element levelElement = (Element) levelElements.item(0); - priority = levelElement.getAttribute("value").toUpperCase(); - } - else - { - //there is no exiting priority or level to view, move onto next category/logger - continue; - } + level = levelElement.getAttribute("value"); - try - { - Object[] itemData = {categoryName, priority}; - CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData); - loggerLevelList.put(loggerData); + loggerLevelList.put(loggerName, level); } - catch (OpenDataException e) + + return loggerLevelList; + } + finally + { + LOCK.unlock(); + } + } + + public synchronized TabularData viewConfigFileLoggerLevels() throws IOException + { + try + { + LOCK.lock(); + + if (_loggerLevelTabularType == null) { - _logger.warn("Unable to create logger level list due to :" + e); + _logger.warn("TabluarData type not set up correctly"); return null; } - } - //retrieve the 'logger' element nodes - NodeList loggerElements = doc.getElementsByTagName("logger"); - - String loggerName; - String level; + _logger.info("Getting logger levels from log4j configuration file"); - for (int i = 0; i < loggerElements.getLength(); i++) - { - Element loggerElement = (Element) loggerElements.item(i); - loggerName = loggerElement.getAttribute("name"); + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); - //retrieve the logger's mandatory 'level' element's value - //It may not be the only child node, so request by tag name. - NodeList levelElements = loggerElement.getElementsByTagName("level"); + Map<String,String> levels = retrieveConfigFileLoggersLevels(_log4jConfigFileName); - Element levelElement = (Element) levelElements.item(0); - level = levelElement.getAttribute("value").toUpperCase(); - - try + for (String loggerName : levels.keySet()) { - Object[] itemData = {loggerName, level}; - CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData); - loggerLevelList.put(loggerData); - } - catch (OpenDataException e) - { - _logger.warn("Unable to create logger level list due to :" + e); - return null; + String level = levels.get(loggerName); + + try + { + Object[] itemData = {loggerName, level.toUpperCase()}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData); + loggerLevelList.put(loggerData); + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } } + + return loggerLevelList; + } + finally + { + LOCK.unlock(); } - - return loggerLevelList; } public synchronized boolean setConfigFileLoggerLevel(String logger, String level) throws IOException { - //check that the specified level is a valid log4j Level try { - getLevel(level); - } - catch (Exception e) - { - //it isnt a valid level - return false; - } - - _logger.info("Setting level to " + level + " for logger '" + logger - + "' in log4j xml configuration file: " + _log4jConfigFileName); - - Document doc = parseConfigFile(_log4jConfigFileName); + LOCK.lock(); - //retrieve the 'category' and 'logger' element nodes - NodeList categoryElements = doc.getElementsByTagName("category"); - NodeList loggerElements = doc.getElementsByTagName("logger"); - - //collect them into a single elements list - List<Element> logElements = new ArrayList<Element>(); - - for (int i = 0; i < categoryElements.getLength(); i++) - { - logElements.add((Element) categoryElements.item(i)); - } - for (int i = 0; i < loggerElements.getLength(); i++) - { - logElements.add((Element) loggerElements.item(i)); - } + //check that the specified level is a valid log4j Level + try + { + getLevel(level); + } + catch (Exception e) + { + //it isnt a valid level + return false; + } - //try to locate the specified logger/category in the elements retrieved - Element logElement = null; - for (Element e : logElements) - { - if (e.getAttribute("name").equals(logger)) + _logger.info("Setting level to " + level + " for logger '" + logger + + "' in log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the 'category' and 'logger' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + NodeList loggerElements = doc.getElementsByTagName("logger"); + + //collect them into a single elements list + List<Element> logElements = new ArrayList<Element>(); + + for (int i = 0; i < categoryElements.getLength(); i++) { - logElement = e; - break; + logElements.add((Element) categoryElements.item(i)); + } + for (int i = 0; i < loggerElements.getLength(); i++) + { + logElements.add((Element) loggerElements.item(i)); } - } - if (logElement == null) - { - //no loggers/categories with given name found, does not exist to update - _logger.warn("Specified logger does not exist in the configuration file: " +logger); - return false; - } + //try to locate the specified logger/category in the elements retrieved + Element logElement = null; + for (Element e : logElements) + { + if (e.getAttribute("name").equals(logger)) + { + logElement = e; + break; + } + } - //retrieve the optional 'priority' or 'level' sub-element value. - //It may not be the only child node, so request by tag name. - NodeList priorityElements = logElement.getElementsByTagName("priority"); - NodeList levelElements = logElement.getElementsByTagName("level"); + if (logElement == null) + { + //no loggers/categories with given name found, does not exist to update + _logger.warn("Specified logger does not exist in the configuration file: " +logger); + return false; + } - Element levelElement = null; - if (priorityElements.getLength() != 0) - { - levelElement = (Element) priorityElements.item(0); - } - else if (levelElements.getLength() != 0) - { - levelElement = (Element) levelElements.item(0); + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = logElement.getElementsByTagName("priority"); + NodeList levelElements = logElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority or level element to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level.toLowerCase()); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); } - else + finally { - //there is no exiting priority or level element to update - return false; + LOCK.unlock(); } - - //update the element with the new level/priority - levelElement.setAttribute("value", level); - - //output the new file - return writeUpdatedConfigFile(_log4jConfigFileName, doc); } @@ -574,102 +654,167 @@ public class LoggingManagementMBean extends AMQManagedObject implements LoggingM * and not the only possible child element. */ - public synchronized String getConfigFileRootLoggerLevel() throws IOException + public static synchronized String retrieveConfigFileRootLoggerLevel(String fileName) throws IOException { - _logger.info("Getting root logger level from log4j configuration file"); + try + { + LOCK.lock(); - Document doc = parseConfigFile(_log4jConfigFileName); - - //retrieve the optional 'root' element node - NodeList rootElements = doc.getElementsByTagName("root"); + Document doc = parseConfigFile(fileName); - if (rootElements.getLength() == 0) - { - //there is not root logger definition - return null; - } + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); - Element rootElement = (Element) rootElements.item(0); + if (rootElements.getLength() == 0) + { + //there is no root logger definition + return "N/A"; + } - //retrieve the optional 'priority' or 'level' element value. - //It may not be the only child node, so request by tag name. - NodeList priorityElements = rootElement.getElementsByTagName("priority"); - NodeList levelElements = rootElement.getElementsByTagName("level"); - String priority = null; - - if (priorityElements.getLength() != 0) - { - Element priorityElement = (Element) priorityElements.item(0); - priority = priorityElement.getAttribute("value"); - } - else if(levelElements.getLength() != 0) - { - Element levelElement = (Element) levelElements.item(0); - priority = levelElement.getAttribute("value"); - } + Element rootElement = (Element) rootElements.item(0); - if(priority != null) - { - return priority.toUpperCase(); + //retrieve the optional 'priority' or 'level' element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + String priority = null; + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if(levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + + if(priority != null) + { + return priority; + } + else + { + return "N/A"; + } } - else + finally { - return null; + LOCK.unlock(); } } + public synchronized String getConfigFileRootLoggerLevel() throws IOException + { + return retrieveConfigFileRootLoggerLevel(_log4jConfigFileName).toUpperCase(); + } + public synchronized boolean setConfigFileRootLoggerLevel(String level) throws IOException { - //check that the specified level is a valid log4j Level try { - getLevel(level); + LOCK.lock(); + + //check that the specified level is a valid log4j Level + try + { + Level newLevel = getLevel(level); + if(newLevel == null) + { + //A null Level reference implies inheritance. Setting the config file RootLogger + //to "null" or "inherited" just ensures it defaults to DEBUG at startup as Log4J + //prevents this catastrophic situation at startup and runtime anyway. + return false; + } + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for the Root logger in " + + "log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + return false; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority/level to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); } - catch (Exception e) + finally { - //it isnt a valid level - return false; + LOCK.unlock(); } - - _logger.info("Setting level to " + level + " for the Root logger in " + - "log4j xml configuration file: " + _log4jConfigFileName); + } - Document doc = parseConfigFile(_log4jConfigFileName); - - //retrieve the optional 'root' element node - NodeList rootElements = doc.getElementsByTagName("root"); + public synchronized void reloadConfigFile() throws IOException + { + try + { + LOCK.lock(); - if (rootElements.getLength() == 0) + QpidLog4JConfigurator.configure(_log4jConfigFileName); + _logger.info("Applied log4j configuration from: " + _log4jConfigFileName); + } + catch (IllegalLoggerLevelException e) { - return false; + _logger.warn("The log4j configuration reload request was aborted: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); } - - Element rootElement = (Element) rootElements.item(0); - - //retrieve the optional 'priority' or 'level' sub-element value. - //It may not be the only child node, so request by tag name. - NodeList priorityElements = rootElement.getElementsByTagName("priority"); - NodeList levelElements = rootElement.getElementsByTagName("level"); - - Element levelElement = null; - if (priorityElements.getLength() != 0) + catch (ParserConfigurationException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (SAXException e) { - levelElement = (Element) priorityElements.item(0); + _logger.warn("The log4j configuration reload request was aborted: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); } - else if (levelElements.getLength() != 0) + catch (IOException e) { - levelElement = (Element) levelElements.item(0); + _logger.warn("The log4j configuration reload request was aborted: " + e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); } - else + finally { - //there is no exiting priority/level to update - return false; + LOCK.unlock(); } - - //update the element with the new level/priority - levelElement.setAttribute("value", level); - - //output the new file - return writeUpdatedConfigFile(_log4jConfigFileName, doc); } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java index 5ffcee77f2..42b3b05ac5 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java @@ -35,11 +35,7 @@ import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.ObjectName; import javax.management.NotificationListener; -import javax.management.MalformedObjectNameException; -import javax.management.NotificationFilter; import javax.management.NotificationFilterSupport; -import javax.management.InstanceNotFoundException; -import javax.management.relation.MBeanServerNotificationFilter; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXServiceURL; import javax.management.remote.MBeanServerForwarder; @@ -82,6 +78,7 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry public static final int PORT_EXPORT_OFFSET = 100; private final MBeanServer _mbeanServer; + private JMXConnectorServer _cs; private Registry _rmiRegistry; @@ -120,7 +117,6 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases(); PrincipalDatabase db = map.get(jmxDatabaseName); - final JMXConnectorServer cs; HashMap<String,Object> env = new HashMap<String,Object>(); //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration @@ -246,7 +242,7 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry "service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi"); final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET); - cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) + _cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) { @Override public synchronized void start() throws IOException @@ -282,7 +278,7 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer. MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); - cs.setMBeanServerForwarder(mbsf); + _cs.setMBeanServerForwarder(mbsf); NotificationFilterSupport filter = new NotificationFilterSupport(); filter.enableType(JMXConnectionNotification.OPENED); @@ -290,9 +286,9 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry filter.enableType(JMXConnectionNotification.FAILED); // Get the handler that is used by the above MBInvocationHandler Proxy. // which is the MBeanInvocationHandlerImpl and so also a NotificationListener - cs.addNotificationListener((NotificationListener) Proxy.getInvocationHandler(mbsf), filter, null); + _cs.addNotificationListener((NotificationListener) Proxy.getInvocationHandler(mbsf), filter, null); - cs.start(); + _cs.start(); CurrentActor.get().message(ManagementConsoleMessages.MNG_1004()); @@ -377,6 +373,19 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry UnicastRemoteObject.unexportObject(_rmiRegistry, true); } + if (_cs != null) + { + // Stopping the JMX ConnectorServer + try + { + _cs.stop(); + } + catch (IOException e) + { + _log.warn("Error while closing the JMX ConnectorServer: " + e.getMessage()); + } + } + //ObjectName query to gather all Qpid related MBeans ObjectName mbeanNameQuery = null; try diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java index 25c3754462..b6d2c3ab67 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java @@ -27,6 +27,7 @@ import org.apache.qpid.server.management.AMQManagedObject; import org.apache.qpid.server.management.MBeanInvocationHandlerImpl; import org.apache.qpid.server.security.auth.database.PrincipalDatabase; import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.util.FileUtils; import org.apache.log4j.Logger; import org.apache.commons.configuration.ConfigurationException; @@ -439,16 +440,44 @@ public class AMQUserManagementMBean extends AMQManagedObject implements UserMana // Create temporary file File tmp = File.createTempFile(_accessFile.getName(), ".tmp"); + tmp.deleteOnExit(); FileOutputStream output = new FileOutputStream(tmp); _accessRights.store(output, "Generated by AMQUserManagementMBean Console : Last edited by user:" + getCurrentJMXUser()); output.close(); - // Rename new file to main file - tmp.renameTo(_accessFile); - - // delete tmp - tmp.delete(); + // Swap temp file to main rights file. + File old = new File(_accessFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + try + { + if(!_accessFile.renameTo(old)) + { + FileUtils.copyCheckedEx(_accessFile, old); + } + } + catch (IOException e) + { + _logger.warn("Could not backup the existing management rights file: " +e); + throw new IOException("Could not backup the existing management rights file: " +e); + } + + try + { + if(!tmp.renameTo(_accessFile)) + { + FileUtils.copyCheckedEx(tmp, _accessFile); + } + } + catch (IOException e) + { + _logger.warn("Could not copy the new management rights file into place: " +e); + throw new IOException("Could not copy the new management rights file into place" +e); + } } finally { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java index 3c211746e3..cd4eb0bec7 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java @@ -25,6 +25,7 @@ import org.apache.qpid.server.security.access.management.AMQUserManagementMBean; import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser; +import org.apache.qpid.util.FileUtils; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; @@ -428,6 +429,7 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase BufferedReader reader = null; PrintStream writer = null; File tmp = File.createTempFile(_passwordFile.getName(), ".tmp"); + tmp.deleteOnExit(); try { @@ -501,6 +503,11 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase } } } + catch(IOException e) + { + _logger.error("Unable to create the new password file: " + e); + throw new IOException("Unable to create the new password file" + e); + } finally { if (reader != null) @@ -512,16 +519,39 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase { writer.close(); } - - // Swap temp file to main password file. - File old = new File(_passwordFile.getAbsoluteFile() + ".old"); - if (old.exists()) + } + + // Swap temp file to main password file. + File old = new File(_passwordFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + try + { + if(!_passwordFile.renameTo(old)) { - old.delete(); + FileUtils.copyCheckedEx(_passwordFile, old); } - _passwordFile.renameTo(old); - tmp.renameTo(_passwordFile); - tmp.delete(); + } + catch (IOException e) + { + _logger.error("Could not backup the existing password file: " +e); + throw new IOException("Could not backup the existing password file: " + e); + } + + try + { + if(!tmp.renameTo(_passwordFile)) + { + FileUtils.copyCheckedEx(tmp, _passwordFile); + } + } + catch (IOException e) + { + _logger.error("Could not copy the new password file into place: " +e); + throw new IOException("Could not copy the new password file into place: " + e); } } finally diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java index 5e4678a63b..6ec7cea4c0 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java @@ -26,6 +26,7 @@ import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainInitialiser; import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; +import org.apache.qpid.util.FileUtils; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; @@ -395,6 +396,7 @@ public class PlainPasswordFilePrincipalDatabase implements PrincipalDatabase BufferedReader reader = null; PrintStream writer = null; File tmp = File.createTempFile(_passwordFile.getName(), ".tmp"); + tmp.deleteOnExit(); try { @@ -452,6 +454,11 @@ public class PlainPasswordFilePrincipalDatabase implements PrincipalDatabase } } } + catch(IOException e) + { + _logger.error("Unable to create the new password file: " + e); + throw new IOException("Unable to create the new password file" + e); + } finally { if (reader != null) @@ -463,17 +470,41 @@ public class PlainPasswordFilePrincipalDatabase implements PrincipalDatabase { writer.close(); } - - // Swap temp file to main password file. - File old = new File(_passwordFile.getAbsoluteFile() + ".old"); - if (old.exists()) + } + + // Swap temp file to main password file. + File old = new File(_passwordFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + try + { + if(!_passwordFile.renameTo(old)) { - old.delete(); + FileUtils.copyCheckedEx(_passwordFile, old); } - _passwordFile.renameTo(old); - tmp.renameTo(_passwordFile); - tmp.delete(); } + catch (IOException e) + { + _logger.error("Could not backup the existing password file: " +e); + throw new IOException("Could not backup the existing password file: " + e); + } + + try + { + if(!tmp.renameTo(_passwordFile)) + { + FileUtils.copyCheckedEx(tmp, _passwordFile); + } + } + catch (IOException e) + { + _logger.error("Could not copy the new password file into place: " +e); + throw new IOException("Could not copy the new password file into place: " + e); + } + } finally { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java index 1b07cbdf65..64f5d0dfaa 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java @@ -615,7 +615,6 @@ public abstract class SubscriptionImpl implements Subscription, FlowCreditManage if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) { _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); - CurrentActor.get().message(_logSubject,SubscriptionMessages.SUB_1003(_state.get().toString())); } else { @@ -628,9 +627,9 @@ public abstract class SubscriptionImpl implements Subscription, FlowCreditManage if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) { _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); - CurrentActor.get().message(_logSubject,SubscriptionMessages.SUB_1003(_state.get().toString())); } } + CurrentActor.get().message(_logSubject,SubscriptionMessages.SUB_1003(_state.get().toString())); } public State getState() diff --git a/qpid/java/broker/src/test/java/org/apache/log4j/xml/QpidLog4JConfiguratorTest.java b/qpid/java/broker/src/test/java/org/apache/log4j/xml/QpidLog4JConfiguratorTest.java new file mode 100644 index 0000000000..9f350033d6 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/log4j/xml/QpidLog4JConfiguratorTest.java @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.log4j.xml; + + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import org.apache.log4j.xml.QpidLog4JConfigurator.IllegalLoggerLevelException; + +import junit.framework.TestCase; + +public class QpidLog4JConfiguratorTest extends TestCase +{ + private static final String NEWLINE = System.getProperty("line.separator"); + + private File _testConfigFile; + + private File createTempTestLog4JConfig(String loggerLevel,String rootLoggerLevel, boolean missingTagClose, boolean incorrectAttribute) + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE); + writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE); + + writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " + + "threshold=\"null\">"+NEWLINE); + + writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE); + writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE); + writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE); + writer.write(" </layout>"+NEWLINE); + writer.write(" </appender>"+NEWLINE); + + String closeTag="/"; + if(missingTagClose) + { + closeTag=""; + } + + //Example of a 'category' with a 'priority' + writer.write(" <category additivity=\"true\" name=\"logger1\">"+NEWLINE); + writer.write(" <priority value=\"" + loggerLevel+ "\"" + closeTag + ">"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + String attributeName="value"; + if(incorrectAttribute) + { + attributeName="values"; + } + + //Example of a 'category' with a 'level' + writer.write(" <category additivity=\"true\" name=\"logger2\">"+NEWLINE); + writer.write(" <level " + attributeName + "=\"" + loggerLevel+ "\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'logger' with a 'level' + writer.write(" <logger additivity=\"true\" name=\"logger3\">"+NEWLINE); + writer.write(" <level value=\"" + loggerLevel+ "\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </logger>"+NEWLINE); + + //'root' logger + writer.write(" <root>"+NEWLINE); + writer.write(" <priority value=\"" + rootLoggerLevel+ "\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </root>"+NEWLINE); + + writer.write("</log4j:configuration>"+NEWLINE); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create temporary test log4j configuration"); + } + + return tmpFile; + } + + + + //******* Test Methods ******* // + + public void testCheckLevelsAndStrictParser() + { + //try the valid logger levels + _testConfigFile = createTempTestLog4JConfig("all", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("trace", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("debug", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("warn", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("error", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("fatal", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("off", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("null", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("inherited", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + //now try an invalid logger level + _testConfigFile = createTempTestLog4JConfig("madeup", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IllegalLoggerLevelException expected, invalid levels used"); + } + catch (IllegalLoggerLevelException e) + { + //expected, ignore + } + catch (IOException e) + { + fail("Incorrect Exception, expected an IllegalLoggerLevelException"); + } + + + + //now try the valid rootLogger levels + _testConfigFile = createTempTestLog4JConfig("info", "all", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "trace", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "debug", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "info", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "warn", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "error", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "fatal", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "off", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "null", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "inherited", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + _testConfigFile = createTempTestLog4JConfig("info", "debug", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + } + catch (Exception e) + { + fail("No exception expected, valid levels and xml were used"); + } + + //now try an invalid logger level + _testConfigFile = createTempTestLog4JConfig("info", "madeup", false, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IllegalLoggerLevelException expected, invalid levels used"); + } + catch (IllegalLoggerLevelException e) + { + //expected, ignore + } + catch (IOException e) + { + fail("Incorrect Exception, expected an IllegalLoggerLevelException"); + } + + + + //now try invalid xml + _testConfigFile = createTempTestLog4JConfig("info", "info", true, false); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IOException expected, malformed XML used"); + } + catch (IllegalLoggerLevelException e) + { + fail("Incorrect Exception, expected an IOException"); + } + catch (IOException e) + { + //expected, ignore + } + + _testConfigFile = createTempTestLog4JConfig("info", "info", false, true); + try + { + QpidLog4JConfigurator.checkLoggerLevels(_testConfigFile.getAbsolutePath()); + fail("IOException expected, malformed XML used"); + } + catch (IllegalLoggerLevelException e) + { + //expected, ignore + } + catch (IOException e) + { + fail("Incorrect Exception, expected an IllegalLoggerLevelException"); + } + } +} diff --git a/qpid/java/client/example/src/main/java/README.txt b/qpid/java/client/example/src/main/java/README.txt index c9367b706d..c99c3fddec 100644 --- a/qpid/java/client/example/src/main/java/README.txt +++ b/qpid/java/client/example/src/main/java/README.txt @@ -3,14 +3,15 @@ variables, QPID_HOME and QPID_SAMPLE. If not the default values will be used. QPID_HOME --------- -This is the distribution directory. You would have set the QPID_HOME when you -started the Qpid Java broker. +This is the directory that contains the QPID distribution. If you are running the Qpid +Java broker on the same machine as the examples, you have already set QPID_HOME to this +directory. default: /usr/share/java/ QPID_SAMPLE ----------- -This is the parent directory of the directory in which you find the runSample.sh +This is the parent directory of the 'java' directory in which you find 'runSample.sh' (Ex:- $QPID_SRC_HOME/java/client/example/src/main) default: $PWD diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java b/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java index bddbc329ab..ee7fc533a3 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java @@ -22,10 +22,11 @@ package org.apache.qpid.client.util; import java.util.Iterator; import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A blocking queue that emits events above a user specified threshold allowing the caller to take action (e.g. flow * control) to try to prevent the queue growing (much) further. The underlying queue itself is not bounded therefore the @@ -36,6 +37,8 @@ import java.util.concurrent.ConcurrentLinkedQueue; */ public class FlowControllingBlockingQueue { + private static final Logger _logger = LoggerFactory.getLogger(FlowControllingBlockingQueue.class); + /** This queue is bounded and is used to store messages before being dispatched to the consumer */ private final Queue _queue = new ConcurrentLinkedQueue(); @@ -46,6 +49,8 @@ public class FlowControllingBlockingQueue /** We require a separate count so we can track whether we have reached the threshold */ private int _count; + + private boolean disableFlowControl; public boolean isEmpty() { @@ -69,6 +74,10 @@ public class FlowControllingBlockingQueue _flowControlHighThreshold = highThreshold; _flowControlLowThreshold = lowThreshold; _listener = listener; + if (highThreshold == 0) + { + disableFlowControl = true; + } } public Object take() throws InterruptedException @@ -84,7 +93,7 @@ public class FlowControllingBlockingQueue } } } - if (_listener != null) + if (!disableFlowControl && _listener != null) { synchronized (_listener) { @@ -93,6 +102,7 @@ public class FlowControllingBlockingQueue _listener.underThreshold(_count); } } + } return o; @@ -106,7 +116,7 @@ public class FlowControllingBlockingQueue notifyAll(); } - if (_listener != null) + if (!disableFlowControl && _listener != null) { synchronized (_listener) { diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java b/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java index 515c849290..7ba38f4743 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java @@ -196,24 +196,7 @@ public class FileUtils { try { - InputStream in = new FileInputStream(src); - if (!dst.exists()) - { - dst.createNewFile(); - } - - OutputStream out = new FileOutputStream(dst); - - // Transfer bytes from in to out - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) - { - out.write(buf, 0, len); - } - - in.close(); - out.close(); + copyCheckedEx(src, dst); } catch (IOException e) { @@ -221,6 +204,36 @@ public class FileUtils } } + /** + * Copies the specified source file to the specified destination file. If the destination file does not exist, + * it is created. + * + * @param src The source file name. + * @param dst The destination file name. + * @throws IOException + */ + public static void copyCheckedEx(File src, File dst) throws IOException + { + InputStream in = new FileInputStream(src); + if (!dst.exists()) + { + dst.createNewFile(); + } + + OutputStream out = new FileOutputStream(dst); + + // Transfer bytes from in to out + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) + { + out.write(buf, 0, len); + } + + in.close(); + out.close(); + } + /* * Deletes a given file */ diff --git a/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/mbeans/LoggingManagement.java b/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/mbeans/LoggingManagement.java index d9c5f59bd0..98dba9fed1 100644 --- a/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/mbeans/LoggingManagement.java +++ b/qpid/java/management/common/src/main/java/org/apache/qpid/management/common/mbeans/LoggingManagement.java @@ -36,7 +36,7 @@ import javax.management.openmbean.TabularData; public interface LoggingManagement { String TYPE = "LoggingManagement"; - int VERSION = 1; + int VERSION = 2; //TabularType and contained CompositeType key/description information //For compatibility reasons, DONT MODIFY the existing key values if expanding the set. @@ -107,6 +107,15 @@ public interface LoggingManagement //****** log4j XML configuration file operations ****** // /** + * Reloads the log4j configuration file, applying any changes made. + * + * @throws IOException + * @since Qpid JMX API 1.3 + */ + @MBeanOperation(name = "reloadConfigFile", description = "Reload the log4j xml configuration file", impact = MBeanOperationInfo.ACTION) + void reloadConfigFile() throws IOException; + + /** * Updates the level of an existing Log4J logger within the xml configuration file * @param logger The name of the logger * @param level The level to set the logger to diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/ClientListener.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/ClientListener.java index c3348b32f0..d88e0f38bb 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/ClientListener.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/ClientListener.java @@ -59,7 +59,12 @@ public class ClientListener implements NotificationListener else if (JMXConnectionNotification.FAILED.equals(type)) { ApplicationRegistry.serverConnectionClosed(server); - MBeanUtility.printOutput("Recieved notification from " + server.getName() + ": " + type ); + MBeanUtility.printOutput("JMX Connection to " + server.getName() + " failed."); + } + else if (JMXConnectionNotification.CLOSED.equals(type)) + { + ApplicationRegistry.serverConnectionClosed(server); + MBeanUtility.printOutput("JMX Connection to " + server.getName() + " was closed."); } } diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/JMXServerRegistry.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/JMXServerRegistry.java index 2b459c858f..ca07e6acf4 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/JMXServerRegistry.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/JMXServerRegistry.java @@ -22,6 +22,7 @@ package org.apache.qpid.management.ui.jmx; import static org.apache.qpid.management.ui.Constants.ALL; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -29,7 +30,6 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import javax.management.ListenerNotFoundException; import javax.management.MBeanInfo; import javax.management.MBeanServerConnection; import javax.management.Notification; @@ -116,25 +116,54 @@ public class JMXServerRegistry extends ServerRegistry * removes all listeners from the mbean server. This is required when user * disconnects the Qpid server connection */ - public void closeServerConnection() throws Exception + public void closeServerConnection() throws IOException { try { + //remove the listener from the JMXConnector if (_jmxc != null && _clientListener != null) + { _jmxc.removeConnectionNotificationListener(_clientListener); + } + } + catch (Exception e) + { + //ignore + } + try + { + //remove the listener from the MBeanServerDelegate MBean if (_mbsc != null && _clientListener != null) + { _mbsc.removeNotificationListener(_serverObjectName, _clientListener); + } + } + catch (Exception e) + { + //ignore + } - // remove mbean notification listeners + if (_mbsc != null && _clientListener != null) + { + //remove any listeners from the Qpid MBeans for (String mbeanName : _subscribedNotificationMap.keySet()) { - _mbsc.removeNotificationListener(new ObjectName(mbeanName), _notificationListener); + try + { + _mbsc.removeNotificationListener(new ObjectName(mbeanName), _notificationListener); + } + catch (Exception e) + { + //ignore + } } } - catch (ListenerNotFoundException ex) + + //close the JMXConnector + if (_jmxc != null) { - MBeanUtility.printOutput(ex.toString()); + _jmxc.close(); } } diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/AttributesTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/AttributesTabControl.java index 2408faae3a..f21647b2d2 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/AttributesTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/AttributesTabControl.java @@ -882,7 +882,7 @@ public class AttributesTabControl extends TabControl { attribute = (AttributeData) element; if (attribute.isWritable()) - return Display.getCurrent().getSystemColor(SWT.COLOR_DARK_BLUE); + return Display.getCurrent().getSystemColor(SWT.COLOR_BLUE); else return Display.getCurrent().getSystemColor(SWT.COLOR_BLACK); } diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java index 0d290ab1c4..056d365f8e 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java @@ -790,9 +790,11 @@ public class NavigationView extends ViewPart return; } - serverRegistry.closeServerConnection(); // Add server to the closed server list and the worker thread will remove the server from required places. ApplicationRegistry.serverConnectionClosed(managedServer); + + //close the connection + serverRegistry.closeServerConnection(); } /** diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/connection/ConnectionOperationsTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/connection/ConnectionOperationsTabControl.java index e981ec1c3c..f82d37dcd1 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/connection/ConnectionOperationsTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/connection/ConnectionOperationsTabControl.java @@ -28,10 +28,13 @@ import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.TabularDataSupport; +import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ManagedBean; +import org.apache.qpid.management.ui.ServerRegistry; import org.apache.qpid.management.common.mbeans.ManagedConnection; import org.apache.qpid.management.ui.jmx.JMXManagedObject; import org.apache.qpid.management.ui.jmx.MBeanUtility; +import org.apache.qpid.management.ui.views.MBeanView; import org.apache.qpid.management.ui.views.TabControl; import org.apache.qpid.management.ui.views.ViewUtility; import org.eclipse.jface.viewers.ISelectionChangedListener; @@ -43,6 +46,8 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; @@ -55,6 +60,8 @@ import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; @@ -199,6 +206,8 @@ public class ConnectionOperationsTabControl extends TabControl _tableViewer.setContentProvider(new ContentProviderImpl()); _tableViewer.setLabelProvider(new LabelProviderImpl()); _tableViewer.setSorter(tableSorter); + _table.setSortColumn(_table.getColumn(0)); + _table.setSortDirection(SWT.UP); Composite buttonsComposite = _toolkit.createComposite(viewChannelsGroup); gridData = new GridData(SWT.RIGHT, SWT.BOTTOM, false, false); @@ -278,6 +287,19 @@ public class ConnectionOperationsTabControl extends TabControl } }); + //listener for double clicking to open the selection mbean + _table.addMouseListener(new MouseListener() + { + // MouseListener implementation + public void mouseDoubleClick(MouseEvent event) + { + openMBean(_table); + } + + public void mouseDown(MouseEvent e){} + public void mouseUp(MouseEvent e){} + }); + _tableViewer.addSelectionChangedListener(new ISelectionChangedListener(){ public void selectionChanged(SelectionChangedEvent evt) { @@ -465,5 +487,43 @@ public class ConnectionOperationsTabControl extends TabControl return comparison; } } + + private void openMBean(Table table) + { + int selectionIndex = table.getSelectionIndex(); + + if (selectionIndex == -1) + { + return; + } + + CompositeData channelResult = (CompositeData) table.getItem(selectionIndex).getData(); + String queueName = (String) channelResult.get(DEFAULT_QUEUE); + + if(queueName == null) + { + return; + } + + ServerRegistry serverRegistry = ApplicationRegistry.getServerRegistry(_mbean); + ManagedBean selectedMBean = serverRegistry.getQueue(queueName, _mbean.getVirtualHostName()); + + if(selectedMBean == null) + { + ViewUtility.popupErrorMessage("Error", "Unable to retrieve the selected MBean to open it"); + return; + } + + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + MBeanView view = (MBeanView) window.getActivePage().findView(MBeanView.ID); + try + { + view.openMBean(selectedMBean); + } + catch (Exception ex) + { + MBeanUtility.handleException(selectedMBean, ex); + } + } } diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/ExchangeOperationsTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/ExchangeOperationsTabControl.java index 35b3e8150c..e3dea6e96b 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/ExchangeOperationsTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/ExchangeOperationsTabControl.java @@ -237,6 +237,8 @@ public class ExchangeOperationsTabControl extends TabControl _keysTableViewer.setContentProvider(new ContentProviderImpl(BINDING_KEY)); _keysTableViewer.setLabelProvider(new LabelProviderImpl(BINDING_KEY)); _keysTableViewer.setSorter(tableSorter); + _keysTable.setSortColumn(_keysTable.getColumn(0)); + _keysTable.setSortDirection(SWT.UP); _queuesTable = new Table (tablesComposite, SWT.SINGLE | SWT.SCROLL_LINE | SWT.BORDER | SWT.FULL_SELECTION); @@ -287,6 +289,8 @@ public class ExchangeOperationsTabControl extends TabControl _queuesTableViewer.setContentProvider(new ContentProviderImpl(QUEUES)); _queuesTableViewer.setLabelProvider(new LabelProviderImpl(QUEUES)); _queuesTableViewer.setSorter(queuesTableSorter); + _queuesTable.setSortColumn(_queuesTable.getColumn(0)); + _queuesTable.setSortDirection(SWT.UP); _queuesTableViewer.setInput(new String[]{"Select a binding key to view queues"}); //listener for double clicking to open the selection mbean diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/HeadersExchangeOperationsTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/HeadersExchangeOperationsTabControl.java index 5146bab74c..fcce0e67b6 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/HeadersExchangeOperationsTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/exchange/HeadersExchangeOperationsTabControl.java @@ -22,6 +22,7 @@ package org.apache.qpid.management.ui.views.exchange; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import javax.management.MBeanServerConnection; @@ -48,6 +49,7 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionAdapter; @@ -60,6 +62,7 @@ import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.Table; @@ -182,7 +185,7 @@ public class HeadersExchangeOperationsTabControl extends TabControl final TableSorter tableSorter = new TableSorter(BINDING_NUM); String[] titles = {"Binding Number", "Queue Name"}; - int[] bounds = {125, 175}; + int[] bounds = {135, 175}; for (int i = 0; i < titles.length; i++) { final int index = i; @@ -220,6 +223,8 @@ public class HeadersExchangeOperationsTabControl extends TabControl _bindingNumberTableViewer.setContentProvider(new ContentProviderImpl(BINDING_NUM)); _bindingNumberTableViewer.setLabelProvider(new LabelProviderImpl(BINDING_NUM)); _bindingNumberTableViewer.setSorter(tableSorter); + _bindingNumberTable.setSortColumn(_bindingNumberTable.getColumn(0)); + _bindingNumberTable.setSortDirection(SWT.UP); //table of header bindings _headersTable = new Table (tablesComposite, SWT.SINGLE | SWT.SCROLL_LINE | SWT.BORDER | SWT.FULL_SELECTION); @@ -272,6 +277,8 @@ public class HeadersExchangeOperationsTabControl extends TabControl _headersTableViewer.setContentProvider(new ContentProviderImpl(HEADER_BINDINGS)); _headersTableViewer.setLabelProvider(new LabelProviderImpl(HEADER_BINDINGS)); _headersTableViewer.setSorter(queuesTableSorter); + _headersTable.setSortColumn(_headersTable.getColumn(0)); + _headersTable.setSortDirection(SWT.UP); _headersTableViewer.setInput(new String[]{"Select a binding to view key-value pairs"}); _bindingNumberTableViewer.addSelectionChangedListener(new ISelectionChangedListener(){ @@ -490,25 +497,38 @@ public class HeadersExchangeOperationsTabControl extends TabControl private void createNewBinding(Shell parent) { final Shell shell = ViewUtility.createModalDialogShell(parent, "Create New Binding"); - - Composite destinationComposite = _toolkit.createComposite(shell, SWT.NONE); - destinationComposite.setBackground(shell.getBackground()); - destinationComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - destinationComposite.setLayout(new GridLayout(2,false)); + + Composite queueNameComposite = _toolkit.createComposite(shell, SWT.NONE); + queueNameComposite.setBackground(shell.getBackground()); + GridData layoutData = new GridData(SWT.CENTER, SWT.TOP, true, false); + layoutData.minimumWidth = 300; + queueNameComposite.setLayoutData(layoutData); + queueNameComposite.setLayout(new GridLayout(2,false)); - _toolkit.createLabel(destinationComposite,"Queue:").setBackground(shell.getBackground()); - final Combo destinationCombo = new Combo(destinationComposite,SWT.NONE | SWT.READ_ONLY); + _toolkit.createLabel(queueNameComposite,"Queue:").setBackground(shell.getBackground()); + final Combo destinationCombo = new Combo(queueNameComposite,SWT.NONE | SWT.READ_ONLY); destinationCombo.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - Composite bindingComposite = _toolkit.createComposite(shell, SWT.NONE); - bindingComposite.setBackground(shell.getBackground()); - bindingComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - bindingComposite.setLayout(new GridLayout(2,false)); + final ScrolledComposite scrolledComposite = new ScrolledComposite(shell, SWT.V_SCROLL); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.setLayout(new GridLayout()); + scrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + scrolledComposite.setBackground(shell.getBackground()); + + final Composite bindingComposite = _toolkit.createComposite(scrolledComposite, SWT.NONE); + bindingComposite.setBackground(scrolledComposite.getBackground()); + bindingComposite.setLayout(new GridLayout(2,true)); + layoutData = new GridData(SWT.FILL, SWT.TOP, true, false); + bindingComposite.setLayoutData(layoutData); + scrolledComposite.setContent(bindingComposite); + + Composite addMoreButtonComp = _toolkit.createComposite(shell); + addMoreButtonComp.setBackground(shell.getBackground()); + addMoreButtonComp.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, true, true)); + addMoreButtonComp.setLayout(new GridLayout()); + + final Button addMoreButton = _toolkit.createButton(addMoreButtonComp, "Add additional field", SWT.PUSH); - _toolkit.createLabel(bindingComposite,"Binding:").setBackground(shell.getBackground()); - final Text bindingText = new Text(bindingComposite, SWT.BORDER); - bindingText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - Composite okCancelButtonsComp = _toolkit.createComposite(shell); okCancelButtonsComp.setBackground(shell.getBackground()); okCancelButtonsComp.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, true, true)); @@ -532,26 +552,106 @@ public class HeadersExchangeOperationsTabControl extends TabControl destinationCombo.setItems(queueList.toArray(new String[0])); } destinationCombo.select(0); + + final HashMap<Text, Text> headerBindingHashMap = new HashMap<Text, Text>(); + + //add headings + Label keyLabel = _toolkit.createLabel(bindingComposite,"Key:"); + keyLabel.setBackground(bindingComposite.getBackground()); + keyLabel.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); + + Label valueLabel = _toolkit.createLabel(bindingComposite,"Value:"); + valueLabel.setBackground(bindingComposite.getBackground()); + valueLabel.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); + + //add the x-match key by default and offer a comobo to select its value + final Text xmatchKeyText = new Text(bindingComposite, SWT.BORDER); + xmatchKeyText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + xmatchKeyText.setText("x-match"); + xmatchKeyText.setEditable(false); + + final Combo xmatchValueCombo = new Combo(bindingComposite,SWT.NONE | SWT.READ_ONLY); + xmatchValueCombo.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + xmatchValueCombo.setItems(new String[]{"any", "all"}); + xmatchValueCombo.select(0); + + //make some empty key-value fields + for(int i=0; i < 4; i++) + { + Text keyText = new Text(bindingComposite, SWT.BORDER); + Text valueText = new Text(bindingComposite, SWT.BORDER); + keyText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + valueText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + headerBindingHashMap.put(keyText, valueText); + } + bindingComposite.setSize(bindingComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + + //allow adding more fields for additional key-value pairs + addMoreButton.addSelectionListener(new SelectionAdapter() + { + public void widgetSelected(SelectionEvent e) + { + Text keyText = new Text(bindingComposite, SWT.BORDER); + Text valueText = new Text(bindingComposite, SWT.BORDER); + keyText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + valueText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + headerBindingHashMap.put(keyText, valueText); + + bindingComposite.setSize(bindingComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + bindingComposite.layout(true); + scrolledComposite.layout(true); + } + }); okButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { - String binding = bindingText.getText(); + String xMatchString = xmatchValueCombo.getText(); - if (binding == null || binding.length() == 0) + String destQueue = destinationCombo.getItem(destinationCombo.getSelectionIndex()).toString(); + + StringBuffer bindingValue = new StringBuffer(); + + //insert the x-match key-value pair + if (xMatchString.equalsIgnoreCase("any")) { - ViewUtility.popupErrorMessage("Create New Binding", "Please enter a valid binding"); - return; + bindingValue.append("x-match=any"); + } + else + { + bindingValue.append("x-match=all"); } - String destQueue = destinationCombo.getItem(destinationCombo.getSelectionIndex()).toString(); + //insert the other key-value pairs + for (Text keyText : headerBindingHashMap.keySet()) + { + + String key = keyText.getText(); + if(key == null || key.length() == 0) + { + continue; + } + + Text valueText = headerBindingHashMap.get(keyText); + String value = valueText.getText(); + + bindingValue.append(","); + bindingValue.append(key + "="); + //empty values are permitted, signalling only key-presence is required + if(value != null && value.length() > 0) + { + bindingValue.append(value); + } + } shell.dispose(); try { - _emb.createNewBinding(destQueue, binding); + _emb.createNewBinding(destQueue, bindingValue.toString()); ViewUtility.operationResultFeedback(null, "Created new Binding", null); } catch (Exception e4) diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/ConfigurationFileTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/ConfigurationFileTabControl.java index 6af90d117c..1b1d08aa67 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/ConfigurationFileTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/ConfigurationFileTabControl.java @@ -30,6 +30,7 @@ import javax.management.openmbean.TabularDataSupport; import static org.apache.qpid.management.ui.Constants.FONT_BOLD; +import org.apache.qpid.management.ui.ApiVersion; import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ManagedBean; import org.apache.qpid.management.common.mbeans.LoggingManagement; @@ -80,6 +81,7 @@ public class ConfigurationFileTabControl extends TabControl private String[] _availableLoggerLevels; private TabularDataSupport _configFileLoggerLevels = null; private LoggingManagement _lmmb; + private ApiVersion _ApiVersion; static final String LOGGER_NAME = LoggingManagement.COMPOSITE_ITEM_NAMES[0]; static final String LOGGER_LEVEL = LoggingManagement.COMPOSITE_ITEM_NAMES[1]; @@ -91,6 +93,7 @@ public class ConfigurationFileTabControl extends TabControl _lmmb = (LoggingManagement) MBeanServerInvocationHandler.newProxyInstance(mbsc, mbean.getObjectName(), LoggingManagement.class, false); + _ApiVersion = ApplicationRegistry.getServerRegistry(mbean).getManagementApiVersion(); _toolkit = new FormToolkit(_tabFolder.getDisplay()); _form = _toolkit.createScrolledForm(_tabFolder); _form.getBody().setLayout(new GridLayout()); @@ -191,11 +194,15 @@ public class ConfigurationFileTabControl extends TabControl } Label noteLabel = _toolkit.createLabel(_headerComposite, - "NOTE: These options modify the config file. " + - "Changes take effect if LogWatch is enabled " + - "or the broker is restarted."); + "NOTE: These options modify the configuration file. " + + "Changes only take effect automatically if LogWatch is enabled."); + Label noteLabel2 = _toolkit.createLabel(_headerComposite, + "A child Logger set to a non-inherited Level in the Runtime tab " + + "will retain that value after the file is reloaded."); GridData gridData = new GridData(SWT.FILL, SWT.FILL, false, true); noteLabel.setLayoutData(gridData); + gridData = new GridData(SWT.FILL, SWT.FILL, false, true); + noteLabel2.setLayoutData(gridData); Group configFileLoggerLevelsGroup = new Group(_paramsComposite, SWT.SHADOW_NONE); configFileLoggerLevelsGroup.setBackground(_paramsComposite.getBackground()); @@ -275,7 +282,7 @@ public class ConfigurationFileTabControl extends TabControl public void mouseUp(MouseEvent e){} }); - final Button logLevelEditButton = _toolkit.createButton(configFileLoggerLevelsGroup, "Edit Selected Logger...", SWT.PUSH); + final Button logLevelEditButton = _toolkit.createButton(configFileLoggerLevelsGroup, "Edit Selected Logger(s)...", SWT.PUSH); logLevelEditButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); logLevelEditButton.setEnabled(false); logLevelEditButton.addSelectionListener(new SelectionAdapter() @@ -341,6 +348,43 @@ public class ConfigurationFileTabControl extends TabControl _logWatchIntervalLabel = _toolkit.createLabel(logWatchIntervalGroup, "-"); _logWatchIntervalLabel.setFont(ApplicationRegistry.getFont(FONT_BOLD)); _logWatchIntervalLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true)); + + if(_ApiVersion.greaterThanOrEqualTo(1, 3)) + { + Group reloadConfigFileGroup = new Group(attributesComposite, SWT.SHADOW_NONE); + reloadConfigFileGroup.setBackground(attributesComposite.getBackground()); + reloadConfigFileGroup.setText("Reload Configuration File"); + gridData = new GridData(SWT.LEFT, SWT.TOP, true, false); + reloadConfigFileGroup.setLayoutData(gridData); + reloadConfigFileGroup.setLayout(new GridLayout()); + + final Button reloadConfigFileButton = _toolkit.createButton(reloadConfigFileGroup, "Reload Config File", SWT.PUSH); + reloadConfigFileButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false)); + reloadConfigFileButton.addSelectionListener(new SelectionAdapter() + { + public void widgetSelected(SelectionEvent e) + { + int response = ViewUtility.popupOkCancelConfirmationMessage("Reload", + "Reload Logging Configuration File?"); + if (response == SWT.OK) + { + try + { + _lmmb.reloadConfigFile(); + ViewUtility.operationResultFeedback(null, "Reloaded Logging Configuration File", null); + + } + catch (Exception ex) + { + ViewUtility.operationFailedStatusBarMessage("Error Reloading Logging Configuration File"); + MBeanUtility.handleException(_mbean, ex); + } + + refresh(_mbean);; + } + } + }); + } } @@ -362,7 +406,7 @@ public class ConfigurationFileTabControl extends TabControl selectedLoggers.add(user); } - final Shell shell = ViewUtility.createModalDialogShell(parent, "Set Config File Logger Level"); + final Shell shell = ViewUtility.createModalDialogShell(parent, "Set Config File Logger Level(s)"); _toolkit.createLabel(shell, "Logger(s): ").setBackground(shell.getBackground()); diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/RuntimeTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/RuntimeTabControl.java index 012c22e6de..9834cd729a 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/RuntimeTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/logging/RuntimeTabControl.java @@ -26,6 +26,7 @@ import java.util.HashMap; import javax.management.MBeanServerConnection; import javax.management.MBeanServerInvocationHandler; import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.TabularDataSupport; import static org.apache.qpid.management.ui.Constants.FONT_BOLD; @@ -37,7 +38,10 @@ import org.apache.qpid.management.ui.jmx.JMXManagedObject; import org.apache.qpid.management.ui.jmx.MBeanUtility; import org.apache.qpid.management.ui.views.TabControl; import org.apache.qpid.management.ui.views.ViewUtility; +import org.eclipse.jface.viewers.IColorProvider; import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; @@ -46,12 +50,15 @@ import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; @@ -78,10 +85,11 @@ public class RuntimeTabControl extends TabControl private Label _runtimeRootLoggerLevelLabel = null; private String[] _availableLoggerLevels; private TabularDataSupport _runtimeLoggerLevels = null; + private ArrayList<String> _configFileLoggerNames = new ArrayList<String>(); private LoggingManagement _lmmb; - static final String LOGGER_NAME = LoggingManagement.COMPOSITE_ITEM_NAMES[0]; - static final String LOGGER_LEVEL = LoggingManagement.COMPOSITE_ITEM_NAMES[1]; + private static final String LOGGER_NAME = LoggingManagement.COMPOSITE_ITEM_NAMES[0]; + private static final String LOGGER_LEVEL = LoggingManagement.COMPOSITE_ITEM_NAMES[1]; public RuntimeTabControl(TabFolder tabFolder, JMXManagedObject mbean, MBeanServerConnection mbsc) { @@ -148,6 +156,26 @@ public class RuntimeTabControl extends TabControl MBeanUtility.handleException(_mbean, e2); } + + try + { + TabularDataSupport confLoggers = (TabularDataSupport) _lmmb.viewConfigFileLoggerLevels(); + ArrayList<String> confLoggerNames = new ArrayList<String>(); + + for(Object obj : confLoggers.values()) + { + CompositeData comp = (CompositeData) obj; + confLoggerNames.add((String) comp.get(LOGGER_NAME)); + } + + _configFileLoggerNames = confLoggerNames; + } + catch(Exception e2) + { + //dont signal the failure, just dont highlight the config file loggers, empty the existing list. + _configFileLoggerNames.clear(); + } + _runtimeRootLoggerLevelLabel.setText(String.valueOf(runtimeRootLoggerLevel)); _tableViewer.setInput(_runtimeLoggerLevels); @@ -174,8 +202,13 @@ public class RuntimeTabControl extends TabControl Label noteLabel = _toolkit.createLabel(_headerComposite, "NOTE: These options modify only the live runtime settings. " + "Non-default values will be lost following broker restart."); + Label noteLabel2 = _toolkit.createLabel(_headerComposite, + "Loggers currently defined in the configuration file are " + + "highlighted. The other Loggers inherit a Level by default."); GridData gridData = new GridData(SWT.FILL, SWT.FILL, false, true); noteLabel.setLayoutData(gridData); + gridData = new GridData(SWT.FILL, SWT.FILL, false, true); + noteLabel2.setLayoutData(gridData); Group effectiveRuntimeLoggerLevelsGroup = new Group(_paramsComposite, SWT.SHADOW_NONE); effectiveRuntimeLoggerLevelsGroup.setBackground(_paramsComposite.getBackground()); @@ -239,7 +272,7 @@ public class RuntimeTabControl extends TabControl } _tableViewer.setContentProvider(new LoggingTableContentProvider()); - _tableViewer.setLabelProvider(new LoggingTableLabelProvider()); + _tableViewer.setLabelProvider(new RuntimeLoggingTableLabelProvider()); _tableViewer.setSorter(tableSorter); _table.setSortColumn(_table.getColumn(0)); _table.setSortDirection(SWT.UP); @@ -254,7 +287,7 @@ public class RuntimeTabControl extends TabControl public void mouseUp(MouseEvent e){} }); - final Button logLevelEditButton = _toolkit.createButton(effectiveRuntimeLoggerLevelsGroup, "Edit Selected Logger...", SWT.PUSH); + final Button logLevelEditButton = _toolkit.createButton(effectiveRuntimeLoggerLevelsGroup, "Edit Selected Logger(s)...", SWT.PUSH); logLevelEditButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); logLevelEditButton.setEnabled(false); logLevelEditButton.addSelectionListener(new SelectionAdapter() @@ -326,7 +359,7 @@ public class RuntimeTabControl extends TabControl selectedLoggers.add(user); } - final Shell shell = ViewUtility.createModalDialogShell(parent, "Set Runtime Logger Level"); + final Shell shell = ViewUtility.createModalDialogShell(parent, "Set Runtime Logger Level(s)"); _toolkit.createLabel(shell, "Logger(s): ").setBackground(shell.getBackground()); @@ -510,4 +543,53 @@ public class RuntimeTabControl extends TabControl shell.open(); } + + /** + * Label Provider class for the RuntimeLoggers table viewer + */ + public class RuntimeLoggingTableLabelProvider extends LabelProvider implements ITableLabelProvider, IColorProvider + { + + @Override + public String getColumnText(Object element, int columnIndex) + { + switch (columnIndex) + { + case 0 : // logger name column + return (String) ((CompositeDataSupport) element).get(LOGGER_NAME); + case 1 : // logger level column + return (String) ((CompositeDataSupport) element).get(LOGGER_LEVEL); + default : + return "-"; + } + } + + @Override + public Image getColumnImage(Object element, int columnIndex) + { + return null; + } + + @Override + public Color getBackground(Object element) + { + return null; + } + + @Override + public Color getForeground(Object element) + { + String loggerName = (String) ((CompositeData) element).get(LOGGER_NAME); + if(_configFileLoggerNames.contains(loggerName)) + { + return Display.getCurrent().getSystemColor(SWT.COLOR_BLUE); + } + else + { + return null; + } + } + + + } } diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/queue/QueueOperationsTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/queue/QueueOperationsTabControl.java index c7c7a1791a..43d2cfe204 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/queue/QueueOperationsTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/queue/QueueOperationsTabControl.java @@ -299,8 +299,8 @@ public class QueueOperationsTabControl extends TabControl //message table Composite tableAndButtonsComposite = _toolkit.createComposite(messagesGroup); gridData = new GridData(SWT.FILL, SWT.FILL, true, true); - gridData.minimumHeight = 220; - gridData.heightHint = 220; + gridData.minimumHeight = 180; + gridData.heightHint = 180; tableAndButtonsComposite.setLayoutData(gridData); tableAndButtonsComposite.setLayout(new GridLayout(2,false)); @@ -358,6 +358,8 @@ public class QueueOperationsTabControl extends TabControl _tableViewer.setContentProvider(new ContentProviderImpl()); _tableViewer.setLabelProvider(new LabelProviderImpl()); _tableViewer.setSorter(tableSorter); + _table.setSortColumn(_table.getColumn(0)); + _table.setSortDirection(SWT.UP); //Side Buttons Composite buttonsComposite = _toolkit.createComposite(tableAndButtonsComposite); @@ -492,12 +494,12 @@ public class QueueOperationsTabControl extends TabControl headerEtcComposite.setLayoutData(gridData); headerEtcComposite.setLayout(new GridLayout()); - final Text headerText = new Text(headerEtcComposite, SWT.WRAP | SWT.BORDER ); + final Text headerText = new Text(headerEtcComposite, SWT.WRAP | SWT.BORDER | SWT.V_SCROLL); headerText.setText("Select a message to view its header."); headerText.setEditable(false); data = new GridData(SWT.LEFT, SWT.TOP, false, false); - data.minimumHeight = 230; - data.heightHint = 230; + data.minimumHeight = 210; + data.heightHint = 210; data.minimumWidth = 500; data.widthHint = 500; headerText.setLayoutData(data); @@ -570,10 +572,16 @@ public class QueueOperationsTabControl extends TabControl String[] msgHeader = (String[]) selectedMsg.get(MSG_HEADER); headerText.setText(""); String lineSeperator = System.getProperty("line.separator"); - for(String s: msgHeader) + int size = msgHeader.length; + for(int i=0; i < size; i++) { - headerText.append(s + lineSeperator); + headerText.append(msgHeader[i]); + if(!(i == size - 1)) + { + headerText.append(lineSeperator); + } } + headerText.setSelection(0); } if (_table.getSelectionCount() > 1) diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/MBeanTypeTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/MBeanTypeTabControl.java index 37af1f8f31..cab9bc9f95 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/MBeanTypeTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/MBeanTypeTabControl.java @@ -77,6 +77,8 @@ public abstract class MBeanTypeTabControl extends TabControl protected String _virtualHost; protected JMXServerRegistry _serverRegistry; protected Composite _tableComposite; + protected Button _favouritesButton; + protected Button _openButton; public MBeanTypeTabControl(TabFolder tabFolder, ManagedServer server, String virtualHost, String type) { @@ -191,8 +193,54 @@ public abstract class MBeanTypeTabControl extends TabControl _tableViewer.setSorter(tableSorter); _table.setSortColumn(_table.getColumn(0)); _table.setSortDirection(SWT.UP); + + addTableListeners(); } + protected void addTableListeners() + { + _favouritesButton.setEnabled(false); + _openButton.setEnabled(false); + + _tableViewer.addSelectionChangedListener(new ISelectionChangedListener(){ + public void selectionChanged(SelectionChangedEvent evt) + { + int selectionIndex = _table.getSelectionIndex(); + + if (selectionIndex == -1) + { + _favouritesButton.setEnabled(false); + _openButton.setEnabled(false); + return; + } + else + { + _favouritesButton.setEnabled(true); + } + + if(_table.getSelectionCount() > 1) + { + _openButton.setEnabled(false); + } + else + { + _openButton.setEnabled(true); + } + } + }); + + _table.addMouseListener(new MouseListener() + { + // MouseListener implementation + public void mouseDoubleClick(MouseEvent event) + { + openMBean(); + } + + public void mouseDown(MouseEvent e){} + public void mouseUp(MouseEvent e){} + }); + } private void createWidgets() @@ -206,11 +254,11 @@ public abstract class MBeanTypeTabControl extends TabControl buttonComposite.setLayoutData(gridData); buttonComposite.setLayout(new GridLayout(2,true)); - final Button favouritesButton = _toolkit.createButton(buttonComposite, + _favouritesButton = _toolkit.createButton(buttonComposite, "<-- Add " + _type + "(s) to favourites", SWT.PUSH); gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false); - favouritesButton.setLayoutData(gridData); - favouritesButton.addSelectionListener(new SelectionAdapter() + _favouritesButton.setLayoutData(gridData); + _favouritesButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { @@ -218,10 +266,10 @@ public abstract class MBeanTypeTabControl extends TabControl } }); - final Button openButton = _toolkit.createButton(buttonComposite, "Open selected " + _type, SWT.PUSH); + _openButton = _toolkit.createButton(buttonComposite, "Open selected " + _type, SWT.PUSH); gridData = new GridData(SWT.RIGHT, SWT.CENTER, true, false); - openButton.setLayoutData(gridData); - openButton.addSelectionListener(new SelectionAdapter() + _openButton.setLayoutData(gridData); + _openButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { @@ -236,48 +284,6 @@ public abstract class MBeanTypeTabControl extends TabControl createTable(); - favouritesButton.setEnabled(false); - openButton.setEnabled(false); - - _tableViewer.addSelectionChangedListener(new ISelectionChangedListener(){ - public void selectionChanged(SelectionChangedEvent evt) - { - int selectionIndex = _table.getSelectionIndex(); - - if (selectionIndex == -1) - { - favouritesButton.setEnabled(false); - openButton.setEnabled(false); - return; - } - else - { - favouritesButton.setEnabled(true); - } - - if(_table.getSelectionCount() > 1) - { - openButton.setEnabled(false); - } - else - { - openButton.setEnabled(true); - } - } - }); - - _table.addMouseListener(new MouseListener() - { - // MouseListener implementation - public void mouseDoubleClick(MouseEvent event) - { - openMBean(); - } - - public void mouseDown(MouseEvent e){} - public void mouseUp(MouseEvent e){} - }); - createLowerAreaButton(mainComposite); } diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/QueueTypeTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/QueueTypeTabControl.java index 3e1f198606..df2a1eca59 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/QueueTypeTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/type/QueueTypeTabControl.java @@ -357,6 +357,8 @@ public class QueueTypeTabControl extends MBeanTypeTabControl _tableViewer.setSorter(tableSorter); _table.setSortColumn(_table.getColumn(0)); _table.setSortDirection(SWT.UP); + + addTableListeners(); } protected void createLowerAreaButton(Composite parent) diff --git a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/vhost/VHostTabControl.java b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/vhost/VHostTabControl.java index fef1e9f887..3b03aeaff1 100644 --- a/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/vhost/VHostTabControl.java +++ b/qpid/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/vhost/VHostTabControl.java @@ -205,6 +205,8 @@ public class VHostTabControl extends TabControl _queueTableViewer.setContentProvider(new ContentProviderImpl()); _queueTableViewer.setLabelProvider(new LabelProviderImpl()); _queueTableViewer.setSorter(tableSorter); + _queueTable.setSortColumn(_queueTable.getColumn(0)); + _queueTable.setSortDirection(SWT.UP); Composite queuesRightComposite = _toolkit.createComposite(queuesGroup); gridData = new GridData(SWT.FILL, SWT.FILL, false, true); @@ -314,6 +316,8 @@ public class VHostTabControl extends TabControl _exchangeTableViewer.setContentProvider(new ContentProviderImpl()); _exchangeTableViewer.setLabelProvider(new LabelProviderImpl()); _exchangeTableViewer.setSorter(exchangeTableSorter); + _exchangeTable.setSortColumn(_exchangeTable.getColumn(0)); + _exchangeTable.setSortDirection(SWT.UP); Composite exchangesRightComposite = _toolkit.createComposite(exchangesGroup); gridData = new GridData(SWT.FILL, SWT.FILL, false, true); diff --git a/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini b/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini index 9e3de042d5..312580769e 100644 --- a/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini +++ b/qpid/java/management/eclipse-plugin/src/main/resources/win32-win32-x86/qpidmc.ini @@ -23,14 +23,14 @@ -XX:MaxPermSize=256m
-Dosgi.requiredJavaVersion=1.5
-Declipse.consoleLog=true
- -#=============================================== -# SSL trust store configuration options. -#=============================================== - -# Uncomment lines below to specify custom truststore for server SSL -# certificate verification, eg when using self-signed server certs. -# -#-Djavax.net.ssl.trustStore=<path.to.truststore> -#-Djavax.net.ssl.trustStorePassword=<truststore.password> - +
+#===============================================
+# SSL trust store configuration options.
+#===============================================
+
+# Uncomment lines below to specify custom truststore for server SSL
+# certificate verification, eg when using self-signed server certs.
+#
+#-Djavax.net.ssl.trustStore=<path.to.truststore>
+#-Djavax.net.ssl.trustStorePassword=<truststore.password>
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java new file mode 100644 index 0000000000..52120019f5 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server; + +import org.apache.qpid.server.logging.AbstractTestLogging; +import org.apache.qpid.util.LogMonitor; +import org.apache.log4j.Logger; +import org.apache.log4j.Level; + +import java.util.List; + +import junit.framework.AssertionFailedError; + +import javax.jms.Connection; +import javax.jms.Session; +import javax.jms.Queue; + +/** + * Series of tests to validate the external Java broker starts up as expected. + */ +public class BrokerStartupTest extends AbstractTestLogging +{ + public void setUp() throws Exception + { + // We either do this here or have a null check in tearDown. + // As when this test is run against profiles other than java it will NPE + _monitor = new LogMonitor(_outputFile); + //We explicitly do not call super.setUp as starting up the broker is + //part of the test case. + } + + + /** + * Description: + * Test that providing an invalid broker logging configuration file does not + * cause the broker to enable DEBUG logging that will seriously impair + * performance + * Input: + * -l value that does not exist + * <p/> + * Output: + * <p/> + * No DEBUG output + * <p/> + * Validation Steps: + * <p/> + * 1. Start the broker and verify no DEBUG output exists + * + * @throws Exception caused by broker startup + */ + public void testInvalidLog4jConfigurationFile() throws Exception + { + // This logging startup code only occurs when you run a Java broker, + // that broker must be started via Main so not an InVM broker. + if (isJavaBroker() && isExternalBroker()) + { + //Remove test Log4j config from the commandline + _broker = _broker.substring(0, _broker.indexOf("-l")); + + // Add an invalid value + _broker += " -l invalid"; + + // The release-bin build of the broker uses this log4j configuration + // so set up the broker environment to use it for this test. + // Also include -Dlog4j.debug so we can validate that it picked up this config + setBrokerEnvironment("QPID_OPTS", "-Dlog4j.debug -Dlog4j.configuration=file:" + System.getProperty(QPID_HOME) + "/../broker/src/main/java/log4j.properties"); + + // Disable all client logging so we can test for broker DEBUG only. + Logger.getRootLogger().setLevel(Level.WARN); + Logger.getLogger("qpid.protocol").setLevel(Level.WARN); + Logger.getLogger("org.apache.qpid").setLevel(Level.WARN); + + startBroker(); + + assertEquals("Log4j could not load desired configruation.", + 0, _monitor.findMatches("log4j:ERROR Could not read configuration file from URL").size()); + + assertEquals("Logging did not error as expected", + 1, _monitor.findMatches("Logging configuration error: unable to read file ").size()); + + + // Perfom some action on the broker to ensure that we hit the DEBUG + // messages that we know are there. Though the current xml parsing + // will generate a LOT of DEBUG on startup. + Connection connection = getConnection(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + Queue queue = session.createQueue(getTestQueueName()); + session.createConsumer(queue).close(); + + int COUNT = 10; + sendMessage(session, queue, COUNT); + + assertEquals(COUNT,drainQueue(queue)); + + List<String> results = _monitor.findMatches("DEBUG"); + try + { + // Validation + + assertEquals("DEBUG messages should not be logged", 0, results.size()); + } + catch (AssertionFailedError afe) + { + System.err.println("Log Dump:"); + for (String log : results) + { + System.err.println(log); + } + + if (results.size() == 0) + { + System.err.println("Monitored file contents:"); + System.err.println(_monitor.readFile()); + } + + throw afe; + } + } + } + +}
\ No newline at end of file diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java index 14eec8daff..620b2a5161 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java @@ -65,6 +65,15 @@ public class AlertingTest extends AbstractTestLogging setupConnection(); } + @Override + public void tearDown() throws Exception + { + // Ensure queue is clean for next run. + drainQueue(_destination); + super.tearDown(); + } + + /** * Create a new connection and ensure taht our destination queue is created * and bound. diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java index d97ed71607..033c0533f8 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java @@ -327,7 +327,7 @@ public class SubscriptionLoggingTest extends AbstractTestLogging int PREFETCH = 15; //Create new session with small prefetch - _session = ((AMQConnection) _connection).createSession(false, Session.AUTO_ACKNOWLEDGE, PREFETCH); + _session = ((AMQConnection) _connection).createSession(true, Session.AUTO_ACKNOWLEDGE, PREFETCH); MessageConsumer consumer = _session.createConsumer(_queue); @@ -336,27 +336,35 @@ public class SubscriptionLoggingTest extends AbstractTestLogging //Fill the prefetch and two extra so that our receive bellow allows the // subscription to become active then return to a suspended state. sendMessage(_session, _queue, 17); - + _session.commit(); // Retreive the first message, and start the flow of messages assertNotNull("First message not retreived", consumer.receive(1000)); - - //Give the internal broker time to respond to the ack that the above - // receive will perform. - if (!isExternalBroker()) - { - Thread.sleep(1000); - } + _session.commit(); + - _connection.close(); - //Validate List<String> results = _monitor.findMatches("SUB-1003"); + + int i = 0; + while (results.size() != 3 && i < 10) + { + try + { + Thread.sleep(1500); + } + catch (InterruptedException e) + { + + } + results = _monitor.findMatches("SUB-1003"); + i++; + } try { // Validation expects three messages. // The first will be logged by the QueueActor as part of the processQueue thread -// INFO - MESSAGE [vh(/test)/qu(example.queue)] [sub:6(qu(example.queue))] SUB-1003 : State : SUSPENDED +// INFO - MESSAGE [vh(/test)/qu(example.queue)] [sub:6(qu(example.queue))] SUB-1003 : State : SUSPENDED // The second will be by the connnection as it acknowledges and activates the subscription // INFO - MESSAGE [con:6(guest@anonymous(26562441)/test)/ch:3] [sub:6(qu(example.queue))] SUB-1003 : State : ACTIVE // The final one can be the subscription suspending as part of the SubFlushRunner or the processQueue thread @@ -365,7 +373,7 @@ public class SubscriptionLoggingTest extends AbstractTestLogging // INFO - MESSAGE [sub:6(vh(test)/qu(example.queue))] [sub:6(qu(example.queue))] SUB-1003 : State : SUSPENDED // INFO - MESSAGE [vh(/test)/qu(example.queue)] [sub:6(qu(example.queue))] SUB-1003 : State : SUSPENDED - assertEquals("Result set larger than expected.", 3, results.size()); + assertEquals("Result set not expected size:", 3, results.size()); // Validate Initial Suspension String expectedState = "SUSPENDED"; @@ -401,6 +409,7 @@ public class SubscriptionLoggingTest extends AbstractTestLogging } throw afe; } + _connection.close(); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java index 9bca1ec39f..dfc3bb7b42 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java @@ -333,8 +333,9 @@ public class FailoverTest extends FailoverBaseCase implements ConnectionListener } int iterations = Integer.getInteger("profile.failoverIterations",0); - boolean b = true; - int failingPort = getFailingPort(); + boolean useAltPort = false; + int altPort = FAILING_PORT; + int stdPort = DEFAULT_PORT; init(false, Session.AUTO_ACKNOWLEDGE); for (int i=0; i < iterations; i++) { @@ -343,25 +344,25 @@ public class FailoverTest extends FailoverBaseCase implements ConnectionListener _logger.debug("==================================================================="); runP2PFailover(numMessages, false,false, false); - startBroker(failingPort); - if (b) + startBroker(getFailingPort()); + if (useAltPort) { - failingPort = getFailingPort()-1; - b = false; + setFailingPort(altPort); + useAltPort = false; } else { - failingPort = getFailingPort()+1; - b = true; + setFailingPort(stdPort); + useAltPort = true; } - setFailingPort(failingPort); + } - //To prevent any failover logic being initiaed when we shutdown the brokers. + //To prevent any failover logic being initiated when we shutdown the brokers. connection.close(); // Shutdown the brokers - stopBroker(getFailingPort()); - stopBroker(b?getFailingPort()+1 : getFailingPort()-1); + stopBroker(altPort); + stopBroker(stdPort); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java index af11a94ca3..5a5e23baa5 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java @@ -20,7 +20,7 @@ public class SelectorTest extends QpidTestCase { Connection conn = getConnection(); conn.start(); - Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); Destination dest = session.createQueue("SelectorQueue"); @@ -32,6 +32,7 @@ public class SelectorTest extends QpidTestCase Message msg = session.createTextMessage("Msg" + String.valueOf(i)); prod.send(msg); } + session.commit(); Message msg1 = consumer.receive(1000); Message msg2 = consumer.receive(1000); @@ -39,6 +40,8 @@ public class SelectorTest extends QpidTestCase Assert.assertNotNull("Msg1 should not be null", msg1); Assert.assertNotNull("Msg2 should not be null", msg2); + session.commit(); + prod.setDisableMessageID(true); for (int i=0; i<2; i++) @@ -47,14 +50,15 @@ public class SelectorTest extends QpidTestCase prod.send(msg); } + session.commit(); Message msg3 = consumer.receive(1000); Assert.assertNull("Msg3 should be null", msg3); - + session.commit(); consumer = session.createConsumer(dest,"JMSMessageID IS NULL"); Message msg4 = consumer.receive(1000); Message msg5 = consumer.receive(1000); - + session.commit(); Assert.assertNotNull("Msg4 should not be null", msg4); Assert.assertNotNull("Msg5 should not be null", msg5); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java index 0be11011b4..d911bb33d7 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java @@ -29,6 +29,7 @@ import org.apache.qpid.test.utils.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.MessageProducer; @@ -62,7 +63,7 @@ public class Client implements MessageListener { _connection = connection; _expected = expected; - _session = (AMQSession) _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE); + _session = (AMQSession) _connection.createSession(true, AMQSession.NO_ACKNOWLEDGE); AMQQueue response = new AMQQueue(_connection.getDefaultQueueExchangeName(), new AMQShortString("ResponseQueue"), true); _session.createConsumer(response).setMessageListener(this); @@ -73,6 +74,7 @@ public class Client implements MessageListener request.setJMSReplyTo(response); MessageProducer prod = _session.createProducer(service); prod.send(request); + _session.commit(); } void shutdownWhenComplete() throws Exception @@ -90,6 +92,14 @@ public class Client implements MessageListener notifyAll(); } + try + { + _session.commit(); + } + catch (JMSException e) + { + + } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java index 9cd8b183af..ce50ceae19 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java @@ -55,7 +55,7 @@ public class Service implements MessageListener { _connection = connection; //AMQQueue queue = new SpecialQueue(connection, "ServiceQueue"); - _session = (AMQSession) _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE); + _session = (AMQSession) _connection.createSession(true, AMQSession.NO_ACKNOWLEDGE); AMQQueue queue = (AMQQueue) _session.createQueue("ServiceQueue") ; _session.createConsumer(queue).setMessageListener(this); _connection.start(); @@ -68,6 +68,7 @@ public class Service implements MessageListener Message response = _session.createTextMessage("Response!"); Destination replyTo = request.getJMSReplyTo(); _session.createProducer(replyTo).send(response); + _session.commit(); } catch (Exception e) { diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java index c5cdb83bbf..2a44413ac8 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java @@ -51,7 +51,13 @@ import javax.jms.TopicSubscriber; public class DurableSubscriptionTest extends QpidTestCase { private static final Logger _logger = LoggerFactory.getLogger(DurableSubscriptionTest.class); - + + /** Timeout for receive() if we are expecting a message */ + private static final long POSITIVE_RECEIVE_TIMEOUT = 2000; + + /** Timeout for receive() if we are not expecting a message */ + private static final long NEGATIVE_RECEIVE_TIMEOUT = 1000; + public void testUnsubscribe() throws Exception { AMQConnection con = (AMQConnection) getConnection("guest", "guest"); @@ -76,16 +82,18 @@ public class DurableSubscriptionTest extends QpidTestCase Message msg; _logger.info("Receive message on consumer 1:expecting A"); - msg = consumer1.receive(); + msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT); + assertNotNull("Message should have been received",msg); assertEquals("A", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 1 :expecting null"); - msg = consumer1.receive(1000); + msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals(null, msg); - _logger.info("Receive message on consumer 1:expecting A"); - msg = consumer2.receive(); + _logger.info("Receive message on consumer 2:expecting A"); + msg = consumer2.receive(POSITIVE_RECEIVE_TIMEOUT); + assertNotNull("Message should have been received",msg); assertEquals("A", ((TextMessage) msg).getText()); - msg = consumer2.receive(1000); + msg = consumer2.receive(NEGATIVE_RECEIVE_TIMEOUT); _logger.info("Receive message on consumer 1 :expecting null"); assertEquals(null, msg); @@ -96,14 +104,15 @@ public class DurableSubscriptionTest extends QpidTestCase producer.send(session1.createTextMessage("B")); _logger.info("Receive message on consumer 1 :expecting B"); - msg = consumer1.receive(); + msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT); + assertNotNull("Message should have been received",msg); assertEquals("B", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 1 :expecting null"); - msg = consumer1.receive(1000); + msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals(null, msg); _logger.info("Receive message on consumer 2 :expecting null"); - msg = consumer2.receive(1000); + msg = consumer2.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals(null, msg); _logger.info("Close connection"); @@ -143,14 +152,16 @@ public class DurableSubscriptionTest extends QpidTestCase producer.send(session1.createTextMessage("A")); Message msg; - msg = consumer1.receive(); + msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT); + assertNotNull("Message should have been received",msg); assertEquals("A", ((TextMessage) msg).getText()); - msg = consumer1.receive(1000); + msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals(null, msg); - msg = consumer2.receive(); + msg = consumer2.receive(POSITIVE_RECEIVE_TIMEOUT); + assertNotNull("Message should have been received",msg); assertEquals("A", ((TextMessage) msg).getText()); - msg = consumer2.receive(1000); + msg = consumer2.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals(null, msg); consumer2.close(); @@ -220,8 +231,8 @@ public class DurableSubscriptionTest extends QpidTestCase msg = consumer1.receive(500); assertNull("There should be no more messages for consumption on consumer1.", msg); - msg = consumer2.receive(); - assertNotNull(msg); + msg = consumer2.receive(POSITIVE_RECEIVE_TIMEOUT); + assertNotNull("Message should have been received",msg); assertEquals("Consumer 2 should also received the first msg.", "A", ((TextMessage) msg).getText()); msg = consumer2.receive(500); assertNull("There should be no more messages for consumption on consumer2.", msg); @@ -235,10 +246,10 @@ public class DurableSubscriptionTest extends QpidTestCase producer.send(session0.createTextMessage("B")); _logger.info("Receive message on consumer 1 :expecting B"); - msg = consumer1.receive(1000); + msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals("B", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 1 :expecting null"); - msg = consumer1.receive(1000); + msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT); assertEquals(null, msg); // Re-attach a new consumer to the durable subscription, and check that it gets the message that it missed. @@ -296,7 +307,7 @@ public class DurableSubscriptionTest extends QpidTestCase producer.send(session.createTextMessage("testDurableWithInvalidSelector2")); - Message msg = liveSubscriber.receive(); + Message msg = liveSubscriber.receive(POSITIVE_RECEIVE_TIMEOUT); assertNotNull ("Message should have been received", msg); assertEquals ("testDurableWithInvalidSelector2", ((TextMessage) msg).getText()); assertNull("Should not receive subsequent message", liveSubscriber.receive(200)); @@ -331,7 +342,7 @@ public class DurableSubscriptionTest extends QpidTestCase assertNotNull("Subscriber should have been created", liveSubscriber); producer.send(session.createTextMessage("testDurableWithInvalidSelector2")); - Message msg = liveSubscriber.receive(); + Message msg = liveSubscriber.receive(POSITIVE_RECEIVE_TIMEOUT); assertNotNull ("Message should have been received", msg); assertEquals ("testDurableWithInvalidSelector2", ((TextMessage) msg).getText()); assertNull("Should not receive subsequent message", liveSubscriber.receive(200)); @@ -360,13 +371,13 @@ public class DurableSubscriptionTest extends QpidTestCase // Send 1 matching message and 1 non-matching message sendMatchingAndNonMatchingMessage(session, producer); - Message rMsg = subA.receive(1000); + Message rMsg = subA.receive(NEGATIVE_RECEIVE_TIMEOUT); assertNotNull(rMsg); assertEquals("Content was wrong", "testResubscribeWithChangedSelector1", ((TextMessage) rMsg).getText()); - rMsg = subA.receive(1000); + rMsg = subA.receive(NEGATIVE_RECEIVE_TIMEOUT); assertNull(rMsg); // Disconnect subscriber @@ -379,13 +390,13 @@ public class DurableSubscriptionTest extends QpidTestCase // Check messages are recieved properly sendMatchingAndNonMatchingMessage(session, producer); - rMsg = subB.receive(1000); + rMsg = subB.receive(NEGATIVE_RECEIVE_TIMEOUT); assertNotNull(rMsg); assertEquals("Content was wrong", "testResubscribeWithChangedSelector2", ((TextMessage) rMsg).getText()); - rMsg = subB.receive(1000); + rMsg = subB.receive(NEGATIVE_RECEIVE_TIMEOUT); assertNull(rMsg); session.unsubscribe("testResubscribeWithChangedSelector"); } @@ -429,5 +440,5 @@ public class DurableSubscriptionTest extends QpidTestCase public static junit.framework.Test suite() { return new junit.framework.TestSuite(DurableSubscriptionTest.class); - } + } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java index 95b90481c7..f8ba7060a9 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java @@ -57,7 +57,7 @@ public class TopicSessionTest extends QpidTestCase AMQConnection con = (AMQConnection) getConnection("guest", "guest"); AMQTopic topic = new AMQTopic(con.getDefaultTopicExchangeName(), "MyTopic"); - TopicSession session1 = con.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE); + TopicSession session1 = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE); TopicSubscriber sub = session1.createDurableSubscriber(topic, "subscription0"); TopicPublisher publisher = session1.createPublisher(topic); @@ -65,10 +65,11 @@ public class TopicSessionTest extends QpidTestCase TextMessage tm = session1.createTextMessage("Hello"); publisher.publish(tm); + session1.commit(); tm = (TextMessage) sub.receive(2000); assertNotNull(tm); - + session1.commit(); session1.unsubscribe("subscription0"); try @@ -104,15 +105,17 @@ public class TopicSessionTest extends QpidTestCase AMQTopic topic = new AMQTopic(con, "MyTopic1" + String.valueOf(shutdown)); AMQTopic topic2 = new AMQTopic(con, "MyOtherTopic1" + String.valueOf(shutdown)); - TopicSession session1 = con.createTopicSession(false, AMQSession.AUTO_ACKNOWLEDGE); + TopicSession session1 = con.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE); TopicSubscriber sub = session1.createDurableSubscriber(topic, "subscription0"); TopicPublisher publisher = session1.createPublisher(null); con.start(); publisher.publish(topic, session1.createTextMessage("hello")); + session1.commit(); TextMessage m = (TextMessage) sub.receive(2000); assertNotNull(m); + session1.commit(); if (shutdown) { @@ -120,17 +123,20 @@ public class TopicSessionTest extends QpidTestCase con.close(); con = (AMQConnection) getConnection("guest", "guest"); con.start(); - session1 = con.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE); + session1 = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE); publisher = session1.createPublisher(null); } TopicSubscriber sub2 = session1.createDurableSubscriber(topic2, "subscription0"); publisher.publish(topic, session1.createTextMessage("hello")); + session1.commit(); if (!shutdown) { m = (TextMessage) sub.receive(2000); assertNull(m); + session1.commit(); } publisher.publish(topic2, session1.createTextMessage("goodbye")); + session1.commit(); m = (TextMessage) sub2.receive(2000); assertNotNull(m); assertEquals("goodbye", m.getText()); @@ -143,25 +149,29 @@ public class TopicSessionTest extends QpidTestCase AMQConnection con1 = (AMQConnection) getConnection("guest", "guest", "clientid"); AMQTopic topic = new AMQTopic(con1, "MyTopic3"); - TopicSession session1 = con1.createTopicSession(false, AMQSession.AUTO_ACKNOWLEDGE); + TopicSession session1 = con1.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE); TopicPublisher publisher = session1.createPublisher(topic); AMQConnection con2 = (AMQConnection) getConnection("guest", "guest", "clientid"); - TopicSession session2 = con2.createTopicSession(false, AMQSession.AUTO_ACKNOWLEDGE); + TopicSession session2 = con2.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE); TopicSubscriber sub = session2.createDurableSubscriber(topic, "subscription0"); con2.start(); publisher.publish(session1.createTextMessage("Hello")); + session1.commit(); TextMessage tm = (TextMessage) sub.receive(2000); + session2.commit(); assertNotNull(tm); con2.close(); publisher.publish(session1.createTextMessage("Hello2")); + session1.commit(); con2 = (AMQConnection) getConnection("guest", "guest", "clientid"); - session2 = con2.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE); + session2 = con2.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE); sub = session2.createDurableSubscriber(topic, "subscription0"); con2.start(); tm = (TextMessage) sub.receive(2000); + session2.commit(); assertNotNull(tm); assertEquals("Hello2", tm.getText()); session2.unsubscribe("subscription0"); @@ -174,12 +184,13 @@ public class TopicSessionTest extends QpidTestCase AMQConnection con = (AMQConnection) getConnection("guest", "guest"); AMQTopic topic = new AMQTopic(con, "MyTopic4"); - TopicSession session1 = con.createTopicSession(false, AMQSession.AUTO_ACKNOWLEDGE); + TopicSession session1 = con.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE); TopicPublisher publisher = session1.createPublisher(topic); MessageConsumer consumer1 = session1.createConsumer(topic); con.start(); TextMessage tm = session1.createTextMessage("Hello"); publisher.publish(tm); + session1.commit(); tm = (TextMessage) consumer1.receive(10000L); assertNotNull(tm); String msgText = tm.getText(); @@ -188,15 +199,19 @@ public class TopicSessionTest extends QpidTestCase msgText = tm.getText(); assertNull(msgText); publisher.publish(tm); + session1.commit(); tm = (TextMessage) consumer1.receive(10000L); assertNotNull(tm); + session1.commit(); msgText = tm.getText(); assertNull(msgText); tm.clearBody(); tm.setText("Now we are not null"); publisher.publish(tm); + session1.commit(); tm = (TextMessage) consumer1.receive(2000); assertNotNull(tm); + session1.commit(); msgText = tm.getText(); assertEquals("Now we are not null", msgText); @@ -204,7 +219,9 @@ public class TopicSessionTest extends QpidTestCase msgText = tm.getText(); assertEquals("Empty string not returned", "", msgText); publisher.publish(tm); + session1.commit(); tm = (TextMessage) consumer1.receive(2000); + session1.commit(); assertNotNull(tm); assertEquals("Empty string not returned", "", msgText); con.close(); @@ -213,7 +230,7 @@ public class TopicSessionTest extends QpidTestCase public void testSendingSameMessage() throws Exception { AMQConnection conn = (AMQConnection) getConnection("guest", "guest"); - TopicSession session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicSession session = conn.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); TemporaryTopic topic = session.createTemporaryTopic(); assertNotNull(topic); TopicPublisher producer = session.createPublisher(topic); @@ -221,14 +238,16 @@ public class TopicSessionTest extends QpidTestCase conn.start(); TextMessage sentMessage = session.createTextMessage("Test Message"); producer.send(sentMessage); + session.commit(); TextMessage receivedMessage = (TextMessage) consumer.receive(2000); assertNotNull(receivedMessage); assertEquals(sentMessage.getText(), receivedMessage.getText()); producer.send(sentMessage); + session.commit(); receivedMessage = (TextMessage) consumer.receive(2000); assertNotNull(receivedMessage); assertEquals(sentMessage.getText(), receivedMessage.getText()); - + session.commit(); conn.close(); } @@ -236,17 +255,18 @@ public class TopicSessionTest extends QpidTestCase public void testTemporaryTopic() throws Exception { AMQConnection conn = (AMQConnection) getConnection("guest", "guest"); - TopicSession session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicSession session = conn.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); TemporaryTopic topic = session.createTemporaryTopic(); assertNotNull(topic); TopicPublisher producer = session.createPublisher(topic); MessageConsumer consumer = session.createConsumer(topic); conn.start(); producer.send(session.createTextMessage("hello")); + session.commit(); TextMessage tm = (TextMessage) consumer.receive(2000); assertNotNull(tm); assertEquals("hello", tm.getText()); - + session.commit(); try { topic.delete(); @@ -291,7 +311,7 @@ public class TopicSessionTest extends QpidTestCase AMQTopic topic = new AMQTopic(con, "testNoLocal"); - TopicSession session1 = con.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE); + TopicSession session1 = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE); TopicSubscriber noLocal = session1.createSubscriber(topic, "", true); TopicSubscriber select = session1.createSubscriber(topic, "Selector = 'select'", false); TopicSubscriber normal = session1.createSubscriber(topic); @@ -304,15 +324,17 @@ public class TopicSessionTest extends QpidTestCase //send message to all consumers publisher.publish(session1.createTextMessage("hello-new2")); - + session1.commit(); //test normal subscriber gets message m = (TextMessage) normal.receive(1000); assertNotNull(m); - + session1.commit(); + //test selector subscriber doesn't message m = (TextMessage) select.receive(1000); assertNull(m); - + session1.commit(); + //test nolocal subscriber doesn't message m = (TextMessage) noLocal.receive(1000); if (m != null) @@ -326,21 +348,24 @@ public class TopicSessionTest extends QpidTestCase message.setStringProperty("Selector", "select"); publisher.publish(message); - + session1.commit(); + //test normal subscriber gets message m = (TextMessage) normal.receive(1000); assertNotNull(m); - + session1.commit(); + //test selector subscriber does get message m = (TextMessage) select.receive(1000); assertNotNull(m); + session1.commit(); //test nolocal subscriber doesn't message m = (TextMessage) noLocal.receive(100); assertNull(m); AMQConnection con2 = (AMQConnection) getConnection("guest", "guest", "foo"); - TopicSession session2 = con2.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE); + TopicSession session2 = con2.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE); TopicPublisher publisher2 = session2.createPublisher(topic); @@ -348,14 +373,17 @@ public class TopicSessionTest extends QpidTestCase message.setStringProperty("Selector", "select"); publisher2.publish(message); + session2.commit(); //test normal subscriber gets message m = (TextMessage) normal.receive(1000); assertNotNull(m); + session1.commit(); //test selector subscriber does get message m = (TextMessage) select.receive(1000); assertNotNull(m); + session1.commit(); //test nolocal subscriber does message m = (TextMessage) noLocal.receive(100); @@ -378,7 +406,7 @@ public class TopicSessionTest extends QpidTestCase // Setup Topic AMQTopic topic = new AMQTopic(con, "testNoLocal"); - TopicSession session = con.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE); + TopicSession session = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE); // Setup subscriber with selector TopicSubscriber selector = session.createSubscriber(topic, "Selector = 'select'", false); @@ -391,13 +419,15 @@ public class TopicSessionTest extends QpidTestCase // Send non-matching message message = session.createTextMessage("non-matching 1"); publisher.publish(message); + session.commit(); // Send and consume matching message message = session.createTextMessage("hello"); message.setStringProperty("Selector", "select"); publisher.publish(message); - + session.commit(); + m = (TextMessage) selector.receive(1000); assertNotNull("should have received message", m); assertEquals("Message contents were wrong", "hello", m.getText()); @@ -405,7 +435,8 @@ public class TopicSessionTest extends QpidTestCase // Send non-matching message message = session.createTextMessage("non-matching 2"); publisher.publish(message); - + session.commit(); + // Assert queue count is 0 long depth = ((AMQTopicSessionAdaptor) session).getSession().getQueueDepth(topic); assertEquals("Queue depth was wrong", 0, depth); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java index f7d8152c83..1bef07fcd5 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java @@ -28,7 +28,7 @@ public class FailoverBaseCase extends QpidTestCase { public static int FAILING_VM_PORT = 2; - public static int FAILING_PORT = DEFAULT_PORT + 1; + public static int FAILING_PORT = DEFAULT_PORT + 100; protected int failingPort; @@ -42,7 +42,7 @@ public class FailoverBaseCase extends QpidTestCase } else { - failingPort = FAILING_PORT; + failingPort = FAILING_PORT; } } @@ -55,7 +55,7 @@ public class FailoverBaseCase extends QpidTestCase { super.setUp(); setSystemProperty("QPID_WORK", System.getProperty("java.io.tmpdir")+"/"+getFailingPort()); - startBroker(getFailingPort()); + startBroker(failingPort); } /** @@ -67,14 +67,16 @@ public class FailoverBaseCase extends QpidTestCase public Connection getConnection() throws Exception { Connection conn = - getConnectionFactory("failover").createConnection("guest", "guest"); + (Boolean.getBoolean("profile.use_ssl"))? + getConnectionFactory("failover.ssl").createConnection("guest", "guest"): + getConnectionFactory("failover").createConnection("guest", "guest"); _connections.add(conn); return conn; } public void tearDown() throws Exception { - stopBroker(getFailingPort()); + stopBroker(_broker.equals(VM)?FAILING_PORT:FAILING_PORT); super.tearDown(); FileUtils.deleteDirectory(System.getProperty("java.io.tmpdir")+"/"+getFailingPort()); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidTestCase.java index e7218d6975..db096710dc 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidTestCase.java @@ -157,7 +157,7 @@ public class QpidTestCase extends TestCase private static final String VERSION_08 = "0-8"; private static final String VERSION_010 = "0-10"; - private static final String QPID_HOME = "QPID_HOME"; + protected static final String QPID_HOME = "QPID_HOME"; protected static int DEFAULT_VM_PORT = 1; protected static int DEFAULT_PORT = Integer.getInteger("test.port", 5672); @@ -182,6 +182,8 @@ public class QpidTestCase extends TestCase protected List<Connection> _connections = new ArrayList<Connection>(); public static final String QUEUE = "queue"; public static final String TOPIC = "topic"; + /** Map to hold test defined environment properties */ + private Map<String,String> _env; public QpidTestCase(String name) @@ -199,6 +201,9 @@ public class QpidTestCase extends TestCase _testName = getClass().getSimpleName() + "." + getName(); String qname = getClass().getName() + "." + getName(); + // Initalise this for each test run + _env = new HashMap<String, String>(); + PrintStream oldOut = System.out; PrintStream oldErr = System.err; PrintStream out = null; @@ -435,6 +440,16 @@ public class QpidTestCase extends TestCase //Add the test name to the broker run. env.put("QPID_PNAME", "-DPNAME=\"" + _testName + "\""); env.put("QPID_WORK", System.getProperty("QPID_WORK")); + + // Add all the environment settings the test requested + if (!_env.isEmpty()) + { + for(Map.Entry<String,String> entry : _env.entrySet()) + { + env.put(entry.getKey() ,entry.getValue()); + } + } + process = pb.start(); Piper p = new Piper(process.getInputStream(), @@ -677,6 +692,18 @@ public class QpidTestCase extends TestCase } /** + * Add an environtmen variable for the external broker environment + * + * @param property the property to set + * @param value the value to set it to + */ + protected void setBrokerEnvironment(String property, String value) + { + _env.put(property,value); + } + + + /** * Check whether the broker is an 0.8 * * @return true if the broker is an 0_8 version, false otherwise. diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java index df8dd0b85b..44ac5b4838 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java @@ -31,6 +31,7 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; +import java.util.ArrayList; import java.util.List; /** @@ -118,7 +119,7 @@ public class LogMonitor * @throws java.io.FileNotFoundException if the Log file can nolonger be found * @throws IOException thrown when reading the log file */ - public boolean waitForMessage(String message, long wait) + public boolean waitForMessage(String message, long wait, boolean printFileOnFailure) throws FileNotFoundException, IOException { // Loop through alerts until we're done or wait ms seconds have passed, @@ -126,20 +127,35 @@ public class LogMonitor BufferedReader reader = new BufferedReader(new FileReader(_logfile)); boolean found = false; long endtime = System.currentTimeMillis() + wait; + ArrayList<String> contents = new ArrayList<String>(); while (!found && System.currentTimeMillis() < endtime) { while (reader.ready()) { String line = reader.readLine(); + contents.add(line); if (line.contains(message)) { found = true; } } } - + if (!found && printFileOnFailure) + { + for (String line : contents) + { + System.out.println(line); + } + } return found; } + + + public boolean waitForMessage(String messageCountAlert, long alertLogWaitPeriod) throws FileNotFoundException, IOException + { + return waitForMessage(messageCountAlert, alertLogWaitPeriod, true); + } + /** * Read the log file in to memory as a String diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java index d1a0df30a9..b721e27726 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java @@ -30,25 +30,25 @@ import java.util.List; public class LogMonitorTest extends TestCase { + private LogMonitor _monitor; + + @Override + public void setUp() throws Exception + { + _monitor = new LogMonitor(); + _monitor.getMonitoredFile().deleteOnExit(); // Make sure we clean up + } + /** * Test that a new file is created when attempting to set up a monitor with * the default constructor. */ public void testMonitor() { - // Validate that a NPE is thrown with null input - try - { - LogMonitor montior = new LogMonitor(); - //Validte that the monitor is now running on a new file - assertTrue("New file does not have correct name:" + montior. - getMonitoredFile().getName(), - montior.getMonitoredFile().getName().contains("LogMonitor")); - } - catch (IOException ioe) - { - fail("IOE thrown:" + ioe); - } + //Validate that the monitor is now running on a new file + assertTrue("New file does not have correct name:" + _monitor. + getMonitoredFile().getName(), + _monitor.getMonitoredFile().getName().contains("LogMonitor")); } /** @@ -63,13 +63,11 @@ public class LogMonitorTest extends TestCase File testFile = File.createTempFile("testMonitorFile", ".log"); testFile.deleteOnExit(); - LogMonitor monitor; - //Ensure that we can create a monitor on a file try { - monitor = new LogMonitor(testFile); - assertEquals(testFile, monitor.getMonitoredFile()); + _monitor = new LogMonitor(testFile); + assertEquals(testFile, _monitor.getMonitoredFile()); } catch (IOException ioe) { @@ -136,13 +134,12 @@ public class LogMonitorTest extends TestCase */ public void testFindMatches_Match() throws IOException { - LogMonitor monitor = new LogMonitor(); String message = getName() + ": Test Message"; Logger.getRootLogger().warn(message); - validateLogContainsMessage(monitor, message); + validateLogContainsMessage(_monitor, message); } /** @@ -152,21 +149,17 @@ public class LogMonitorTest extends TestCase */ public void testFindMatches_NoMatch() throws IOException { - LogMonitor monitor = new LogMonitor(); - String message = getName() + ": Test Message"; Logger.getRootLogger().warn(message); String notLogged = "This text was not logged"; - validateLogDoesNotContainsMessage(monitor, notLogged); + validateLogDoesNotContainsMessage(_monitor, notLogged); } public void testWaitForMessage_Found() throws IOException { - LogMonitor monitor = new LogMonitor(); - String message = getName() + ": Test Message"; long TIME_OUT = 2000; @@ -174,13 +167,11 @@ public class LogMonitorTest extends TestCase logMessageWithDelay(message, TIME_OUT / 2); assertTrue("Message was not logged ", - monitor.waitForMessage(message, TIME_OUT)); + _monitor.waitForMessage(message, TIME_OUT)); } public void testWaitForMessage_Timeout() throws IOException { - LogMonitor monitor = new LogMonitor(); - String message = getName() + ": Test Message"; long TIME_OUT = 2000; @@ -189,41 +180,37 @@ public class LogMonitorTest extends TestCase // Verify that we can time out waiting for a message assertFalse("Message was logged ", - monitor.waitForMessage(message, TIME_OUT / 2)); + _monitor.waitForMessage(message, TIME_OUT / 2, false)); // Verify that the message did eventually get logged. assertTrue("Message was never logged.", - monitor.waitForMessage(message, TIME_OUT)); + _monitor.waitForMessage(message, TIME_OUT)); } public void testReset() throws IOException { - LogMonitor monitor = new LogMonitor(); - String message = getName() + ": Test Message"; Logger.getRootLogger().warn(message); - validateLogContainsMessage(monitor, message); + validateLogContainsMessage(_monitor, message); String LOG_RESET_TEXT = "Log Monitor Reset"; - validateLogDoesNotContainsMessage(monitor, LOG_RESET_TEXT); + validateLogDoesNotContainsMessage(_monitor, LOG_RESET_TEXT); - monitor.reset(); + _monitor.reset(); - assertEquals("", monitor.readFile()); + assertEquals("", _monitor.readFile()); } public void testRead() throws IOException { - LogMonitor monitor = new LogMonitor(); - String message = getName() + ": Test Message"; Logger.getRootLogger().warn(message); - String fileContents = monitor.readFile(); + String fileContents = _monitor.readFile(); assertTrue("Logged message not found when reading file.", fileContents.contains(message)); diff --git a/qpid/java/test-profiles/010Excludes b/qpid/java/test-profiles/010Excludes index 7577ad8da1..e238934124 100644 --- a/qpid/java/test-profiles/010Excludes +++ b/qpid/java/test-profiles/010Excludes @@ -4,6 +4,7 @@ org.apache.qpid.client.ResetMessageListenerTest#* //These tests are for the java broker org.apache.qpid.server.security.acl.SimpleACLTest#* org.apache.qpid.server.plugins.PluginTest#* +org.apache.qpid.server.BrokerStartupTest#* // This test is not finished org.apache.qpid.test.testcases.TTLTest#* diff --git a/qpid/java/test-profiles/cpp.ssl.excludes b/qpid/java/test-profiles/cpp.ssl.excludes index bf21ab9cc7..1828581d55 100644 --- a/qpid/java/test-profiles/cpp.ssl.excludes +++ b/qpid/java/test-profiles/cpp.ssl.excludes @@ -1 +1 @@ -org.apache.qpid.test.client.failover.FailoverTest#* +#org.apache.qpid.test.client.failover.FailoverTest#* diff --git a/qpid/java/test-profiles/default.testprofile b/qpid/java/test-profiles/default.testprofile index 864cf149d4..49d4a25b82 100644 --- a/qpid/java/test-profiles/default.testprofile +++ b/qpid/java/test-profiles/default.testprofile @@ -19,7 +19,8 @@ log4j.debug=false test.port=15672 test.mport=18999 test.port.ssl=15671 -test.port.alt=15673 +test.port.alt=15772 +test.port.alt.ssl=15771 test.exclude=true profile.excludes=08TransientExcludes diff --git a/qpid/java/test-profiles/test-provider.properties b/qpid/java/test-profiles/test-provider.properties index 2479b5a5f0..e5cb18da0b 100644 --- a/qpid/java/test-profiles/test-provider.properties +++ b/qpid/java/test-profiles/test-provider.properties @@ -21,13 +21,17 @@ test.port=5672 test.port.ssl=5671 -test.port.alt=5673 +test.port.alt=5772 +test.port.alt.ssl=5771 connectionfactory.default = amqp://username:password@clientid/test?brokerlist='tcp://localhost:${test.port}' connectionfactory.default.vm = amqp://username:password@clientid/test?brokerlist='vm://:1' connectionfactory.ssl = amqp://username:password@clientid/test?brokerlist='tcp://localhost:${test.port.ssl}?ssl='true'' connectionfactory.failover = amqp://username:password@clientid/test?brokerlist='tcp://localhost:${test.port.alt};tcp://localhost:${test.port}'&sync_ack='true'&sync_publish='all'&failover='roundrobin?cyclecount='20'' + +connectionfactory.failover.ssl = amqp://username:password@clientid/test?brokerlist='tcp://localhost:${test.port.alt.ssl}?ssl='true';tcp://localhost:${test.port.ssl}?ssl='true''&sync_ack='true'&sync_publish='all'&failover='roundrobin?cyclecount='20'' + connectionfactory.failover.vm = amqp://username:password@clientid/test?brokerlist='vm://:2;vm://:1' connectionfactory.connection1 = amqp://username:password@clientid/test?brokerlist='tcp://localhost:${test.port}' connectionfactory.connection2 = amqp://username:password@clientid/test?brokerlist='tcp://localhost:${test.port.alt}' diff --git a/qpid/python/Makefile b/qpid/python/Makefile new file mode 100644 index 0000000000..380115db41 --- /dev/null +++ b/qpid/python/Makefile @@ -0,0 +1,92 @@ +# +# 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. +# + +PREFIX=/usr/local +EXEC_PREFIX=$(PREFIX)/bin +DATA_DIR=$(PREFIX)/share + +PYTHON_LIB=$(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib(prefix='$(PREFIX)')") +PYTHON_VERSION=$(shell python -c "from distutils.sysconfig import get_python_version; print get_python_version()") + +ddfirst=$(shell ddir=$(DATA_DIR) && echo $${ddir:0:1}) +ifeq ($(ddfirst),/) +AMQP_SPEC_DIR=$(DATA_DIR)/amqp +else +AMQP_SPEC_DIR=$(PWD)/$(DATA_DIR)/amqp +endif + +DIRS=qmf qpid mllib models examples tests tests_0-8 tests_0-9 tests_0-10 +SRCS=$(shell find $(DIRS) -name "*.py") qpid_config.py +BUILD=build +TARGETS=$(SRCS:%.py=$(BUILD)/%.py) + +PYCC=python -c "import compileall, sys; compileall.compile_dir(sys.argv[1])" + +all: build + +$(BUILD)/%.py: %.py + @mkdir -p $(shell dirname $@) + ./preppy $(PYTHON_VERSION) < $< > $@ + +build: $(TARGETS) + +install: build + install -d $(PYTHON_LIB) + + install -d $(PYTHON_LIB)/mllib + install -pm 0644 LICENSE.txt NOTICE.txt $(BUILD)/mllib/*.* $(PYTHON_LIB)/mllib + $(PYCC) $(PYTHON_LIB)/mllib + + install -d $(PYTHON_LIB)/qpid + install -pm 0644 LICENSE.txt NOTICE.txt README.txt $(BUILD)/qpid/*.* $(PYTHON_LIB)/qpid + TDIR=$(shell mktemp -d) && \ + sed s@AMQP_SPEC_DIR=.*@AMQP_SPEC_DIR='"$(AMQP_SPEC_DIR)"'@ \ + $(BUILD)/qpid_config.py > $${TDIR}/qpid_config.py && \ + install -pm 0644 $${TDIR}/qpid_config.py $(PYTHON_LIB) && \ + rm -rf $${TDIR} + + install -d $(PYTHON_LIB)/qpid/tests + install -pm 0644 $(BUILD)/qpid/tests/*.* $(PYTHON_LIB)/qpid/tests + $(PYCC) $(PYTHON_LIB)/qpid + + install -d $(PYTHON_LIB)/qmf + install -pm 0644 LICENSE.txt NOTICE.txt qmf/*.* $(PYTHON_LIB)/qmf + $(PYCC) $(PYTHON_LIB)/qmf + + install -d $(PYTHON_LIB)/tests + install -pm 0644 $(BUILD)/tests/*.* $(PYTHON_LIB)/tests + $(PYCC) $(PYTHON_LIB)/tests + + install -d $(PYTHON_LIB)/tests_0-8 + install -pm 0644 $(BUILD)/tests_0-8/*.* $(PYTHON_LIB)/tests_0-8 + $(PYCC) $(PYTHON_LIB)/tests_0-8 + + install -d $(PYTHON_LIB)/tests_0-9 + install -pm 0644 $(BUILD)/tests_0-9/*.* $(PYTHON_LIB)/tests_0-9 + $(PYCC) $(PYTHON_LIB)/tests_0-9 + + install -d $(PYTHON_LIB)/tests_0-10 + install -pm 0644 $(BUILD)/tests_0-10/*.* $(PYTHON_LIB)/tests_0-10 + $(PYCC) $(PYTHON_LIB)/tests_0-10 + + install -d $(EXEC_PREFIX) + install -pm 0755 qpid-python-test commands/* $(EXEC_PREFIX) + +clean: + rm -rf $(BUILD) diff --git a/qpid/python/commands/qpid-config b/qpid/python/commands/qpid-config index c4ea5c5f2d..4cf9505c58 100755 --- a/qpid/python/commands/qpid-config +++ b/qpid/python/commands/qpid-config @@ -110,6 +110,12 @@ def Usage (): print " --force-if-not-empty Force delete of queue even if it's not empty" print " --force-if-used Force delete of queue even if it's currently used" print + print "Add Exchange <type> values:" + print " direct Direct exchange for point-to-point communication" + print " fanout Fanout exchange for broadcast communication" + print " topic Topic exchange that routes messages using binding keys with wildcards" + print " headers Headers exchange that matches header fields against the binding keys" + print print "Add Exchange Options:" print " --alternate-exchange [name of the alternate exchange]" print " In the event that a message cannot be routed, this is the name of the exchange to" @@ -190,6 +196,8 @@ class BrokerManager: if ex.durable: print "--durable", if MSG_SEQUENCE in args and args[MSG_SEQUENCE] == 1: print "--sequence", if IVE in args and args[IVE] == 1: print "--ive", + if ex.altExchange: + print "--alternate-exchange=%s" % ex._altExchange_.name, print def ExchangeListRecurse (self, filter): diff --git a/qpid/python/preppy b/qpid/python/preppy new file mode 100755 index 0000000000..22893dad03 --- /dev/null +++ b/qpid/python/preppy @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# 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. +# + +import os, re, sys + +ann = re.compile(r"([ \t]*)@([_a-zA-Z][_a-zA-Z0-9]*)([ \t\n\r]+def[ \t]+)([_a-zA-Z][_a-zA-Z0-9]*)") +line = re.compile(r"\n([ \t]*)[^ \t\n#]+") + +if len(sys.argv) == 2: + major, minor = [int(p) for p in sys.argv[1].split(".")] +elif len(sys.argv) == 1: + major, minor = sys.version_info[0:2] +else: + print "usage: %s [ version ] < input.py > output.py" % sys.argv[0] + sys.exit(-1) + +if major <= 2 and minor <= 3: + def process(input): + output = "" + pos = 0 + while True: + m = ann.search(input, pos) + if m: + indent, decorator, idef, function = m.groups() + output += input[pos:m.start()] + output += "%s#@%s%s%s" % (indent, decorator, idef, function) + pos = m.end() + + subst = "\n%s%s = %s(%s)\n" % (indent, function, decorator, function) + npos = pos + while True: + n = line.search(input, npos) + if not n: + input += subst + break + if len(n.group(1)) <= len(indent): + idx = n.start() + input = input[:idx] + subst + input[idx:] + break + npos = n.end() + else: + break + + output += input[pos:] + return output +else: + def process(input): + return input + +sys.stdout.write(process(sys.stdin.read())) diff --git a/qpid/python/qpid/compat.py b/qpid/python/qpid/compat.py index 26f60fb8aa..49273193df 100644 --- a/qpid/python/qpid/compat.py +++ b/qpid/python/qpid/compat.py @@ -26,3 +26,10 @@ try: from socket import SHUT_RDWR except ImportError: SHUT_RDWR = 2 + +try: + from traceback import format_exc +except ImportError: + import sys, traceback + def format_exc(): + return "".join(traceback.format_exception(*sys.exc_info())) diff --git a/qpid/python/qpid/datatypes.py b/qpid/python/qpid/datatypes.py index bba3f5b9ab..f832ddae34 100644 --- a/qpid/python/qpid/datatypes.py +++ b/qpid/python/qpid/datatypes.py @@ -132,7 +132,7 @@ class Serial: return hash(self.value) def __cmp__(self, other): - if other is None: + if other.__class__ not in (int, long, Serial): return 1 other = serial(other) @@ -150,7 +150,10 @@ class Serial: return Serial(self.value + other) def __sub__(self, other): - return Serial(self.value - other) + if isinstance(other, Serial): + return self.value - other.value + else: + return Serial(self.value - other) def __repr__(self): return "serial(%s)" % self.value @@ -169,7 +172,7 @@ class Range: def __contains__(self, n): return self.lower <= n and n <= self.upper - + def __iter__(self): i = self.lower while i <= self.upper: @@ -230,7 +233,7 @@ class RangedSet: def add(self, lower, upper = None): self.add_range(Range(lower, upper)) - + def __iter__(self): return iter(self.ranges) diff --git a/qpid/python/qpid/management.py b/qpid/python/qpid/management.py index c006da76f5..65807548e6 100644 --- a/qpid/python/qpid/management.py +++ b/qpid/python/qpid/management.py @@ -234,8 +234,7 @@ class managementClient: #======================================================== # User API - interacts with the class's user #======================================================== - def __init__ (self, amqpSpec, ctrlCb=None, configCb=None, instCb=None, methodCb=None, closeCb=None): - self.spec = amqpSpec + def __init__ (self, unused=None, ctrlCb=None, configCb=None, instCb=None, methodCb=None, closeCb=None): self.ctrlCb = ctrlCb self.configCb = configCb self.instCb = instCb @@ -268,7 +267,7 @@ class managementClient: self.channels.append (mch) self.incOutstanding (mch) - codec = Codec (self.spec) + codec = Codec () self.setHeader (codec, ord ('B')) msg = mch.message(codec.encoded) mch.send ("qpid.management", msg) @@ -285,7 +284,7 @@ class managementClient: def getObjects (self, channel, userSequence, className, bank=0): """ Request immediate content from broker """ - codec = Codec (self.spec) + codec = Codec () self.setHeader (codec, ord ('G'), userSequence) ft = {} ft["_class"] = className @@ -353,7 +352,7 @@ class managementClient: #======================================================== def topicCb (self, ch, msg): """ Receive messages via the topic queue of a particular channel. """ - codec = Codec (self.spec, msg.body) + codec = Codec (msg.body) while True: hdr = self.checkHeader (codec) if hdr == None: @@ -372,7 +371,7 @@ class managementClient: def replyCb (self, ch, msg): """ Receive messages via the reply queue of a particular channel. """ - codec = Codec (self.spec, msg.body) + codec = Codec (msg.body) hdr = self.checkHeader (codec) if hdr == None: return @@ -498,7 +497,7 @@ class managementClient: data = codec.read_uuid () elif typecode == 15: # FTABLE data = {} - sc = Codec(codec.spec, codec.read_vbin32()) + sc = Codec(codec.read_vbin32()) if sc.encoded: count = sc.read_uint32() while count > 0: @@ -599,7 +598,7 @@ class managementClient: self.ctrlCb (ch.context, self.CTRL_BROKER_INFO, ch.brokerInfo) # Send a package request - sendCodec = Codec (self.spec) + sendCodec = Codec () seq = self.seqMgr.reserve ("outstanding") self.setHeader (sendCodec, ord ('P'), seq) smsg = ch.message(sendCodec.encoded) @@ -611,7 +610,7 @@ class managementClient: self.packages[pname] = {} # Send a class request - sendCodec = Codec (self.spec) + sendCodec = Codec () seq = self.seqMgr.reserve ("outstanding") self.setHeader (sendCodec, ord ('Q'), seq) self.incOutstanding (ch) @@ -631,7 +630,7 @@ class managementClient: if (cname, hash) not in self.packages[pname]: # Send a schema request - sendCodec = Codec (self.spec) + sendCodec = Codec () seq = self.seqMgr.reserve ("outstanding") self.setHeader (sendCodec, ord ('S'), seq) self.incOutstanding (ch) @@ -885,7 +884,7 @@ class managementClient: def method (self, channel, userSequence, objId, classId, methodName, args): """ Invoke a method on an object """ - codec = Codec (self.spec) + codec = Codec () sequence = self.seqMgr.reserve ((userSequence, classId, methodName)) self.setHeader (codec, ord ('M'), sequence) objId.encode(codec) diff --git a/qpid/python/qpid/managementdata.py b/qpid/python/qpid/managementdata.py index c0d32d46cf..655ee13464 100644 --- a/qpid/python/qpid/managementdata.py +++ b/qpid/python/qpid/managementdata.py @@ -212,7 +212,6 @@ class ManagementData: sock.settimeout(10) self.conn = Connection (sock, username=self.broker.username, password=self.broker.password) - self.spec = self.conn.spec def aborted(): raise Timeout("Waiting for connection to be established with broker") oldAborted = self.conn.aborted @@ -223,7 +222,7 @@ class ManagementData: sock.settimeout(oldTimeout) self.conn.aborted = oldAborted - self.mclient = managementClient (self.spec, self.ctrlHandler, self.configHandler, + self.mclient = managementClient ("unused", self.ctrlHandler, self.configHandler, self.instHandler, self.methodReply, self.closeHandler) self.mclient.schemaListener (self.schemaHandler) self.mch = self.mclient.addChannel (self.conn.session(self.sessionId)) diff --git a/qpid/python/qpid/messaging.py b/qpid/python/qpid/messaging.py index 9b3fecbf9b..3c41a2c417 100644 --- a/qpid/python/qpid/messaging.py +++ b/qpid/python/qpid/messaging.py @@ -30,12 +30,13 @@ Areas that still need work: - protocol negotiation/multiprotocol impl """ -import connection, time, socket, sys, traceback +import connection, time, socket, sys, compat from codec010 import StringCodec -from datatypes import timestamp, uuid4, RangedSet, Message as Message010 +from datatypes import timestamp, uuid4, RangedSet, Message as Message010, Serial +from exceptions import Timeout from logging import getLogger -from ops import PRIMITIVE -from session import Client, INCOMPLETE +from ops import PRIMITIVE, delivery_mode +from session import Client, INCOMPLETE, SessionDetached from threading import Thread, RLock, Condition from util import connect @@ -101,7 +102,10 @@ class Constant: UNLIMITED = Constant("UNLIMITED", 0xFFFFFFFFL) -class ConnectError(Exception): +class ConnectionError(Exception): + pass + +class ConnectError(ConnectionError): pass class Connection(Lockable): @@ -142,12 +146,35 @@ class Connection(Lockable): self.host = host self.port = default(port, AMQP_PORT) self.started = False - self._conn = None self.id = str(uuid4()) self.session_counter = 0 self.sessions = {} + self.reconnect = False + self._connected = False self._lock = RLock() self._condition = Condition(self._lock) + self._modcount = Serial(0) + self.error = None + self._driver = Driver(self) + self._driver.start() + + def wakeup(self): + self._modcount += 1 + self._driver.wakeup() + + def catchup(self, exc=ConnectionError): + mc = self._modcount + self.wait(lambda: not self._driver._modcount < mc) + self.check_error(exc) + + def check_error(self, exc=ConnectionError): + if self.error: + raise exc(*self.error) + + def ewait(self, predicate, timeout=None, exc=ConnectionError): + result = self.wait(lambda: self.error or predicate(), timeout) + self.check_error(exc) + return result @synchronized def session(self, name=None, transactional=False): @@ -173,8 +200,7 @@ class Connection(Lockable): else: ssn = Session(self, name, self.started, transactional=transactional) self.sessions[name] = ssn - if self._conn is not None: - ssn._attach() + self.wakeup() return ssn @synchronized @@ -186,38 +212,25 @@ class Connection(Lockable): """ Connect to the remote endpoint. """ - if self._conn is not None: - return - try: - self._socket = connect(self.host, self.port) - except socket.error, e: - raise ConnectError(e) - self._conn = connection.Connection(self._socket) - try: - self._conn.start() - except connection.VersionError, e: - raise ConnectError(e) - - for ssn in self.sessions.values(): - ssn._attach() + self._connected = True + self.wakeup() + self.ewait(lambda: self._driver._connected, exc=ConnectError) @synchronized def disconnect(self): """ Disconnect from the remote endpoint. """ - if self._conn is not None: - self._conn.close() - self._conn = None - for ssn in self.sessions.values(): - ssn._disconnected() + self._connected = False + self.wakeup() + self.ewait(lambda: not self._driver._connected) @synchronized def connected(self): """ Return true if the connection is connected, false otherwise. """ - return self._conn is not None + return self._connected @synchronized def start(self): @@ -255,6 +268,7 @@ class Pattern: def __init__(self, value): self.value = value + # XXX: this should become part of the driver def _bind(self, ssn, exchange, queue): ssn.exchange_bind(exchange=exchange, queue=queue, binding_key=self.value.replace("*", "#")) @@ -263,13 +277,33 @@ FILTER_DEFAULTS = { "topic": Pattern("*") } -def delegate(session): +def delegate(handler, session): class Delegate(Client): def message_transfer(self, cmd): - session._message_transfer(cmd) + handler._message_transfer(session, cmd) return Delegate +class SessionError(Exception): + pass + +class Disconnected(SessionError): + """ + Exception raised when an operation is attempted that is illegal when + disconnected. + """ + pass + +class NontransactionalSession(SessionError): + """ + Exception raised when commit or rollback is attempted on a non + transactional session. + """ + pass + +class TransactionAborted(SessionError): + pass + class Session(Lockable): """ @@ -281,18 +315,26 @@ class Session(Lockable): self.connection = connection self.name = name self.started = started + self.transactional = transactional - self._ssn = None + + self.committing = False + self.committed = True + self.aborting = False + self.aborted = False + self.senders = [] self.receivers = [] - self.closing = False + self.outgoing = [] self.incoming = [] - self.closed = False self.unacked = [] - if self.transactional: - self.acked = [] - self._lock = RLock() - self._condition = Condition(self._lock) + self.acked = [] + + self.closing = False + self.closed = False + + self._lock = connection._lock + self._condition = connection._condition self.thread = Thread(target = self.run) self.thread.setDaemon(True) self.thread.start() @@ -300,60 +342,17 @@ class Session(Lockable): def __repr__(self): return "<Session %s>" % self.name - def _attach(self): - self._ssn = self.connection._conn.session(self.name, delegate=delegate(self)) - self._ssn.auto_sync = False - self._ssn.invoke_lock = self._lock - self._ssn.lock = self._lock - self._ssn.condition = self._condition - if self.transactional: - self._ssn.tx_select() - for link in self.senders + self.receivers: - link._link() - - def _disconnected(self): - self._ssn = None - for link in self.senders + self.receivers: - link._disconnected() + def wakeup(self): + self.connection.wakeup() - @synchronized - def _message_transfer(self, cmd): - m = Message010(cmd.payload) - m.headers = cmd.headers - m.id = cmd.id - msg = self._decode(m) - rcv = self.receivers[int(cmd.destination)] - msg._receiver = rcv - log.debug("RECV [%s] %s", self, msg) - self.incoming.append(msg) - self.notifyAll() - return INCOMPLETE + def catchup(self, exc=SessionError): + self.connection.catchup(exc) - def _decode(self, message): - dp = message.get("delivery_properties") - mp = message.get("message_properties") - ap = mp.application_headers - enc, dec = get_codec(mp.content_type) - content = dec(message.body) - msg = Message(content) - msg.id = mp.message_id - if ap is not None: - msg.to = ap.get("to") - msg.subject = ap.get("subject") - msg.user_id = mp.user_id - if mp.reply_to is not None: - msg.reply_to = reply_to2addr(mp.reply_to) - msg.correlation_id = mp.correlation_id - msg.properties = mp.application_headers - msg.content_type = mp.content_type - msg._transfer_id = message.id - return msg + def check_error(self, exc=SessionError): + self.connection.check_error(exc) - def _exchange_query(self, address): - # XXX: auto sync hack is to avoid deadlock on future - result = self._ssn.exchange_query(name=address, sync=True) - self._ssn.sync() - return result.get() + def ewait(self, predicate, timeout=None, exc=SessionError): + return self.connection.ewait(predicate, timeout, exc) @synchronized def sender(self, target): @@ -368,8 +367,11 @@ class Session(Lockable): """ sender = Sender(self, len(self.senders), target) self.senders.append(sender) - if self._ssn is not None: - sender._link() + self.wakeup() + # XXX: because of the lack of waiting here we can end up getting + # into the driver loop with messages sent for senders that haven't + # been linked yet, something similar can probably happen for + # receivers return sender @synchronized @@ -386,8 +388,7 @@ class Session(Lockable): receiver = Receiver(self, len(self.receivers), source, filter, self.started) self.receivers.append(receiver) - if self._ssn is not None: - receiver._link() + self.wakeup() return receiver @synchronized @@ -419,6 +420,7 @@ class Session(Lockable): timeout): msg = self._pop(predicate) if msg is not None: + msg._receiver.returned += 1 self.unacked.append(msg) log.debug("RETR [%s] %s", self, msg) return msg @@ -438,20 +440,13 @@ class Session(Lockable): else: messages = [message] - ids = RangedSet(*[m._transfer_id for m in messages]) - for range in ids: - self._ssn.receiver._completed.add_range(range) - self._ssn.channel.session_completed(self._ssn.receiver._completed) - self._ssn.message_accept(ids, sync=True) - self._ssn.sync() - for m in messages: - try: - self.unacked.remove(m) - except ValueError: - pass - if self.transactional: - self.acked.append(m) + self.unacked.remove(m) + self.acked.append(m) + + self.wakeup() + self.wait(lambda: self.connection.error or not [m for m in messages if m in self.acked]) + self.check_error() @synchronized def commit(self): @@ -461,11 +456,12 @@ class Session(Lockable): """ if not self.transactional: raise NontransactionalSession() - if self._ssn is None: - raise Disconnected() - self._ssn.tx_commit(sync=True) - del self.acked[:] - self._ssn.sync() + self.committing = True + self.wakeup() + self.ewait(lambda: not self.committing) + if self.aborted: + raise TransactionAborted() + assert self.committed @synchronized def rollback(self): @@ -475,21 +471,10 @@ class Session(Lockable): """ if not self.transactional: raise NontransactionalSession() - if self._ssn is None: - raise Disconnected() - - ids = RangedSet(*[m._transfer_id for m in self.acked + self.unacked + self.incoming]) - for range in ids: - self._ssn.receiver._completed.add_range(range) - self._ssn.channel.session_completed(self._ssn.receiver._completed) - self._ssn.message_release(ids) - self._ssn.tx_rollback(sync=True) - - del self.incoming[:] - del self.unacked[:] - del self.acked[:] - - self._ssn.sync() + self.aborting = True + self.wakeup() + self.ewait(lambda: not self.aborting) + assert self.aborted @synchronized def start(self): @@ -538,13 +523,12 @@ class Session(Lockable): link.close() self.closing = True - self.notifyAll() + self.wakeup() + self.catchup() self.wait(lambda: self.closed) while self.thread.isAlive(): self.thread.join(3) self.thread = None - self._ssn.close() - self._ssn = None self.connection._remove_session(self) def parse_addr(address): @@ -562,18 +546,7 @@ def reply_to2addr(reply_to): else: return "%s/%s" % (reply_to.exchange, reply_to.routing_key) -class Disconnected(Exception): - """ - Exception raised when an operation is attempted that is illegal when - disconnected. - """ - pass - -class NontransactionalSession(Exception): - """ - Exception raised when commit or rollback is attempted on a non - transactional session. - """ +class SendError(SessionError): pass class Sender(Lockable): @@ -587,29 +560,20 @@ class Sender(Lockable): self.index = index self.target = target self.closed = False - self._ssn = None - self._exchange = None - self._routing_key = None - self._subject = None self._lock = self.session._lock self._condition = self.session._condition - def _link(self): - self._ssn = self.session._ssn - node, self._subject = parse_addr(self.target) - result = self.session._exchange_query(node) - if result.not_found: - # XXX: should check 'create' option - self._ssn.queue_declare(queue=node, durable=False, sync=True) - self._ssn.sync() - self._exchange = "" - self._routing_key = node - else: - self._exchange = node - self._routing_key = self._subject + def wakeup(self): + self.session.wakeup() - def _disconnected(self): - self._ssn = None + def catchup(self, exc=SendError): + self.session.catchup(exc) + + def check_error(self, exc=SendError): + self.session.check_error(exc) + + def ewait(self, predicate, timeout=None, exc=SendError): + return self.session.ewait(predicate, timeout, exc) @synchronized def send(self, object): @@ -623,56 +587,35 @@ class Sender(Lockable): @param object: the message or content to send """ - if self._ssn is None: + if not self.session.connection._connected or self.session.closing: raise Disconnected() if isinstance(object, Message): message = object else: message = Message(object) - # XXX: what if subject is specified for a normal queue? - if self._routing_key is None: - rk = message.subject - else: - rk = self._routing_key - # XXX: do we need to query to figure out how to create the reply-to interoperably? - if message.reply_to: - rt = self._ssn.reply_to(*parse_addr(message.reply_to)) - else: - rt = None - dp = self._ssn.delivery_properties(routing_key=rk) - mp = self._ssn.message_properties(message_id=message.id, - user_id=message.user_id, - reply_to=rt, - correlation_id=message.correlation_id, - content_type=message.content_type, - application_headers=message.properties) - if message.subject is not None: - if mp.application_headers is None: - mp.application_headers = {} - mp.application_headers["subject"] = message.subject - if message.to is not None: - if mp.application_headers is None: - mp.application_headers = {} - mp.application_headers["to"] = message.to - enc, dec = get_codec(message.content_type) - body = enc(message.content) - self._ssn.message_transfer(destination=self._exchange, - message=Message010(dp, mp, body), - sync=True) - log.debug("SENT [%s] %s", self.session, message) - self._ssn.sync() + + # XXX: what if we send the same message to multiple senders? + message._sender = self + self.session.outgoing.append(message) + + self.wakeup() + self.ewait(lambda: message not in self.session.outgoing) @synchronized def close(self): """ Close the Sender. """ + # XXX: should make driver do something here if not self.closed: self.session.senders.remove(self) self.closed = True -class Empty(Exception): +class ReceiveError(SessionError): + pass + +class Empty(ReceiveError): """ Exception raised by L{Receiver.fetch} when there is no message available within the alloted time. @@ -693,43 +636,36 @@ class Receiver(Lockable): self.destination = str(self.index) self.source = source self.filter = filter + self.started = started self.capacity = UNLIMITED + self.granted = Serial(0) + self.drain = False + self.impending = Serial(0) + self.received = Serial(0) + self.returned = Serial(0) + + self.closing = False self.closed = False self.listener = None - self._ssn = None - self._queue = None self._lock = self.session._lock self._condition = self.session._condition - def _link(self): - self._ssn = self.session._ssn - result = self.session._exchange_query(self.source) - if result.not_found: - self._queue = self.source - # XXX: should check 'create' option - self._ssn.queue_declare(queue=self._queue, durable=False) - else: - self._queue = "%s.%s" % (self.session.name, self.destination) - self._ssn.queue_declare(queue=self._queue, durable=False, exclusive=True, auto_delete=True) - if self.filter is None: - f = FILTER_DEFAULTS[result.type] - else: - f = self.filter - f._bind(self._ssn, self.source, self._queue) - self._ssn.message_subscribe(queue=self._queue, destination=self.destination, - sync=True) - self._ssn.message_set_flow_mode(self.destination, self._ssn.flow_mode.credit) - self._ssn.sync() - if self.started: - self._start() + def wakeup(self): + self.session.wakeup() - def _disconnected(self): - self._ssn = None + def catchup(self, exc=ReceiveError): + self.session.catchup() + + def check_error(self, exc=ReceiveError): + self.session.check_error(exc) + + def ewait(self, predicate, timeout=None, exc=ReceiveError): + return self.session.ewait(predicate, timeout, exc) @synchronized def pending(self): - return self.session._count(self._pred) + return self.received - self.returned def _capacity(self): if not self.started: @@ -762,23 +698,36 @@ class Receiver(Lockable): @type timeout: float @param timeout: the time to wait for a message to be available """ - if self.capacity is not UNLIMITED or not self.started: - self._ssn.message_flow(self.destination, self._ssn.credit_unit.byte, - UNLIMITED.value) - self._ssn.message_flow(self.destination, self._ssn.credit_unit.message, 1) + if self._capacity() == 0: + self.granted = self.returned + 1 + self.wakeup() + self.ewait(lambda: self.impending == self.granted) msg = self.session._get(self._pred, timeout=timeout) if msg is None: - self._ssn.message_flush(self.destination) - self._start() - self._ssn.sync() + self.drain = True + self.granted = self.received + self.wakeup() + self.ewait(lambda: self.impending == self.received) + self.drain = False + self._grant() + self.wakeup() msg = self.session._get(self._pred, timeout=0) if msg is None: raise Empty() + elif self._capacity() not in (0, UNLIMITED.value): + self.granted += 1 + self.wakeup() return msg - def _start(self): - self._ssn.message_flow(self.destination, self._ssn.credit_unit.byte, UNLIMITED.value) - self._ssn.message_flow(self.destination, self._ssn.credit_unit.message, self._capacity()) + def _grant(self): + if self.started: + if self.capacity is UNLIMITED: + self.granted = UNLIMITED + else: + self.granted = self.received + self._capacity() + else: + self.granted = self.received + @synchronized def start(self): @@ -786,34 +735,31 @@ class Receiver(Lockable): Start incoming message delivery for this receiver. """ self.started = True - if self._ssn is not None: - self._start() - - def _stop(self): - self._ssn.message_stop(self.destination) + self._grant() + self.wakeup() @synchronized def stop(self): """ Stop incoming message delivery for this receiver. """ - if self._ssn is not None: - self._stop() self.started = False + self._grant() + self.wakeup() + self.ewait(lambda: self.impending == self.received) @synchronized def close(self): """ Close the receiver. """ - if not self.closed: - self.closed = True - self._ssn.message_cancel(self.destination, sync=True) - self._ssn.sync() + self.closing = True + self.wakeup() + try: + self.ewait(lambda: self.closed) + finally: self.session.receivers.remove(self) - - def codec(name): type = PRIMITIVE[name] @@ -889,6 +835,7 @@ class Message: self.to = None self.reply_to = None self.correlation_id = None + self.durable = False self.properties = {} self.content_type = get_type(content) self.content = content @@ -896,5 +843,391 @@ class Message: def __repr__(self): return "Message(%r)" % self.content +class Attachment: + + def __init__(self, target): + self.target = target + +DURABLE_DEFAULT=True + +class Driver(Lockable): + + def __init__(self, connection): + self.connection = connection + self._lock = self.connection._lock + self._condition = self.connection._condition + self._wakeup_cond = Condition() + self._socket = None + self._conn = None + self._connected = False + self._attachments = {} + self._modcount = self.connection._modcount + self.thread = Thread(target=self.run) + self.thread.setDaemon(True) + # XXX: need to figure out how to join on this thread + + def start(self): + self.thread.start() + + def wakeup(self): + self._wakeup_cond.acquire() + try: + self._wakeup_cond.notifyAll() + finally: + self._wakeup_cond.release() + + def start(self): + self.thread.start() + + def run(self): + while True: + self._wakeup_cond.acquire() + try: + if self.connection._modcount <= self._modcount: + self._wakeup_cond.wait(10) + finally: + self._wakeup_cond.release() + self.dispatch(self.connection._modcount) + + @synchronized + def dispatch(self, modcount): + try: + if self._conn is None and self.connection._connected: + self.connect() + elif self._conn is not None and not self.connection._connected: + self.disconnect() + + if self._conn is not None: + for ssn in self.connection.sessions.values(): + self.attach(ssn) + self.process(ssn) + + exi = None + except: + exi = sys.exc_info() + + if exi: + msg = compat.format_exc() + recoverable = ["aborted", "Connection refused", "SessionDetached", "Connection reset by peer", + "Bad file descriptor", "start timed out", "Broken pipe"] + for r in recoverable: + if self.connection.reconnect and r in msg: + print "waiting to retry" + self.reset() + time.sleep(3) + print "retrying..." + return + else: + self.connection.error = (msg,) + + self._modcount = modcount + self.notifyAll() + + def connect(self): + if self._conn is not None: + return + try: + self._socket = connect(self.connection.host, self.connection.port) + except socket.error, e: + raise ConnectError(e) + self._conn = connection.Connection(self._socket) + try: + self._conn.start(timeout=10) + self._connected = True + except connection.VersionError, e: + raise ConnectError(e) + except Timeout: + print "start timed out" + raise ConnectError("start timed out") + + def disconnect(self): + self._conn.close() + self.reset() + + def reset(self): + self._conn = None + self._connected = False + self._attachments.clear() + for ssn in self.connection.sessions.values(): + for m in ssn.acked + ssn.unacked + ssn.incoming: + m._transfer_id = None + for rcv in ssn.receivers: + rcv.impending = rcv.received + + def connected(self): + return self._conn is not None + + def attach(self, ssn): + _ssn = self._attachments.get(ssn) + if _ssn is None: + _ssn = self._conn.session(ssn.name, delegate=delegate(self, ssn)) + _ssn.auto_sync = False + _ssn.invoke_lock = self._lock + _ssn.lock = self._lock + _ssn.condition = self._condition + if ssn.transactional: + # XXX: adding an attribute to qpid.session.Session + _ssn.acked = [] + _ssn.tx_select() + self._attachments[ssn] = _ssn + + for snd in ssn.senders: + self.link_out(snd) + for rcv in ssn.receivers: + self.link_in(rcv) + + if ssn.closing: + _ssn.close() + del self._attachments[ssn] + + def _exchange_query(self, ssn, address): + # XXX: auto sync hack is to avoid deadlock on future + result = ssn.exchange_query(name=address, sync=True) + ssn.sync() + return result.get() + + def link_out(self, snd): + _ssn = self._attachments[snd.session] + _snd = self._attachments.get(snd) + if _snd is None: + _snd = Attachment(snd) + node, _snd._subject = parse_addr(snd.target) + result = self._exchange_query(_ssn, node) + if result.not_found: + # XXX: should check 'create' option + _ssn.queue_declare(queue=node, durable=DURABLE_DEFAULT, sync=True) + _ssn.sync() + _snd._exchange = "" + _snd._routing_key = node + else: + _snd._exchange = node + _snd._routing_key = _snd._subject + self._attachments[snd] = _snd + + if snd.closed: + del self._attachments[snd] + return None + else: + return _snd + + def link_in(self, rcv): + _ssn = self._attachments[rcv.session] + _rcv = self._attachments.get(rcv) + if _rcv is None: + _rcv = Attachment(rcv) + result = self._exchange_query(_ssn, rcv.source) + if result.not_found: + _rcv._queue = rcv.source + # XXX: should check 'create' option + _ssn.queue_declare(queue=_rcv._queue, durable=DURABLE_DEFAULT) + else: + _rcv._queue = "%s.%s" % (rcv.session.name, rcv.destination) + _ssn.queue_declare(queue=_rcv._queue, durable=DURABLE_DEFAULT, exclusive=True, auto_delete=True) + if rcv.filter is None: + f = FILTER_DEFAULTS[result.type] + else: + f = rcv.filter + f._bind(_ssn, rcv.source, _rcv._queue) + _ssn.message_subscribe(queue=_rcv._queue, destination=rcv.destination) + _ssn.message_set_flow_mode(rcv.destination, _ssn.flow_mode.credit, sync=True) + self._attachments[rcv] = _rcv + # XXX: need to kill syncs + _ssn.sync() + + if rcv.closing: + _ssn.message_cancel(rcv.destination, sync=True) + # XXX: need to kill syncs + _ssn.sync() + del self._attachments[rcv] + rcv.closed = True + return None + else: + return _rcv + + def process(self, ssn): + if ssn.closing: return + + _ssn = self._attachments[ssn] + + while ssn.outgoing: + msg = ssn.outgoing[0] + snd = msg._sender + self.send(snd, msg) + ssn.outgoing.pop(0) + + for rcv in ssn.receivers: + self.process_receiver(rcv) + + if ssn.acked: + messages = ssn.acked[:] + ids = RangedSet(*[m._transfer_id for m in messages if m._transfer_id is not None]) + for range in ids: + _ssn.receiver._completed.add_range(range) + ch = _ssn.channel + if ch is None: + raise SessionDetached() + ch.session_completed(_ssn.receiver._completed) + _ssn.message_accept(ids, sync=True) + # XXX: really need to make this async so that we don't give up the lock + _ssn.sync() + + for m in messages: + ssn.acked.remove(m) + if ssn.transactional: + _ssn.acked.append(m) + + if ssn.committing: + _ssn.tx_commit(sync=True) + # XXX: need to kill syncs + _ssn.sync() + del _ssn.acked[:] + ssn.committing = False + ssn.committed = True + ssn.aborting = False + ssn.aborted = False + + if ssn.aborting: + for rcv in ssn.receivers: + _ssn.message_stop(rcv.destination) + _ssn.sync() + + messages = _ssn.acked + ssn.unacked + ssn.incoming + ids = RangedSet(*[m._transfer_id for m in messages]) + for range in ids: + _ssn.receiver._completed.add_range(range) + _ssn.channel.session_completed(_ssn.receiver._completed) + _ssn.message_release(ids) + _ssn.tx_rollback(sync=True) + _ssn.sync() + + del ssn.incoming[:] + del ssn.unacked[:] + del _ssn.acked[:] + + for rcv in ssn.receivers: + rcv.impending = rcv.received + rcv.returned = rcv.received + # XXX: do we need to update granted here as well? + + for rcv in ssn.receivers: + self.process_receiver(rcv) + + ssn.aborting = False + ssn.aborted = True + ssn.committing = False + ssn.committed = False + + def grant(self, rcv): + _ssn = self._attachments[rcv.session] + _rcv = self.link_in(rcv) + + if rcv.granted is UNLIMITED: + if rcv.impending is UNLIMITED: + delta = 0 + else: + delta = UNLIMITED + elif rcv.impending is UNLIMITED: + delta = -1 + else: + delta = max(rcv.granted, rcv.received) - rcv.impending + + if delta is UNLIMITED: + _ssn.message_flow(rcv.destination, _ssn.credit_unit.byte, UNLIMITED.value) + _ssn.message_flow(rcv.destination, _ssn.credit_unit.message, UNLIMITED.value) + rcv.impending = UNLIMITED + elif delta > 0: + _ssn.message_flow(rcv.destination, _ssn.credit_unit.byte, UNLIMITED.value) + _ssn.message_flow(rcv.destination, _ssn.credit_unit.message, delta) + rcv.impending += delta + elif delta < 0: + if rcv.drain: + _ssn.message_flush(rcv.destination, sync=True) + else: + _ssn.message_stop(rcv.destination, sync=True) + # XXX: need to kill syncs + _ssn.sync() + rcv.impending = rcv.received + self.grant(rcv) + + def process_receiver(self, rcv): + if rcv.closed: return + self.grant(rcv) + + def send(self, snd, msg): + _ssn = self._attachments[snd.session] + _snd = self.link_out(snd) + + # XXX: what if subject is specified for a normal queue? + if _snd._routing_key is None: + rk = msg.subject + else: + rk = _snd._routing_key + # XXX: do we need to query to figure out how to create the reply-to interoperably? + if msg.reply_to: + rt = _ssn.reply_to(*parse_addr(msg.reply_to)) + else: + rt = None + dp = _ssn.delivery_properties(routing_key=rk) + mp = _ssn.message_properties(message_id=msg.id, + user_id=msg.user_id, + reply_to=rt, + correlation_id=msg.correlation_id, + content_type=msg.content_type, + application_headers=msg.properties) + if msg.subject is not None: + if mp.application_headers is None: + mp.application_headers = {} + mp.application_headers["subject"] = msg.subject + if msg.to is not None: + if mp.application_headers is None: + mp.application_headers = {} + mp.application_headers["to"] = msg.to + if msg.durable: + dp.delivery_mode = delivery_mode.persistent + enc, dec = get_codec(msg.content_type) + body = enc(msg.content) + _ssn.message_transfer(destination=_snd._exchange, + message=Message010(dp, mp, body), + sync=True) + log.debug("SENT [%s] %s", snd.session, msg) + # XXX: really need to make this async so that we don't give up the lock + _ssn.sync() + # XXX: should we log the ack somehow too? + + @synchronized + def _message_transfer(self, ssn, cmd): + m = Message010(cmd.payload) + m.headers = cmd.headers + m.id = cmd.id + msg = self._decode(m) + rcv = ssn.receivers[int(cmd.destination)] + msg._receiver = rcv + rcv.received += 1 + log.debug("RECV [%s] %s", ssn, msg) + ssn.incoming.append(msg) + self.notifyAll() + return INCOMPLETE + + def _decode(self, message): + dp = message.get("delivery_properties") + mp = message.get("message_properties") + ap = mp.application_headers + enc, dec = get_codec(mp.content_type) + content = dec(message.body) + msg = Message(content) + msg.id = mp.message_id + if ap is not None: + msg.to = ap.get("to") + msg.subject = ap.get("subject") + msg.user_id = mp.user_id + if mp.reply_to is not None: + msg.reply_to = reply_to2addr(mp.reply_to) + msg.correlation_id = mp.correlation_id + msg.durable = dp.delivery_mode == delivery_mode.persistent + msg.properties = mp.application_headers + msg.content_type = mp.content_type + msg._transfer_id = message.id + return msg + __all__ = ["Connection", "Pattern", "Session", "Sender", "Receiver", "Message", "Empty", "timestamp", "uuid4"] diff --git a/qpid/python/qpid/session.py b/qpid/python/qpid/session.py index 4413a22899..2f1bd81bd4 100644 --- a/qpid/python/qpid/session.py +++ b/qpid/python/qpid/session.py @@ -146,7 +146,8 @@ class Session(command_invoker()): if self._closing: raise SessionClosed() - if self.channel == None: + ch = self.channel + if ch == None: raise SessionDetached() if op == MessageTransfer: @@ -162,14 +163,12 @@ class Session(command_invoker()): cmd = op(*args, **kwargs) cmd.sync = self.auto_sync or cmd.sync self.need_sync = not cmd.sync - cmd.channel = self.channel.id + cmd.channel = ch.id if op.RESULT: result = Future(exception=SessionException) self.results[self.sender.next_id] = result - log.debug("SENDING %s", cmd) - self.send(cmd) log.debug("SENT %s", cmd) @@ -245,13 +244,16 @@ class Sender: self._completed = RangedSet() def send(self, cmd): + ch = self.session.channel + if ch is None: + raise SessionDetached() cmd.id = self.next_id self.next_id += 1 if self.session.send_id: self.session.send_id = False - self.session.channel.session_command_point(cmd.id, 0) + ch.session_command_point(cmd.id, 0) self.commands.append(cmd) - self.session.channel.connection.write_op(cmd) + ch.connection.write_op(cmd) def completed(self, commands): idx = 0 diff --git a/qpid/python/qpid/tests/messaging.py b/qpid/python/qpid/tests/messaging.py index 7706ebbabe..6062895519 100644 --- a/qpid/python/qpid/tests/messaging.py +++ b/qpid/python/qpid/tests/messaging.py @@ -84,6 +84,10 @@ class Base(Test): contents = self.drain(rcv) assert len(contents) == 0, "%s is supposed to be empty: %s" % (rcv, contents) + def assertPending(self, rcv, expected): + p = rcv.pending() + assert p == expected, "expected %s, got %s" % (expected, p) + def sleep(self): time.sleep(self.delay()) @@ -107,7 +111,8 @@ class SetupTests(Base): try: self.conn = Connection.open("localhost", 0) assert False, "connect succeeded" - except ConnectError: + except ConnectError, e: + # XXX: should verify that e includes appropriate diagnostic info pass class ConnectionTests(Base): @@ -237,7 +242,8 @@ class SessionTests(Base): # were requeued, and ack this time before closing self.ssn = self.conn.session() rcv = self.ssn.receiver("test-ack-queue") - assert contents == self.drain(rcv) + drained = self.drain(rcv) + assert contents == drained, "expected %s, got %s" % (contents, drained) self.ssn.acknowledge() self.ssn.close() @@ -319,7 +325,8 @@ class SessionTests(Base): txssn.acknowledge() else: txssn.rollback() - assert contents == self.drain(txrcv) + drained = self.drain(txrcv) + assert contents == drained, "expected %s, got %s" % (contents, drained) txssn.acknowledge() txssn.rollback() assert contents == self.drain(txrcv) @@ -401,9 +408,9 @@ class ReceiverTests(Base): elapsed = time.time() - start assert elapsed >= self.delay() - one = self.send("testListen", 1) - two = self.send("testListen", 2) - three = self.send("testListen", 3) + one = self.send("testFetch", 1) + two = self.send("testFetch", 2) + three = self.send("testFetch", 3) msg = self.rcv.fetch(0) assert msg.content == one msg = self.rcv.fetch(self.delay()) @@ -467,34 +474,35 @@ class ReceiverTests(Base): def testCapacity(self): self.rcv.capacity = 5 self.rcv.start() - assert self.rcv.pending() == 0 + self.assertPending(self.rcv, 0) for i in range(15): self.send("testCapacity", i) self.sleep() - assert self.rcv.pending() == 5 + self.assertPending(self.rcv, 5) self.drain(self.rcv, limit = 5) self.sleep() - assert self.rcv.pending() == 5 + self.assertPending(self.rcv, 5) - self.drain(self.rcv) - assert self.rcv.pending() == 0 + drained = self.drain(self.rcv) + assert len(drained) == 10 + self.assertPending(self.rcv, 0) self.ssn.acknowledge() def testCapacityUNLIMITED(self): self.rcv.capacity = UNLIMITED self.rcv.start() - assert self.rcv.pending() == 0 + self.assertPending(self.rcv, 0) for i in range(10): self.send("testCapacityUNLIMITED", i) self.sleep() - assert self.rcv.pending() == 10 + self.assertPending(self.rcv, 10) self.drain(self.rcv) - assert self.rcv.pending() == 0 + self.assertPending(self.rcv, 0) self.ssn.acknowledge() diff --git a/qpid/python/qpid_config.py b/qpid/python/qpid_config.py index 3cf6b69b7e..d740a53dfe 100644 --- a/qpid/python/qpid_config.py +++ b/qpid/python/qpid_config.py @@ -19,7 +19,7 @@ import os -qpid_home = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -amqp_spec = os.path.join(qpid_home, "specs", "amqp.0-10-qpid-errata.xml") -amqp_spec_0_8 = os.path.join(qpid_home, "specs", "amqp.0-8.xml") -amqp_spec_0_9 = os.path.join(qpid_home, "specs", "amqp.0-9.xml") +AMQP_SPEC_DIR=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "specs") +amqp_spec = os.path.join(AMQP_SPEC_DIR, "amqp.0-10-qpid-errata.xml") +amqp_spec_0_8 = os.path.join(AMQP_SPEC_DIR, "amqp.0-8.xml") +amqp_spec_0_9 = os.path.join(AMQP_SPEC_DIR, "amqp.0-9.xml") diff --git a/qpid/python/tests/datatypes.py b/qpid/python/tests/datatypes.py index 1a60bb4107..b00e5e78f8 100644 --- a/qpid/python/tests/datatypes.py +++ b/qpid/python/tests/datatypes.py @@ -54,6 +54,19 @@ class SerialTest(TestCase): d[serial(0)] = "zero" assert d[0] == "zero" + def testAdd(self): + assert serial(2) + 2 == serial(4) + assert serial(2) + 2 == 4 + + def testSub(self): + delta = serial(4) - serial(2) + assert isinstance(delta, int) or isinstance(delta, long) + assert delta == 2 + + delta = serial(4) - 2 + assert isinstance(delta, Serial) + assert delta == serial(2) + class RangedSetTest(TestCase): def check(self, ranges): diff --git a/qpid/python/tests_0-10/management.py b/qpid/python/tests_0-10/management.py index 5cd0caba40..51c2a687cb 100644 --- a/qpid/python/tests_0-10/management.py +++ b/qpid/python/tests_0-10/management.py @@ -29,13 +29,13 @@ class ManagementTest (TestBase010): Tests for the management hooks """ - def disabled_test_broker_connectivity_oldAPI (self): + def test_broker_connectivity_oldAPI (self): """ Call the "echo" method on the broker to verify it is alive and talking. """ session = self.session - mc = managementClient (session.spec) + mc = managementClient () mch = mc.addChannel (session) mc.syncWaitForStable (mch) diff --git a/qpid/specs/management-schema.xml b/qpid/specs/management-schema.xml index bc38350a45..e72ba1cdd7 100644 --- a/qpid/specs/management-schema.xml +++ b/qpid/specs/management-schema.xml @@ -164,11 +164,13 @@ =============================================================== --> <class name="Exchange"> - <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/> - <property name="name" type="sstr" access="RC" index="y"/> - <property name="type" type="sstr" access="RO"/> - <property name="durable" type="bool" access="RC"/> - <property name="arguments" type="map" access="RO" desc="Arguments supplied in exchange.declare"/> + <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/> + <property name="name" type="sstr" access="RC" index="y"/> + <property name="type" type="sstr" access="RO"/> + <property name="durable" type="bool" access="RO"/> + <property name="autoDelete" type="bool" access="RO"/> + <property name="altExchange" type="objId" access="RO" optional="y"/> + <property name="arguments" type="map" access="RO" desc="Arguments supplied in exchange.declare"/> <statistic name="producerCount" type="hilo32" desc="Current producers on exchange"/> <statistic name="bindingCount" type="hilo32" desc="Current bindings"/> |