diff options
Diffstat (limited to 'qpid/cpp/bindings/qmf2')
-rw-r--r-- | qpid/cpp/bindings/qmf2/Makefile.am | 33 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/cpp/Makefile.am | 33 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/cpp/agent.cpp | 250 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/cpp/list_agents.cpp | 73 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/cpp/print_events.cpp | 64 | ||||
-rwxr-xr-x | qpid/cpp/bindings/qmf2/examples/python/agent.py | 196 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/python/find_agents.py | 57 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/ruby/agent_external.rb | 84 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/ruby/agent_internal.rb | 77 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/examples/ruby/find_agents.rb | 63 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/python/Makefile.am | 49 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/python/python.i | 41 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/python/qmf2.py | 933 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/qmf2.i | 66 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/ruby/Makefile.am | 44 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/ruby/qmf2.rb | 855 | ||||
-rw-r--r-- | qpid/cpp/bindings/qmf2/ruby/ruby.i | 35 |
17 files changed, 2953 insertions, 0 deletions
diff --git a/qpid/cpp/bindings/qmf2/Makefile.am b/qpid/cpp/bindings/qmf2/Makefile.am new file mode 100644 index 0000000000..52b1bbd457 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/Makefile.am @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if HAVE_SWIG + +EXTRA_DIST = qmf2.i +SUBDIRS = examples/cpp + +if HAVE_RUBY_DEVEL +SUBDIRS += ruby +endif + +if HAVE_PYTHON_DEVEL +SUBDIRS += python +endif + +endif diff --git a/qpid/cpp/bindings/qmf2/examples/cpp/Makefile.am b/qpid/cpp/bindings/qmf2/examples/cpp/Makefile.am new file mode 100644 index 0000000000..84207d43c4 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/cpp/Makefile.am @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +INCLUDE = -I$(top_srcdir)/include + +AM_CPPFLAGS = $(INCLUDE) + +noinst_PROGRAMS=agent list_agents print_events + +agent_SOURCES=agent.cpp +agent_LDADD=$(top_builddir)/src/libqmf2.la + +list_agents_SOURCES=list_agents.cpp +list_agents_LDADD=$(top_builddir)/src/libqmf2.la + +print_events_SOURCES=print_events.cpp +print_events_LDADD=$(top_builddir)/src/libqmf2.la diff --git a/qpid/cpp/bindings/qmf2/examples/cpp/agent.cpp b/qpid/cpp/bindings/qmf2/examples/cpp/agent.cpp new file mode 100644 index 0000000000..00554539eb --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/cpp/agent.cpp @@ -0,0 +1,250 @@ +/* + * 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/Duration.h> +#include <qmf/AgentSession.h> +#include <qmf/AgentEvent.h> +#include <qmf/Schema.h> +#include <qmf/SchemaProperty.h> +#include <qmf/SchemaMethod.h> +#include <qmf/Data.h> +#include <qmf/DataAddr.h> +#include <qpid/types/Variant.h> +#include <string> +#include <iostream> + +using namespace std; +using namespace qmf; +using qpid::types::Variant; +using qpid::messaging::Duration; + +class ExampleAgent { +public: + ExampleAgent(const string& url); + ~ExampleAgent(); + + void setupSchema(); + void populateData(); + void run(); +private: + qpid::messaging::Connection connection; + AgentSession session; + Schema sch_exception; + Schema sch_control; + Schema sch_child; + Schema sch_event; + Data control; + DataAddr controlAddr; + + bool method(AgentEvent& event); +}; + + +ExampleAgent::ExampleAgent(const string& url) +{ + // + // Create and open a messaging connection to a broker. + // + connection = qpid::messaging::Connection(url, "{reconnect:True}"); + connection.open(); + + // + // Create, configure, and open a QMFv2 agent session using the connection. + // + session = AgentSession(connection, "{interval:30}"); + session.setVendor("profitron.com"); + session.setProduct("gizmo"); + session.setAttribute("attr1", 2000); + session.open(); +} + +ExampleAgent::~ExampleAgent() +{ + // + // Clean up the QMF session and the AMQP connection. + // + session.close(); + connection.close(); +} + +void ExampleAgent::setupSchema() +{ + // + // Create and register schema for this agent. + // + string package("com.profitron.gizmo"); + + // + // Declare a schema for a structured exception that can be used in failed + // method invocations. + // + sch_exception = Schema(SCHEMA_TYPE_DATA, package, "exception"); + sch_exception.addProperty(SchemaProperty("whatHappened", SCHEMA_DATA_STRING)); + sch_exception.addProperty(SchemaProperty("howBad", SCHEMA_DATA_INT)); + sch_exception.addProperty(SchemaProperty("details", SCHEMA_DATA_MAP)); + + // + // Declare a control object to test methods against. + // + sch_control = Schema(SCHEMA_TYPE_DATA, package, "control"); + sch_control.addProperty(SchemaProperty("state", SCHEMA_DATA_STRING)); + sch_control.addProperty(SchemaProperty("methodCount", SCHEMA_DATA_INT)); + + SchemaMethod stopMethod("stop", "{desc:'Stop Agent'}"); + stopMethod.addArgument(SchemaProperty("message", SCHEMA_DATA_STRING)); + sch_control.addMethod(stopMethod); + + SchemaMethod echoMethod("echo", "{desc:'Echo Arguments'}"); + echoMethod.addArgument(SchemaProperty("sequence", SCHEMA_DATA_INT, "{dir:INOUT}")); + echoMethod.addArgument(SchemaProperty("map", SCHEMA_DATA_MAP, "{dir:INOUT}")); + sch_control.addMethod(echoMethod); + + SchemaMethod eventMethod("event", "{desc:'Raise an Event'}"); + eventMethod.addArgument(SchemaProperty("text", SCHEMA_DATA_STRING, "{dir:IN}")); + eventMethod.addArgument(SchemaProperty("severity", SCHEMA_DATA_INT, "{dir:IN}")); + sch_control.addMethod(eventMethod); + + SchemaMethod failMethod("fail", "{desc:'Expected to Fail'}"); + failMethod.addArgument(SchemaProperty("useString", SCHEMA_DATA_BOOL, "{dir:IN}")); + failMethod.addArgument(SchemaProperty("stringVal", SCHEMA_DATA_STRING, "{dir:IN}")); + failMethod.addArgument(SchemaProperty("details", SCHEMA_DATA_MAP, "{dir:IN}")); + sch_control.addMethod(failMethod); + + SchemaMethod createMethod("create_child", "{desc:'Create Child Object'}"); + createMethod.addArgument(SchemaProperty("name", SCHEMA_DATA_STRING, "{dir:IN}")); + createMethod.addArgument(SchemaProperty("childAddr", SCHEMA_DATA_MAP, "{dir:OUT}")); + sch_control.addMethod(createMethod); + + // + // Declare the child class + // + sch_child = Schema(SCHEMA_TYPE_DATA, package, "child"); + sch_child.addProperty(SchemaProperty("name", SCHEMA_DATA_STRING)); + + // + // Declare the event class + // + sch_event = Schema(SCHEMA_TYPE_EVENT, package, "event"); + sch_event.addProperty(SchemaProperty("text", SCHEMA_DATA_STRING)); + + // + // Register our schemata with the agent session. + // + session.registerSchema(sch_exception); + session.registerSchema(sch_control); + session.registerSchema(sch_child); + session.registerSchema(sch_event); +} + +void ExampleAgent::populateData() +{ + // + // Create a control object and give it to the agent session to manage. + // + control = Data(sch_control); + control.setProperty("state", "OPERATIONAL"); + control.setProperty("methodCount", 0); + controlAddr = session.addData(control, "singleton"); +} + +void ExampleAgent::run() +{ + AgentEvent event; + bool running(true); + + while (running) { + bool valid(session.nextEvent(event, Duration::SECOND)); + if (valid && running) { + switch (event.getType()) { + case AGENT_METHOD: + running = method(event); + break; + } + } + } +} + +bool ExampleAgent::method(AgentEvent& event) +{ + const string& name(event.getMethodName()); + control.setProperty("methodCount", control.getProperty("methodCount").asUint32() + 1); + + try { + if (controlAddr == event.getDataAddr()) { + if (name == "stop") { + cout << "Stopping: message=" << event.getArguments()["message"] << endl; + session.methodSuccess(event); + return false; + } + + if (name == "echo") { + event.addReturnArgument("sequence", event.getArguments()["sequence"]); + event.addReturnArgument("map", event.getArguments()["map"]); + session.methodSuccess(event); + return true; + } + + if (name == "event") { + Data ev(sch_event); + ev.setProperty("text", event.getArguments()["text"]); + session.raiseEvent(ev, event.getArguments()["severity"]); + session.methodSuccess(event); + return true; + } + + if (name == "fail") { + if (event.getArguments()["useString"]) + session.raiseException(event, event.getArguments()["stringVal"]); + else { + Data ex(sch_exception); + ex.setProperty("whatHappened", "It Failed"); + ex.setProperty("howBad", 75); + ex.setProperty("details", event.getArguments()["details"]); + session.raiseException(event, ex); + } + } + + if (name == "create_child") { + const string& name(event.getArguments()["name"]); + Data child(sch_child); + child.setProperty("name", name); + DataAddr addr(session.addData(child, name)); + event.addReturnArgument("childAddr", addr.asMap()); + session.methodSuccess(event); + } + } + } catch (const exception& e) { + // + // Pass the exception on to the caller. + // + session.raiseException(event, e.what()); + } + + return true; +} + +int main() +{ + ExampleAgent agent("localhost"); + agent.setupSchema(); + agent.populateData(); + agent.run(); +} + diff --git a/qpid/cpp/bindings/qmf2/examples/cpp/list_agents.cpp b/qpid/cpp/bindings/qmf2/examples/cpp/list_agents.cpp new file mode 100644 index 0000000000..327da9661f --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/cpp/list_agents.cpp @@ -0,0 +1,73 @@ +/* + * 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/Duration.h> +#include <qmf/ConsoleSession.h> +#include <qmf/ConsoleEvent.h> +#include <qmf/Agent.h> +#include <qpid/types/Variant.h> +#include <string> +#include <iostream> + +using namespace std; +using namespace qmf; +using qpid::types::Variant; +using qpid::messaging::Duration; + +int main(int argc, char** argv) +{ + string url("localhost"); + string connectionOptions; + string sessionOptions; + + if (argc > 1) + url = argv[1]; + if (argc > 2) + connectionOptions = argv[2]; + if (argc > 3) + sessionOptions = argv[3]; + + qpid::messaging::Connection connection(url, connectionOptions); + connection.open(); + + ConsoleSession session(connection, sessionOptions); + session.open(); + + session.setAgentFilter(""); + + while (true) { + ConsoleEvent event; + if (session.nextEvent(event)) { + if (event.getType() == CONSOLE_AGENT_ADD) { + string extra; + if (event.getAgent().getName() == session.getConnectedBrokerAgent().getName()) + extra = " [Connected Broker]"; + cout << "Agent Added: " << event.getAgent().getName() << extra << endl; + } + if (event.getType() == CONSOLE_AGENT_DEL) { + if (event.getAgentDelReason() == AGENT_DEL_AGED) + cout << "Agent Aged: " << event.getAgent().getName() << endl; + else + cout << "Agent Filtered: " << event.getAgent().getName() << endl; + } + } + } +} + diff --git a/qpid/cpp/bindings/qmf2/examples/cpp/print_events.cpp b/qpid/cpp/bindings/qmf2/examples/cpp/print_events.cpp new file mode 100644 index 0000000000..9883a19962 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/cpp/print_events.cpp @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Duration.h> +#include <qmf/ConsoleSession.h> +#include <qmf/ConsoleEvent.h> +#include <qmf/Data.h> +#include <qpid/types/Variant.h> +#include <string> +#include <iostream> + +using namespace std; +using namespace qmf; +using qpid::types::Variant; +using qpid::messaging::Duration; + +int main(int argc, char** argv) +{ + string url("localhost"); + string connectionOptions; + string sessionOptions; + + if (argc > 1) + url = argv[1]; + if (argc > 2) + connectionOptions = argv[2]; + if (argc > 3) + sessionOptions = argv[3]; + + qpid::messaging::Connection connection(url, connectionOptions); + connection.open(); + + ConsoleSession session(connection, sessionOptions); + session.open(); + + while (true) { + ConsoleEvent event; + if (session.nextEvent(event)) { + if (event.getType() == CONSOLE_EVENT) { + const Data& data(event.getData(0)); + cout << "Event: timestamp=" << event.getTimestamp() << " severity=" << + event.getSeverity() << " content=" << data.getProperties() << endl; + } + } + } +} + diff --git a/qpid/cpp/bindings/qmf2/examples/python/agent.py b/qpid/cpp/bindings/qmf2/examples/python/agent.py new file mode 100755 index 0000000000..b24890f531 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/python/agent.py @@ -0,0 +1,196 @@ +#!/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 cqpid +from qmf2 import * + + +class ExampleAgent(AgentHandler): + """ + This example agent is implemented as a single class that inherits AgentHandler. + It does not use a separate thread since once set up, it is driven strictly by + incoming method calls. + """ + + def __init__(self, url): + ## + ## Create and open a messaging connection to a broker. + ## + self.connection = cqpid.Connection(url, "{reconnect:True}") + self.session = None + self.connection.open() + + ## + ## Create, configure, and open a QMFv2 agent session using the connection. + ## + self.session = AgentSession(self.connection, "{interval:30}") + self.session.setVendor('profitron.com') + self.session.setProduct('blastinator') + self.session.setAttribute('attr1', 1000) + self.session.open() + + ## + ## Initialize the parent class. + ## + AgentHandler.__init__(self, self.session) + + + def shutdown(self): + """ + Clean up the session and connection. + """ + if self.session: + self.session.close() + self.connection.close() + + + def method(self, handle, methodName, args, subtypes, addr, userId): + """ + Handle incoming method calls. + """ + if addr == self.controlAddr: + self.control.methodCount += 1 + + try: + if methodName == "stop": + self.session.methodSuccess(handle) + self.cancel() + + elif methodName == "echo": + handle.addReturnArgument("sequence", args["sequence"]) + handle.addReturnArgument("map", args["map"]) + self.session.methodSuccess(handle) + + elif methodName == "event": + ev = Data(self.sch_event) + ev.text = args['text'] + self.session.raiseEvent(ev, args['severity']) + self.session.methodSuccess(handle) + + elif methodName == "fail": + if args['useString']: + self.session.raiseException(handle, args['stringVal']) + else: + ex = Data(self.sch_exception) + ex.whatHappened = "It Failed" + ex.howBad = 75 + ex.details = args['details'] + self.session.raiseException(handle, ex) + + elif methodName == "create_child": + name = args['name'] + child = Data(self.sch_child) + child.name = name + addr = self.session.addData(child, name) + handle.addReturnArgument("childAddr", addr.asMap()) + self.session.methodSuccess(handle) + except BaseException, e: + self.session.raiseException(handle, "%r" % e) + + + def setupSchema(self): + """ + Create and register the schema for this agent. + """ + package = "com.profitron.bntor" + + ## + ## Declare a schema for a structured exception that can be used in failed + ## method invocations. + ## + self.sch_exception = Schema(SCHEMA_TYPE_DATA, package, "exception") + self.sch_exception.addProperty(SchemaProperty("whatHappened", SCHEMA_DATA_STRING)) + self.sch_exception.addProperty(SchemaProperty("howBad", SCHEMA_DATA_INT)) + self.sch_exception.addProperty(SchemaProperty("details", SCHEMA_DATA_MAP)) + + ## + ## Declare a control object to test methods against. + ## + self.sch_control = Schema(SCHEMA_TYPE_DATA, package, "control") + self.sch_control.addProperty(SchemaProperty("state", SCHEMA_DATA_STRING)) + self.sch_control.addProperty(SchemaProperty("methodCount", SCHEMA_DATA_INT)) + + stopMethod = SchemaMethod("stop", desc="Stop Agent") + stopMethod.addArgument(SchemaProperty("message", SCHEMA_DATA_STRING, direction=DIR_IN)) + self.sch_control.addMethod(stopMethod) + + echoMethod = SchemaMethod("echo", desc="Echo Arguments") + echoMethod.addArgument(SchemaProperty("sequence", SCHEMA_DATA_INT, direction=DIR_IN_OUT)) + echoMethod.addArgument(SchemaProperty("map", SCHEMA_DATA_MAP, direction=DIR_IN_OUT)) + self.sch_control.addMethod(echoMethod) + + eventMethod = SchemaMethod("event", desc="Raise an Event") + eventMethod.addArgument(SchemaProperty("text", SCHEMA_DATA_STRING, direction=DIR_IN)) + eventMethod.addArgument(SchemaProperty("severity", SCHEMA_DATA_INT, direction=DIR_IN)) + self.sch_control.addMethod(eventMethod) + + failMethod = SchemaMethod("fail", desc="Expected to Fail") + failMethod.addArgument(SchemaProperty("useString", SCHEMA_DATA_BOOL, direction=DIR_IN)) + failMethod.addArgument(SchemaProperty("stringVal", SCHEMA_DATA_STRING, direction=DIR_IN)) + failMethod.addArgument(SchemaProperty("details", SCHEMA_DATA_MAP, direction=DIR_IN)) + self.sch_control.addMethod(failMethod) + + createMethod = SchemaMethod("create_child", desc="Create Child Object") + createMethod.addArgument(SchemaProperty("name", SCHEMA_DATA_STRING, direction=DIR_IN)) + createMethod.addArgument(SchemaProperty("childAddr", SCHEMA_DATA_MAP, direction=DIR_OUT)) + self.sch_control.addMethod(createMethod) + + ## + ## Declare a child object + ## + self.sch_child = Schema(SCHEMA_TYPE_DATA, package, "child") + self.sch_child.addProperty(SchemaProperty("name", SCHEMA_DATA_STRING)) + + ## + ## Declare the event class + ## + self.sch_event = Schema(SCHEMA_TYPE_EVENT, package, "event") + self.sch_event.addProperty(SchemaProperty("text", SCHEMA_DATA_STRING)) + + ## + ## Register our schemata with the agent session. + ## + self.session.registerSchema(self.sch_exception) + self.session.registerSchema(self.sch_control) + self.session.registerSchema(self.sch_child) + self.session.registerSchema(self.sch_event) + + + def populateData(self): + """ + Create a control object and give it to the agent session to manage. + """ + self.control = Data(self.sch_control) + self.control.state = "OPERATIONAL" + self.control.methodCount = 0 + self.controlAddr = self.session.addData(self.control, "singleton") + + +try: + agent = ExampleAgent("localhost") + agent.setupSchema() + agent.populateData() + agent.run() # Use agent.start() to launch the agent in a separate thread + agent.shutdown() +except Exception, e: + print "Exception Caught:", e + + diff --git a/qpid/cpp/bindings/qmf2/examples/python/find_agents.py b/qpid/cpp/bindings/qmf2/examples/python/find_agents.py new file mode 100644 index 0000000000..5fd71b3f1c --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/python/find_agents.py @@ -0,0 +1,57 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import cqpid +import qmf2 + +class FindAgents(qmf2.ConsoleHandler): + + def __init__(self, session): + qmf2.ConsoleHandler.__init__(self, session) + + def agentAdded(self, agent): + print "Agent Added: %r" % agent + + def agentDeleted(self, agent, reason): + print "Agent Deleted: %r reason: %s" % (agent, reason) + + def agentRestarted(self, agent): + print "Agent Restarted: %r" % agent + + def agentSchemaUpdated(self, agent): + print "Agent Schema Updated: %r" % agent + + def eventRaised(self, agent, data, timestamp, severity): + print "Event: data=%r time=%d sev=%d" % (data.getProperties(), timestamp, severity) + + + +url = "localhost" +options = "" + +connection = cqpid.Connection(url, options) +connection.open() + +session = qmf2.ConsoleSession(connection) +session.open() +session.setAgentFilter("[]") + +main = FindAgents(session) +main.run() + diff --git a/qpid/cpp/bindings/qmf2/examples/ruby/agent_external.rb b/qpid/cpp/bindings/qmf2/examples/ruby/agent_external.rb new file mode 100644 index 0000000000..75171931ed --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/ruby/agent_external.rb @@ -0,0 +1,84 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'cqpid' +require 'qmf2' + +class MyAgent < Qmf2::AgentHandler + + def initialize(session, data) + super(session) + @data = data + end + + def authorize_query(query, user_id) + puts "Authorizing #{user_id}" + return true + end + + def get_query(context, query, user_id) + puts "Get Query" + context.response(@data) + context.complete + end + + def method_call(context, method_name, data_addr, args, user_id) + puts "Method: #{method_name}" + context._success + end + +end + + +class Program + + def initialize(url) + @url = url + @sess_options = "{allow-queries:False, external:True}" + end + + def setup_schema(agent) + @cls_control = Qmf2::Schema.new(Qmf2::SCHEMA_TYPE_DATA, "org.package", "control") + @cls_control.add_property(Qmf2::SchemaProperty.new("state", Qmf2::SCHEMA_DATA_STRING)) + agent.register_schema(@cls_control) + end + + def run + connection = Cqpid::Connection.new(@url) + connection.open + + session = Qmf2::AgentSession.new(connection, @sess_options) + session.set_vendor("package.org") + session.set_product("external_agent") + setup_schema(session) + session.open + + @control = Qmf2::Data.new(@cls_control) + @control.state = "OPERATIONAL-EXTERNAL" + @control.set_addr(Qmf2::DataAddr.new("singleton")) + + main = MyAgent.new(session, @control) + main.run + end +end + +prog = Program.new("localhost") +prog.run + + diff --git a/qpid/cpp/bindings/qmf2/examples/ruby/agent_internal.rb b/qpid/cpp/bindings/qmf2/examples/ruby/agent_internal.rb new file mode 100644 index 0000000000..fc49a885f7 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/ruby/agent_internal.rb @@ -0,0 +1,77 @@ +# +# 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. +# + +require 'cqpid' +require 'qmf2' + +class MyAgent < Qmf2::AgentHandler + + def initialize(session) + super(session) + end + + def authorize_query(query, user_id) + puts "Authorizing #{user_id}" + return true + end + + def method_call(context, method_name, data_addr, args, user_id) + puts "Method: #{method_name}" + context._success + end + +end + + +class Program + + def initialize(url) + @url = url + @sess_options = "{allow-queries:False}" + end + + def setup_schema(agent) + @cls_control = Qmf2::Schema.new(Qmf2::SCHEMA_TYPE_DATA, "org.package", "control") + @cls_control.add_property(Qmf2::SchemaProperty.new("state", Qmf2::SCHEMA_DATA_STRING)) + agent.register_schema(@cls_control) + end + + def run + connection = Cqpid::Connection.new(@url) + connection.open + + session = Qmf2::AgentSession.new(connection, @sess_options) + session.set_vendor("package.org") + session.set_product("internal_agent") + setup_schema(session) + session.open + + control = Qmf2::Data.new(@cls_control) + control.state = "OPERATIONAL" + session.add_data(control) + + main = MyAgent.new(session) + main.run + end +end + +prog = Program.new("localhost") +prog.run + + diff --git a/qpid/cpp/bindings/qmf2/examples/ruby/find_agents.rb b/qpid/cpp/bindings/qmf2/examples/ruby/find_agents.rb new file mode 100644 index 0000000000..41de7e5abe --- /dev/null +++ b/qpid/cpp/bindings/qmf2/examples/ruby/find_agents.rb @@ -0,0 +1,63 @@ +# +# 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. +# + +require 'cqpid' +require 'qmf2' + +class FindAgents < Qmf2::ConsoleHandler + + def initialize(session) + super(session) + end + + def agent_added(agent) + puts "Agent Added: #{agent.name}" + end + + def agent_deleted(agent, reason) + puts "Agent Deleted: #{agent.to_s} reason: #{reason}" + end + + def agent_restarted(agent) + puts "Agent Restarted: #{agent.to_s} epoch: #{agent.epoch}" + end + + def agent_schema_updated(agent) + puts "Agent with new Schemata: #{agent.to_s}" + end + + def event_raised(agent, data, timestamp, severity) + puts "Event Raised time=#{timestamp} sev=#{severity} data=#{data.properties}" + end +end + + +url = "localhost" +options = "" + +connection = Cqpid::Connection.new(url, options) +connection.open + +session = Qmf2::ConsoleSession.new(connection) +session.open +session.set_agent_filter("[]") + +main = FindAgents.new(session) +main.run + diff --git a/qpid/cpp/bindings/qmf2/python/Makefile.am b/qpid/cpp/bindings/qmf2/python/Makefile.am new file mode 100644 index 0000000000..3dc04e832f --- /dev/null +++ b/qpid/cpp/bindings/qmf2/python/Makefile.am @@ -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. +# + +if HAVE_PYTHON_DEVEL + +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src -I$(top_builddir)/src $(QMF_INCLUDES) + +generated_file_list = \ + cqmf2.cpp \ + cqmf2.py + +EXTRA_DIST = python.i +BUILT_SOURCES = $(generated_file_list) +SWIG_FLAGS = -w362,401 + +$(generated_file_list): $(srcdir)/python.i $(srcdir)/../qmf2.i $(srcdir)/../../swig_python_typemaps.i + $(SWIG) -c++ -python $(SWIG_FLAGS) $(INCLUDES) $(QPID_CXXFLAGS) -I/usr/include -o cqmf2.cpp $(srcdir)/python.i + +pylibdir = $(PYTHON_LIB) + +lib_LTLIBRARIES = _cqmf2.la +cqpiddir = $(pyexecdir) +cqpid_PYTHON = qmf2.py cqmf2.py + +_cqmf2_la_LDFLAGS = -avoid-version -module -shared +_cqmf2_la_LIBADD = $(PYTHON_LIBS) -L$(top_builddir)/src/.libs $(top_builddir)/src/libqmf2.la +_cqmf2_la_CXXFLAGS = $(INCLUDES) -I$(srcdir)/qmf -I$(PYTHON_INC) -fno-strict-aliasing +nodist__cqmf2_la_SOURCES = cqmf2.cpp + +CLEANFILES = $(generated_file_list) + +endif # HAVE_PYTHON_DEVEL + diff --git a/qpid/cpp/bindings/qmf2/python/python.i b/qpid/cpp/bindings/qmf2/python/python.i new file mode 100644 index 0000000000..02dd1632b0 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/python/python.i @@ -0,0 +1,41 @@ +/* + * 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. + */ + +%module cqmf2 +%include "std_string.i" +%include "../../swig_python_typemaps.i" + +/* Define the general-purpose exception handling */ +%exception { + std::string error; + Py_BEGIN_ALLOW_THREADS; + try { + $action + } catch (qpid::types::Exception& ex) { + error = ex.what(); + } + Py_END_ALLOW_THREADS; + if (!error.empty()) { + PyErr_SetString(PyExc_RuntimeError, error.c_str()); + return NULL; + } +} + +%include "../qmf2.i" + diff --git a/qpid/cpp/bindings/qmf2/python/qmf2.py b/qpid/cpp/bindings/qmf2/python/qmf2.py new file mode 100644 index 0000000000..9f2d8556f4 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/python/qmf2.py @@ -0,0 +1,933 @@ +# +# 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 cqmf2 +import cqpid +from threading import Thread +import time + +#=================================================================================================== +# CONSTANTS +#=================================================================================================== +SCHEMA_TYPE_DATA = cqmf2.SCHEMA_TYPE_DATA +SCHEMA_TYPE_EVENT = cqmf2.SCHEMA_TYPE_EVENT + +SCHEMA_DATA_VOID = cqmf2.SCHEMA_DATA_VOID +SCHEMA_DATA_BOOL = cqmf2.SCHEMA_DATA_BOOL +SCHEMA_DATA_INT = cqmf2.SCHEMA_DATA_INT +SCHEMA_DATA_FLOAT = cqmf2.SCHEMA_DATA_FLOAT +SCHEMA_DATA_STRING = cqmf2.SCHEMA_DATA_STRING +SCHEMA_DATA_MAP = cqmf2.SCHEMA_DATA_MAP +SCHEMA_DATA_LIST = cqmf2.SCHEMA_DATA_LIST +SCHEMA_DATA_UUID = cqmf2.SCHEMA_DATA_UUID + +ACCESS_READ_CREATE = cqmf2.ACCESS_READ_CREATE +ACCESS_READ_WRITE = cqmf2.ACCESS_READ_WRITE +ACCESS_READ_ONLY = cqmf2.ACCESS_READ_ONLY + +DIR_IN = cqmf2.DIR_IN +DIR_OUT = cqmf2.DIR_OUT +DIR_IN_OUT = cqmf2.DIR_IN_OUT + +SEV_EMERG = cqmf2.SEV_EMERG +SEV_ALERT = cqmf2.SEV_ALERT +SEV_CRIT = cqmf2.SEV_CRIT +SEV_ERROR = cqmf2.SEV_ERROR +SEV_WARN = cqmf2.SEV_WARN +SEV_NOTICE = cqmf2.SEV_NOTICE +SEV_INFORM = cqmf2.SEV_INFORM +SEV_DEBUG = cqmf2.SEV_DEBUG + +QUERY_OBJECT = cqmf2.QUERY_OBJECT +QUERY_OBJECT_ID = cqmf2.QUERY_OBJECT_ID +QUERY_SCHEMA = cqmf2.QUERY_SCHEMA +QUERY_SCHEMA_ID = cqmf2.QUERY_SCHEMA_ID + + +#=================================================================================================== +# EXCEPTIONS +#=================================================================================================== +class QmfAgentException(Exception): + """ + This exception class represents an exception that was raised by a remote agent and propagated + to a console via QMFv2. + """ + def __init__(self, data): + self.value = data + + def __str__(self): + return "From Remote Agent: %r" % self.value.getProperties() + + +#=================================================================================================== +# AGENT HANDLER +#=================================================================================================== +class AgentHandler(Thread): + """ + Agent applications can create a subclass of AgentHandler to handle asynchronous events (like + incoming method calls) that occur on the agent session. AgentHandler contains a thread on which + the handler callbacks are invoked. + + There are two ways to operate the handler: Cause it to start its own thread by calling + start() and later stop it by calling cancel(); and directly calling run() to operate it on the + main thread. + + Example Usage: + + class MyAgentHandler(qmf2.AgentHandler): + def __init__(self, agentSession): + qmf2.AgentHandler.__init__(self, agentSession) + def method(self, handle, methodName, args, subtypes, addr, userId): + ...method handling code goes here... + For success, add output arguments: + handle.addReturnArgument("argname", argvalue) + ... + self.agent.methodSuccess(handle) + For failure, raise an exception: + self.agent.raiseException(handle, "error text") + Or, if you have created a schema for a structured exception: + ex = qmf2.Data(exceptionSchema) + ex.whatHappened = "it failed" + ex.howBad = 84 + ex.detailMap = {} + ... + self.agent.raiseException(handle, ex) + """ + + def __init__(self, agentSession): + Thread.__init__(self) + self.__agent = agentSession + self.__running = True + + def cancel(self): + """ + Stop the handler thread. + """ + self.__running = None + + def run(self): + event = cqmf2.AgentEvent() + while self.__running: + valid = self.__agent._impl.nextEvent(event, cqpid.Duration.SECOND) + if valid and self.__running: + if event.getType() == cqmf2.AGENT_METHOD: + self.method(event, event.getMethodName(), event.getArguments(), event.getArgumentSubtypes(), + DataAddr(event.getDataAddr()), event.getUserId()) + + def method(self, handle, methodName, args, subtypes, addr, userId): + """ + Override this method to create your own method handler. + """ + pass + + +#=================================================================================================== +# CONSOLE HANDLER +#=================================================================================================== +class ConsoleHandler(Thread): + + def __init__(self, consoleSession): + Thread.__init__(self) + self.__session = consoleSession + self.__running = True + + def cancel(self): + """ + Stop the handler thread. + """ + self.__running = None + + def run(self): + event = cqmf2.ConsoleEvent() + while self.__running: + valid = self.__session._impl.nextEvent(event, cqpid.Duration.SECOND) + if valid and self.__running: + if event.getType() == cqmf2.CONSOLE_AGENT_ADD: + self.agentAdded(Agent(event.getAgent())) + + elif event.getType() == cqmf2.CONSOLE_AGENT_DEL: + reason = 'filter' + if event.getAgentDelReason() == cqmf2.AGENT_DEL_AGED: + reason = 'aged' + self.agentDeleted(Agent(event.getAgent()), reason) + + elif event.getType() == cqmf2.CONSOLE_AGENT_RESTART: + self.agentRestarted(Agent(event.getAgent())) + + elif event.getType() == cqmf2.CONSOLE_AGENT_SCHEMA_UPDATE: + self.agentSchemaUpdated(Agent(event.getAgent())) + + elif event.getType() == cqmf2.CONSOLE_EVENT: + self.eventRaised(Agent(event.getAgent()), Data(event.getData(0)), event.getTimestamp(), event.getSeverity()) + + ## + ## The following methods are intended to be overridden in a sub-class. They are + ## handlers for events that occur on QMF consoles. + ## + + # + # A new agent, whose attributes match the console's agent filter, has been discovered. + # + def agentAdded(self, agent): + pass + + # + # A known agent has been removed from the agent list. There are two possible reasons + # for agent deletion: + # + # 1) 'aged' - The agent hasn't been heard from for the maximum age interval and is + # presumed dead. + # 2) 'filter' - The agent no longer matches the console's agent-filter and has been + # effectively removed from the agent list. Such occurrences are likely + # to be seen immediately after setting the filter to a new value. + # + def agentDeleted(self, agent, reason): + pass + + # + # An agent-restart was detected. This occurs when the epoch number advertised by the + # agent changes. It indicates that the agent in question was shut-down/crashed and + # restarted. + # + def agentRestarted(self, agent): + pass + + # + # The agent has registered new schema information which can now be queried, if desired. + # + def agentSchemaUpdated(self, agent): + pass + + # + # An agent raised an event. The 'data' argument is a Data object that contains the + # content of the event. + # + def eventRaised(self, agent, data, timestamp, severity): + pass + + +#=================================================================================================== +# CONSOLE SESSION +#=================================================================================================== +class ConsoleSession(object): + """ + """ + + def __init__(self, connection, options=""): + """ + ## The options string is of the form "{key:value,key:value}". The following keys are supported: + ## + ## domain:NAME - QMF Domain to join [default: "default"] + ## max-agent-age:N - Maximum time, in minutes, that we will tolerate not hearing from + ## an agent before deleting it [default: 5] + ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default] + ## If False: Listen only on the routable direct address + ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network + ## - If False: Operate more flexibly with regard to use of messaging facilities [default] + ## + """ + self._impl = cqmf2.ConsoleSession(connection, options) + + def setDomain(self, domain): + """ + """ + self._impl.setDomain(domain) + + def setAgentFilter(self, filt): + """ + """ + self._impl.setAgentFilter(filt) + + def open(self): + """ + """ + self._impl.open() + + def close(self): + """ + """ + self._impl.close() + + def getAgents(self): + """ + """ + result = [] + count = self._impl.getAgentCount() + for i in range(count): + result.append(Agent(self._impl.getAgent(i))) + return result + + def getConnectedBrokerAgent(self): + """ + """ + return Agent(self._impl.getConnectedBrokerAgent()) + + ## TODO: Async methods + +#=================================================================================================== +# AGENT SESSION +#=================================================================================================== +class AgentSession(object): + """ + """ + + def __init__(self, connection, options=""): + """ + ## The options string is of the form "{key:value,key:value}". The following keys are supported: + ## + ## interval:N - Heartbeat interval in seconds [default: 60] + ## external:{True,False} - Use external data storage (queries and subscriptions are pass-through) [default: False] + ## allow-queries:{True,False} - If True: automatically allow all queries [default] + ## If False: generate an AUTH_QUERY event to allow per-query authorization + ## allow-methods:{True,False} - If True: automatically allow all methods [default] + ## If False: generate an AUTH_METHOD event to allow per-method authorization + ## max-subscriptions:N - Maximum number of concurrent subscription queries permitted [default: 64] + ## min-sub-interval:N - Minimum publish interval (in milliseconds) permitted for a subscription [default: 3000] + ## sub-lifetime:N - Lifetime (in seconds with no keepalive) for a subscription [default: 300] + ## public-events:{True,False} - If True: QMF events are sent to the topic exchange [default] + ## If False: QMF events are only sent to authorized subscribers + ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default] + ## If False: Listen only on the routable direct address + ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network + ## - If False: Operate more flexibly with regard to use of messaging facilities [default] + ## + """ + self._impl = cqmf2.AgentSession(connection, options) + + def setDomain(self, domain): + """ + """ + self._impl.setDomain(domain) + + def setVendor(self, val): + """ + """ + self._impl.setVendor(val) + + def setProduct(self, val): + """ + """ + self._impl.setProduct(val) + + def setInstance(self, val): + """ + """ + self._impl.setInstance(val) + + def setAttribute(self, key, val): + """ + """ + self._impl.setAttribute(key, val) + + def open(self): + """ + """ + self._impl.open() + + def close(self): + """ + """ + self._impl.close() + + def registerSchema(self, schema): + """ + """ + self._impl.registerSchema(schema._impl) + + def addData(self, data, name="", persistent=False): + """ + """ + return DataAddr(self._impl.addData(data._impl, name, persistent)) + + def delData(self, addr): + """ + """ + self._impl.delData(addr._impl) + + def methodSuccess(self, handle): + """ + """ + self._impl.methodSuccess(handle) + + def raiseException(self, handle, data): + """ + """ + if data.__class__ == Data: + self._impl.raiseException(handle, data._impl) + else: + self._impl.raiseException(handle, data) + + def raiseEvent(self, data, severity=None): + """ + """ + if not severity: + self._impl.raiseEvent(data._impl) + else: + if (severity.__class__ != int and severity.__class__ != long) or severity < 0 or severity > 7: + raise Exception("Severity must be an int between 0..7") + self._impl.raiseEvent(data._impl, severity); + + +#=================================================================================================== +# AGENT PROXY +#=================================================================================================== +class Agent(object): + """ + """ + + def __init__(self, impl): + self._impl = impl + + def __repr__(self): + return self.getName() + + def getName(self): + """ + """ + return self._impl.getName() + + def getEpoch(self): + """ + """ + return self._impl.getEpoch() + + def getVendor(self): + """ + """ + return self._impl.getVendor() + + def getProduct(self): + """ + """ + return self._impl.getProduct() + + def getInstance(self): + """ + """ + return self._impl.getInstance() + + def getAttributes(self): + """ + """ + return self._impl.getAttributes() + + def query(self, q, timeout=30): + """ + """ + if q.__class__ == Query: + q_arg = q._impl + else: + q_arg = q + dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout) + result = self._impl.query(q_arg, dur) + if result.getType() == cqmf2.CONSOLE_EXCEPTION: + raise Exception(Data(result.getData(0))) + if result.getType() != cqmf2.CONSOLE_QUERY_RESPONSE: + raise Exception("Protocol error, expected CONSOLE_QUERY_RESPONSE, got %d" % result.getType()) + dataList = [] + count = result.getDataCount() + for i in range(count): + dataList.append(Data(result.getData(i))) + return dataList + + def loadSchemaInfo(self, timeout=30): + """ + """ + dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout) + self._impl.querySchema(dur) + + def getPackages(self): + """ + """ + result = [] + count = self._impl.getPackageCount() + for i in range(count): + result.append(self._impl.getPackage(i)) + return result + + def getSchemaIds(self, package): + """ + """ + result = [] + count = self._impl.getSchemaIdCount(package) + for i in range(count): + result.append(SchemaId(self._impl.getSchemaId(package, i))) + return result + + def getSchema(self, schemaId, timeout=30): + """ + """ + dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout) + return Schema(self._impl.getSchema(schemaId._impl, dur)) + + ## TODO: Async query + ## TODO: Agent method + +#=================================================================================================== +# QUERY +#=================================================================================================== +class Query(object): + """ + """ + + def __init__(self, arg1, arg2=None, arg3=None, *kwargs): + """ + """ + if arg1.__class__ == DataAddr: + self._impl = cqmf2.Query(arg1._impl) + + def getAddr(self): + """ + """ + return DataAddr(self._impl.getDataAddr()) + + def getSchemaId(self): + """ + """ + return SchemaId(self._impl.getSchemaId()) + + def getPredicate(self): + """ + """ + return self._impl.getPredicate() + + def matches(self, data): + """ + """ + m = data + if data.__class__ == Data: + m = data.getProperties() + return self._impl.matchesPredicate(m) + +#=================================================================================================== +# DATA +#=================================================================================================== +class Data(object): + """ + """ + + def __init__(self, arg=None): + """ + """ + if arg == None: + self._impl = cqmf2.Data() + elif arg.__class__ == cqmf2.Data: + self._impl = arg + elif arg.__class__ == Schema: + self._impl = cqmf2.Data(arg._impl) + else: + raise Exception("Unsupported initializer for Data") + self._schema = None + + def getSchemaId(self): + """ + """ + if self._impl.hasSchema(): + return SchemaId(self._impl.getSchemaId()) + return None + + def getAddr(self): + """ + """ + if self._impl.hasAddr(): + return DataAddr(self._impl.getAddr()) + return None + + def getAgent(self): + """ + """ + return Agent(self._impl.getAgent()) + + def update(self, timeout=5): + dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout) + agent = self._impl.getAgent() + query = cqmf2.Query(self._impl.getAddr()) + result = agent.query(query, dur) + if result.getType() != cqmf2.CONSOLE_QUERY_RESPONSE: + raise "Update query failed" + if result.getDataCount == 0: + raise "Object no longer exists on agent" + self._impl = cqmf2.Data(result.getData(0)) + + def getProperties(self): + """ + """ + return self._impl.getProperties(); + + def _getSchema(self): + if not self._schema: + if not self._impl.hasSchema(): + raise Exception("Data object has no schema") + self._schema = Schema(self._impl.getAgent().getSchema(self._impl.getSchemaId())) + + def _invoke(self, name, args, kwargs): + ## + ## Get local copies of the agent and the address of the data object + ## + agent = self._impl.getAgent() + addr = self._impl.getAddr() + + ## + ## Set up the timeout duration for the method call. Set the default and override + ## it if the _timeout keyword arg was supplied. + ## + timeout = 30 + if '_timeout' in kwargs: + timeout = kwargs['_timeout'] + dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout) + + ## + ## Get the list of arguments from the schema, isolate those that are IN or IN_OUT, + ## validate that we have the right number of arguments supplied, and marshall them + ## into a map for transmission. + ## + arglist = [] + methods = self._schema.getMethods() + for m in methods: + if m.getName() == name: + arglist = m.getArguments() + break + argmap = {} + count = 0 + for a in arglist: + if a.getDirection() == DIR_IN or a.getDirection() == DIR_IN_OUT: + count += 1 + if count != len(args): + raise Exception("Wrong number of arguments: expected %d, got %d" % (count, len(args))) + i = 0 + for a in arglist: + if a.getDirection() == DIR_IN or a.getDirection() == DIR_IN_OUT: + argmap[a.getName()] = args[i] + i += 1 + + ## + ## Invoke the method through the agent proxy. + ## + result = agent.callMethod(name, argmap, addr, dur) + + ## + ## If the agent sent an exception, raise it in a QmfAgentException. + ## + if result.getType() == cqmf2.CONSOLE_EXCEPTION: + exdata = result.getData(0) + raise QmfAgentException(exdata) + + ## + ## If a successful method response was received, collect the output arguments into a map + ## and return them to the caller. + ## + if result.getType() != cqmf2.CONSOLE_METHOD_RESPONSE: + raise Exception("Protocol error: Unexpected event type in method-response: %d" % result.getType()) + return result.getArguments() + + def __getattr__(self, name): + ## + ## If we have a schema and an address, check to see if this name is the name of a method. + ## + if self._impl.hasSchema() and self._impl.hasAddr() and self._impl.hasAgent(): + ## + ## Get the schema for the data object. Note that this call will block if the remote agent + ## needs to be queried for the schema (i.e. the schema is not in the local cache). + ## + self._getSchema() + methods = self._schema.getMethods() + + ## + ## If the name matches a method in the schema, return a closure to be invoked. + ## + for method in methods: + if name == method.getName(): + return lambda *args, **kwargs : self._invoke(name, args, kwargs) + + ## + ## This is not a method call, return the property matching the name. + ## + return self._impl.getProperty(name) + + def __setattr__(self, name, value): + if name[0] == '_': + super.__setattr__(self, name, value) + return + self._impl.setProperty(name, value) + +#=================================================================================================== +# DATA ADDRESS +#=================================================================================================== +class DataAddr(object): + """ + """ + + def __init__(self, arg, agentName=""): + if arg.__class__ == dict: + self._impl = cqmf2.DataAddr(arg) + elif arg.__class__ == cqmf2.DataAddr: + self._impl = arg + else: + self._impl = cqmf2.DataAddr(arg, agentName) + + def __repr__(self): + return "%s:%s" % (self.getAgentName(), self.getName()) + + def __eq__(self, other): + return self.getAgentName() == other.getAgentName() and \ + self.getName() == other.getName() and \ + self.getAgentEpoch() == other.getAgentEpoch() + + def asMap(self): + """ + """ + return self._impl.asMap() + + def getAgentName(self): + """ + """ + return self._impl.getAgentName() + + def getName(self): + """ + """ + return self._impl.getName() + + def getAgentEpoch(self): + """ + """ + return self._impl.getAgentEpoch() + +#=================================================================================================== +# SCHEMA ID +#=================================================================================================== +class SchemaId(object): + """ + """ + + def __init__(self, impl): + self._impl = impl + + def __repr__(self): + return "%s:%s" % (self.getPackageName(), self.getName()) + + def getType(self): + """ + """ + return self._impl.getType() + + def getPackageName(self): + """ + """ + return self._impl.getPackageName() + + def getName(self): + """ + """ + return self._impl.getName() + + def getHash(self): + """ + """ + return self._impl.getHash() + +#=================================================================================================== +# SCHEMA +#=================================================================================================== +class Schema(object): + """ + """ + + def __init__(self, stype, packageName=None, className=None, desc=None, sev=None): + if stype.__class__ == cqmf2.Schema: + self._impl = stype + else: + self._impl = cqmf2.Schema(stype, packageName, className) + if desc: + self._impl.setDesc(desc) + if sev: + self._impl.setDefaultSeverity(sev) + + def __repr__(self): + return "QmfSchema:%r" % SchemaId(self._impl.getSchemaId()) + + def finalize(self): + """ + """ + self._impl.finalize() + + def getSchemaId(self): + """ + """ + return SchemaId(self._impl.getSchemaId()) + + def getDesc(self): + """ + """ + return self._impl.getDesc() + + def getSev(self): + """ + """ + return self._impl.getDefaultSeverity() + + def addProperty(self, prop): + """ + """ + self._impl.addProperty(prop._impl) + + def addMethod(self, meth): + """ + """ + self._impl.addMethod(meth._impl) + + def getProperties(self): + """ + """ + props = [] + count = self._impl.getPropertyCount() + for i in range(count): + props.append(SchemaProperty(self._impl.getProperty(i))) + return props + + def getMethods(self): + """ + """ + meths = [] + count = self._impl.getMethodCount() + for i in range(count): + meths.append(SchemaMethod(self._impl.getMethod(i))) + return meths + +#=================================================================================================== +# SCHEMA PROPERTY +#=================================================================================================== +class SchemaProperty(object): + """ + """ + + def __init__(self, name, dtype=None, **kwargs): + """ + """ + if name.__class__ == cqmf2.SchemaProperty: + self._impl = name + else: + self._impl = cqmf2.SchemaProperty(name, dtype) + if 'access' in kwargs: + self._impl.setAccess(kwargs['access']) + if 'index' in kwargs: + self._impl.setIndex(kwargs['index']) + if 'optional' in kwargs: + self._impl.setOptional(kwargs['optional']) + if 'unit' in kwargs: + self._impl.setUnit(kwargs['unit']) + if 'desc' in kwargs: + self._impl.setDesc(kwargs['desc']) + if 'subtype' in kwargs: + self._impl.setSubtype(kwargs['subtype']) + if 'direction' in kwargs: + self._impl.setDirection(kwargs['direction']) + + def __repr__(self): + return self._impl.getName() + + def getName(self): + """ + """ + return self._impl.getName() + + def getType(self): + """ + """ + return self._impl.getType() + + def getAccess(self): + """ + """ + return self._impl.getAccess() + + def isIndex(self): + """ + """ + return self._impl.isIndex() + + def isOptional(self): + """ + """ + return self._impl.isOptional() + + def getUnit(self): + """ + """ + return self._impl.getUnit() + + def getDesc(self): + """ + """ + return self._impl.getDesc() + + def getSubtype(self): + """ + """ + return self._impl.getSubtype() + + def getDirection(self): + """ + """ + return self._impl.getDirection() + +#=================================================================================================== +# SCHEMA METHOD +#=================================================================================================== +class SchemaMethod(object): + """ + """ + + def __init__(self, name, **kwargs): + """ + """ + if name.__class__ == cqmf2.SchemaMethod: + self._impl = name + else: + self._impl = cqmf2.SchemaMethod(name) + if 'desc' in kwargs: + self._impl.setDesc(kwargs['desc']) + + def __repr__(self): + return "%s()" % self._impl.getName() + + def getName(self): + """ + """ + return self._impl.getName() + + def getDesc(self): + """ + """ + return self._impl.getDesc() + + def addArgument(self, arg): + """ + """ + self._impl.addArgument(arg._impl) + + def getArguments(self): + """ + """ + result = [] + count = self._impl.getArgumentCount() + for i in range(count): + result.append(SchemaProperty(self._impl.getArgument(i))) + return result + diff --git a/qpid/cpp/bindings/qmf2/qmf2.i b/qpid/cpp/bindings/qmf2/qmf2.i new file mode 100644 index 0000000000..0f573fe3e6 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/qmf2.i @@ -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 <qmf/exceptions.h> +#include <qmf/AgentEvent.h> +#include <qmf/Agent.h> +#include <qmf/AgentSession.h> +#include <qmf/ConsoleEvent.h> +#include <qmf/ConsoleSession.h> +#include <qmf/DataAddr.h> +#include <qmf/Data.h> +#include <qmf/Query.h> +#include <qmf/Schema.h> +#include <qmf/SchemaId.h> +#include <qmf/SchemaMethod.h> +#include <qmf/SchemaProperty.h> +#include <qmf/SchemaTypes.h> +#include <qmf/Subscription.h> + +%} + +%include <qpid/ImportExport.h> +%include <qpid/messaging/ImportExport.h> +%include <qpid/messaging/Duration.h> + +%include <qmf/ImportExport.h> +%include <qmf/exceptions.h> +%include <qmf/AgentEvent.h> +%include <qmf/Agent.h> +%include <qmf/AgentSession.h> +%include <qmf/ConsoleEvent.h> +%include <qmf/ConsoleSession.h> +%include <qmf/DataAddr.h> +%include <qmf/Data.h> +%include <qmf/Query.h> +%include <qmf/Schema.h> +%include <qmf/SchemaId.h> +%include <qmf/SchemaMethod.h> +%include <qmf/SchemaProperty.h> +%include <qmf/SchemaTypes.h> +%include <qmf/Subscription.h> + +%{ + +using namespace qmf; + +%}; + diff --git a/qpid/cpp/bindings/qmf2/ruby/Makefile.am b/qpid/cpp/bindings/qmf2/ruby/Makefile.am new file mode 100644 index 0000000000..97bbc6f385 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/ruby/Makefile.am @@ -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. +# + +if HAVE_RUBY_DEVEL + +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src -I$(top_builddir)/src $(QMF_INCLUDES) + +EXTRA_DIST = ruby.i +BUILT_SOURCES = cqmf2.cpp +SWIG_FLAGS = -w362,401 + +rubylibdir = $(RUBY_LIB) + +cqmf2.cpp: $(srcdir)/ruby.i $(srcdir)/../qmf2.i $(srcdir)/../../swig_ruby_typemaps.i + $(SWIG) -ruby -c++ $(SWIG_FLAGS) $(INCLUDES) $(QPID_CXXFLAGS) -I/usr/include -o cqmf2.cpp $(srcdir)/ruby.i + +rubylibarchdir = $(RUBY_LIB_ARCH) +rubylibarch_LTLIBRARIES = cqmf2.la +dist_rubylib_DATA = qmf2.rb + +cqmf2_la_LDFLAGS = -avoid-version -module -shared -shrext ".$(RUBY_DLEXT)" +cqmf2_la_LIBADD = $(RUBY_LIBS) -L$(top_builddir)/src/.libs -lqmf2 $(top_builddir)/src/libqmf2.la +cqmf2_la_CXXFLAGS = $(INCLUDES) -I$(RUBY_INC) -I$(RUBY_INC_ARCH) -fno-strict-aliasing +nodist_cqmf2_la_SOURCES = cqmf2.cpp + +CLEANFILES = cqmf2.cpp + +endif # HAVE_RUBY_DEVEL diff --git a/qpid/cpp/bindings/qmf2/ruby/qmf2.rb b/qpid/cpp/bindings/qmf2/ruby/qmf2.rb new file mode 100644 index 0000000000..c14ecba4e1 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/ruby/qmf2.rb @@ -0,0 +1,855 @@ +# +# 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. +# + +require 'cqmf2' +require 'cqpid' +require 'thread' +require 'socket' +require 'monitor' + +module Qmf2 + + Cqmf2.constants.each do |c| + if c.index('AGENT_') == 0 or c.index('CONSOLE_') == 0 or + c.index('SCHEMA_') == 0 or c.index('SEV_') == 0 or c.index('QUERY') == 0 + const_set(c, Cqmf2.const_get(c)) + end + end + + SCHEMA_TYPE_DATA = Cqmf2::SCHEMA_TYPE_DATA() + SCHEMA_TYPE_EVENT = Cqmf2::SCHEMA_TYPE_EVENT() + + SCHEMA_DATA_VOID = Cqmf2::SCHEMA_DATA_VOID() + SCHEMA_DATA_BOOL = Cqmf2::SCHEMA_DATA_BOOL() + SCHEMA_DATA_INT = Cqmf2::SCHEMA_DATA_INT() + SCHEMA_DATA_FLOAT = Cqmf2::SCHEMA_DATA_FLOAT() + SCHEMA_DATA_STRING = Cqmf2::SCHEMA_DATA_STRING() + SCHEMA_DATA_MAP = Cqmf2::SCHEMA_DATA_MAP() + SCHEMA_DATA_LIST = Cqmf2::SCHEMA_DATA_LIST() + SCHEMA_DATA_UUID = Cqmf2::SCHEMA_DATA_UUID() + + ACCESS_READ_CREATE = Cqmf2::ACCESS_READ_CREATE() + ACCESS_READ_WRITE = Cqmf2::ACCESS_READ_WRITE() + ACCESS_READ_ONLY = Cqmf2::ACCESS_READ_ONLY() + + DIR_IN = Cqmf2::DIR_IN() + DIR_OUT = Cqmf2::DIR_OUT() + DIR_IN_OUT = Cqmf2::DIR_IN_OUT() + + SEV_EMERG = Cqmf2::SEV_EMERG() + SEV_ALERT = Cqmf2::SEV_ALERT() + SEV_CRIT = Cqmf2::SEV_CRIT() + SEV_ERROR = Cqmf2::SEV_ERROR() + SEV_WARN = Cqmf2::SEV_WARN() + SEV_NOTICE = Cqmf2::SEV_NOTICE() + SEV_INFORM = Cqmf2::SEV_INFORM() + SEV_DEBUG = Cqmf2::SEV_DEBUG() + + ##============================================================================== + ## EXCEPTIONS + ##============================================================================== + + class QmfAgentException < RuntimeError + attr :data + + def initialize(data) + @data = data + end + + def to_s + "QmfAgentException: #{@data}" + end + end + + ##============================================================================== + ## AGENT HANDLER + ##============================================================================== + + class AgentHandler + + def initialize(session) + @_session = session + @_running = false + @_thread = nil + end + + ## + ## Call the "start" method to run the handler on a new thread. + ## + def start + @_thread = Thread.new do + run + end + end + + ## + ## Request that the running thread complete and exit. + ## + def cancel + @_running = false + @_thread.join if @_thread + @_thread = nil + end + + ## + ## Call the "run" method only if you want the handler to run on your own thread. + ## + def run + @_running = true + event = Cqmf2::AgentEvent.new + while @_running do + valid = @_session.impl.nextEvent(event, Cqpid::Duration.SECOND) + if valid and @_running + case event.getType + when Cqmf2::AGENT_AUTH_QUERY + yes = authorize_query(Query.new(event.getQuery()), event.getUserId()) + if yes == true + @_session.impl.authAccept(event) + else + @_session.impl.authReject(event) + end + + when Cqmf2::AGENT_QUERY + context = QueryContext.new(@_session, event) + get_query(context, Query.new(event.getQuery()), event.getUserId()) + + when Cqmf2::AGENT_METHOD + context = MethodContext.new(@_session, event) + begin + method_call(context, event.getMethodName(), event.getDataAddr(), event.getArguments(), event.getUserId()) + rescue Exception => ex + @_session.impl.raiseException(event, "#{ex}") + end + + end + end + end + end + + + ## + ## The following methods are intended to be overridden in a sub-class. They are + ## handlers for events that occur on QMF consoles. + ## + + # + # This method will only be invoked if the "allow-queries" option is enabled on the + # agent session. When invoked, it provides the query and the authenticated user-id + # of the querying client. + # + # This method must return true if the query is permitted, false otherwise. + # + def authorize_query(query, user_id); end + + # + # This method will only be invoked if the "external" option is "True" on the agent + # session. When invoked, the method should begin the process of responding to a data + # query. The authenticated user-id of the requestor is provided for informational + # purposes. The 'context' variable is used to provide the results back to the requestor. + # + # For each matching Data object, call context.response(data). When the query is complete, + # call context.complete(). After completing the query, you should not use 'context' any + # longer. + # + # Note: It is not necessary to process the query synchronously. If desired, this method + # may store the context for asynchronous processing or pass it to another thread for + # processing. There is no restriction on the number of contexts that may be in-flight + # concurrently. + # + def get_query(context, query, user_id); end + + # + # This method is invoked when a console calls a QMF method on the agent. Supplied are + # a context for the response, the method name, the data address of the data object being + # called, the input arguments (a dictionary), and the caller's authenticated user-id. + # + # A method call can end one of two ways: Successful completion, in which the output + # arguments (if any) are supplied; and Exceptional completion if there is an error. + # + # Successful Completion: + # For each output argument, assign the value directly to context (context.arg1 = "value") + # Once arguments are assigned, call context._success(). + # + # Exceptional Completion: + # Method 1: Call context._exception(data) where 'data' is a string or a Data object. + # Method 2: Raise an exception (raise "Error Text") synchronously in the method body. + # + # Note: Like get_query, method_call may process methods synchronously or asynchronously. + # This method may store the context for later asynchronous processing. There is no + # restriction on the number of contexts that may be in-flight concurrently. + # + # However, "Method 2" for Exceptional Completion can only be done synchronously. + # + def method_call(context, method_name, data_addr, args, user_id); end + end + + class QueryContext + def initialize(agent, context) + @agent = agent + @context = context + end + + def response(data) + @agent.impl.response(@context, data.impl) + end + + def complete + @agent.impl.complete(@context) + end + end + + class MethodContext + def initialize(agent, context) + @agent = agent + @context = context + end + + def _success + @agent.impl.methodSuccess(@context) + end + + def _exception(ex) + if ex.class == Data + @agent.impl.raiseException(@context, ex.impl) + else + @agent.impl.raiseException(@context, ex) + end + end + + def method_missing(name_in, *args) + name = name_in.to_s + if name[name.length - 1] == 61 + name = name[0..name.length - 2] + @context.impl.addReturnArgument(name, args[0]) + else + super.method_missing(name_in, args) + end + end + end + + ##============================================================================== + ## CONSOLE HANDLER + ##============================================================================== + + class ConsoleHandler + + def initialize(session) + @_session = session + @_running = false + @_thread = nil + end + + ## + ## Call the "start" method to run the handler on a new thread. + ## + def start + @_thread = Thread.new do + run + end + end + + ## + ## Request that the running thread complete and exit. + ## + def cancel + @_running = false + @_thread.join if @_thread + @_thread = nil + end + + ## + ## Call the "run" method only if you want the handler to run on your own thread. + ## + def run + @_running = true + event = Cqmf2::ConsoleEvent.new + while @_running do + valid = @_session.impl.nextEvent(event, Cqpid::Duration.SECOND) + if valid and @_running + case event.getType + when Cqmf2::CONSOLE_AGENT_ADD + agent_added(Agent.new(event.getAgent)) + + when Cqmf2::CONSOLE_AGENT_DEL + reason = :filter + reason = :aged if event.getAgentDelReason == Cqmf2::AGENT_DEL_AGED + agent_deleted(Agent.new(event.getAgent), reason) + + when Cqmf2::CONSOLE_AGENT_RESTART + agent_restarted(Agent.new(event.getAgent)) + + when Cqmf2::CONSOLE_AGENT_SCHEMA_UPDATE + agent_schema_updated(Agent.new(event.getAgent)) + + when Cqmf2::CONSOLE_EVENT + event_raised(Agent.new(event.getAgent), Data.new(event.getData(0)), event.getTimestamp, event.getSeverity) + + end + end + end + end + + + ## + ## The following methods are intended to be overridden in a sub-class. They are + ## handlers for events that occur on QMF consoles. + ## + + # + # A new agent, whose attributes match the console's agent filter, has been discovered. + # + def agent_added(agent); end + + # + # A known agent has been removed from the agent list. There are two possible reasons + # for agent deletion: + # + # 1) :aged - The agent hasn't been heard from for the maximum age interval and is + # presumed dead. + # 2) :filter - The agent no longer matches the console's agent-filter and has been + # effectively removed from the agent list. Such occurrences are likely + # to be seen immediately after setting the filter to a new value. + # + def agent_deleted(agent, reason); end + + # + # An agent-restart was detected. This occurs when the epoch number advertised by the + # agent changes. It indicates that the agent in question was shut-down/crashed and + # restarted. + # + def agent_restarted(agent); end + + # + # The agent has registered new schema information which can now be queried, if desired. + # + def agent_schema_updated(agent); end + + # + # An agent raised an event. The 'data' argument is a Data object that contains the + # content of the event. + # + def event_raised(agent, data, timestamp, severity); end + end + + ##============================================================================== + ## CONSOLE SESSION + ##============================================================================== + + class ConsoleSession + attr_reader :impl + + ## The options string is of the form "{key:value,key:value}". The following keys are supported: + ## + ## domain:NAME - QMF Domain to join [default: "default"] + ## max-agent-age:N - Maximum time, in minutes, that we will tolerate not hearing from + ## an agent before deleting it [default: 5] + ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default] + ## If False: Listen only on the routable direct address + ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network + ## - If False: Operate more flexibly with regard to use of messaging facilities [default] + ## + def initialize(connection, options="") + @impl = Cqmf2::ConsoleSession.new(connection, options) + end + + def set_domain(domain) @impl.setDomain(domain) end + def set_agent_filter(filter) @impl.setAgentFilter(filter) end + + def open() @impl.open end + def close() @impl.close end + + def agents + result = [] + count = @impl.getAgentCount + for i in 0...count + result << Agent.new(@impl.getAgent(i)) + end + return result + end + + def connected_broker_agent + Agent.new(@impl.getConnectedBrokerAgent) + end + end + + ##============================================================================== + ## AGENT SESSION + ##============================================================================== + + class AgentSession + attr_reader :impl + + ## The options string is of the form "{key:value,key:value}". The following keys are supported: + ## + ## interval:N - Heartbeat interval in seconds [default: 60] + ## external:{True,False} - Use external data storage (queries and subscriptions are pass-through) [default: False] + ## allow-queries:{True,False} - If True: automatically allow all queries [default] + ## If False: generate an AUTH_QUERY event to allow per-query authorization + ## allow-methods:{True,False} - If True: automatically allow all methods [default] + ## If False: generate an AUTH_METHOD event to allow per-method authorization + ## max-subscriptions:N - Maximum number of concurrent subscription queries permitted [default: 64] + ## min-sub-interval:N - Minimum publish interval (in milliseconds) permitted for a subscription [default: 3000] + ## sub-lifetime:N - Lifetime (in seconds with no keepalive) for a subscription [default: 300] + ## public-events:{True,False} - If True: QMF events are sent to the topic exchange [default] + ## If False: QMF events are only sent to authorized subscribers + ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default] + ## If False: Listen only on the routable direct address + ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network + ## - If False: Operate more flexibly with regard to use of messaging facilities [default] + ## + def initialize(connection, options="") + @impl = Cqmf2::AgentSession.new(connection, options) + end + + def set_domain(val) @impl.setDomain(val) end + def set_vendor(val) @impl.setVendor(val) end + def set_product(val) @impl.setProduct(val) end + def set_instance(val) @impl.setInstance(val) end + def set_attribute(key, val) @impl.setAttribute(key, val) end + def open() @impl.open end + def close() @impl.close end + def register_schema(cls) @impl.registerSchema(cls.impl) end + + def add_data(data, name="", persistent=false) + DataAddr.new(@impl.addData(data.impl, name, persistent)) + end + + def del_data(addr) + @impl.del_data(addr.impl) + end + + def raise_event(data, severity=nil) + if !severity + @impl.raiseEvent(data.impl) + else + @impl.raiseEvent(data.impl, severity) + end + end + end + + ##============================================================================== + ## AGENT PROXY + ##============================================================================== + + class Agent + attr_reader :impl + + def initialize(impl) + @impl = impl + end + + def name() @impl.getName end + def epoch() @impl.getEpoch end + def vendor() @impl.getVendor end + def product() @impl.getProduct end + def instance() @impl.getInstance end + def attributes() @impl.getAttributes end + + def to_s + "#{vendor}:#{product}:#{instance}" + end + + def query(q, timeout=30) + if q.class == Query + q_arg = q.impl + else + q_arg = q + end + dur = Cqpid::Duration.new(Cqpid::Duration.SECOND.getMilliseconds * timeout) + result = @impl.query(q_arg, dur) + raise QmfAgentException.new(Data.new(result.getData(0))) if result.getType == Cqmf2::CONSOLE_EXCEPTION + raise "Protocol error, expected CONSOLE_QUERY_RESPONSE, got #{result.getType}" if result.getType != Cqmf2::CONSOLE_QUERY_RESPONSE + data_list = [] + count = result.getDataCount + for i in 0...count + data_list << Data.new(result.getData(i)) + end + return data_list + end + + def load_schema_info(timeout=30) + dur = Cqpid::Duration.new(Cqpid::Duration.SECOND.getMilliseconds * timeout) + @impl.querySchema(dur) + end + + def packages + result = [] + count = @impl.getPackageCount + for i in 0...count + result << @impl.getPackage(i) + end + return result + end + + def schema_ids(package) + result = [] + count = @impl.getSchemaIdCount(package) + for i in 0...count + result << SchemaId.new(@impl.getSchemaId(package, i)) + end + return result + end + + def schema(sid, timeout=30) + dur = Cqpid::Duration.new(Cqpid::Duration.SECOND.getMilliseconds * timeout) + Schema.new(@impl.getSchema(sid.impl, dur)) + end + end + + ##============================================================================== + ## QUERY + ##============================================================================== + + class Query + attr_reader :impl + def initialize(arg1, arg2=nil, arg3=nil) + if arg1.class == Qmf2::DataAddr + @impl = Cqmf2::Query.new(arg1.impl) + end + end + + def addr() DataAddr.new(@impl.getDataAddr()) end + def schema_id() SchemaId.new(@impl.getSchemaId()) end + def predicate() @impl.getPredicate() end + + def matches?(data) + map = data + map = data.properties if data.class == Data + @impl.matchesPredicate(map) + end + end + + ##============================================================================== + ## DATA + ##============================================================================== + + class Data + attr_reader :impl + + def initialize(arg=nil) + @schema = nil + if arg == nil + @impl = Cqmf2::Data.new + elsif arg.class == Cqmf2::Data + @impl = arg + elsif arg.class == Schema + @impl = Cqmf2::Data.new(arg.impl) + @schema = arg + else + raise "Unsupported initializer for Data" + end + end + + def to_s + "#{@impl.getProperties}" + end + + def schema_id + if @impl.hasSchema + return SchemaId.new(@impl.getSchemaId) + end + return nil + end + + def set_addr(addr) + @impl.setAddr(addr.impl) + end + + def addr + if @impl.hasAddr + return DataAddr.new(@impl.getAddr) + end + return nil + end + + def agent + return Agent.new(@impl.getAgent) + end + + def update(timeout=5) + dur = Cqpid::Duration.new(Cqpid::Duration.SECOND.getMilliseconds * timeout) + agent = @impl.getAgent + query = Cqmf2::Query.new(@impl.getAddr) + result = agent.query(query, dur) + raise "Update query failed" if result.getType != Cqmf2::CONSOLE_QUERY_RESPONSE + raise "Object no longer exists on agent" if result.getDataCount == 0 + @impl = Cqmf2::Data.new(result.getData(0)) + return nil + end + + def properties + return @impl.getProperties + end + + def get_attr(name) + @impl.getProperty(name) + end + + def set_attr(name, v) + @impl.setProperty(name, v) + end + + def [](name) + get_attr(name) + end + + def []=(name, value) + set_attr(name, value) + end + + def _get_schema + unless @schema + raise "Data object has no schema" unless @impl.hasSchema + @schema = Schema.new(@impl.getAgent.getSchema(@impl.getSchemaId)) + end + end + + def method_missing(name_in, *args) + # + # Convert the name to a string and determine if it represents an + # attribute assignment (i.e. "attr=") + # + name = name_in.to_s + attr_set = (name[name.length - 1] == 61) + name = name[0..name.length - 2] if attr_set + + # + # We'll be needing the schema to determine how to proceed. Get the schema. + # Note that this call may block if the remote agent needs to be queried + # for the schema (i.e. the schema isn't in the local cache). + # + _get_schema + + # + # If the name matches a property name, set or return the value of the property. + # + @schema.properties.each do |prop| + if prop.name == name + if attr_set + return set_attr(name, args[0]) + else + return get_attr(name) + end + end + end + + # + # If we still haven't found a match for the name, check to see if + # it matches a method name. If so, marshall the arguments and invoke + # the method. + # + @schema.methods.each do |method| + if method.name == name + raise "Sets not permitted on methods" if attr_set + result = @impl.getAgent.callMethod(name, _marshall(method, args), @impl.getAddr) + if result.getType == Cqmf2::CONSOLE_EXCEPTION + raise QmfAgentException, result.getData(0) + end + return result.getArguments + end + end + + # + # This name means nothing to us, pass it up the line to the parent + # class's handler. + # + super.method_missing(name_in, args) + end + + # + # Convert a Ruby array of arguments (positional) into a Value object of type "map". + # + private + def _marshall(schema, args) + count = 0 + schema.arguments.each do |arg| + if arg.direction == DIR_IN || arg.direction == DIR_IN_OUT + count += 1 + end + end + raise "Wrong number of arguments: expecter #{count}, got #{arge.length}" if count != args.length + map = {} + count = 0 + schema.arguments.each do |arg| + if arg.direction == DIR_IN || arg.direction == DIR_IN_OUT + map[arg.name] = args[count] + count += 1 + end + end + return map + end + end + + ##============================================================================== + ## DATA ADDRESS + ##============================================================================== + + class DataAddr + attr_reader :impl + + def initialize(arg, agentName="") + if arg.class == Hash + @impl = Cqmf2::DataAddr.new(arg) + elsif arg.class == Cqmf2::DataAddr + @impl = arg + else + @impl = Cqmf2::DataAddr.new(arg, agentName) + end + end + + def ==(other) + return @impl == other.impl + end + + def as_map() @impl.asMap end + def agent_name() @impl.getAgentName end + def name() @impl.getName end + def agent_epoch() @impl.getAgentEpoch end + end + + ##============================================================================== + ## SCHEMA ID + ##============================================================================== + + class SchemaId + attr_reader :impl + def initialize(impl) + @impl = impl + end + + def type() @impl.getType end + def package_name() @impl.getPackageName end + def class_name() @impl.getName end + def hash() @impl.getHash end + end + + ##============================================================================== + ## SCHEMA + ##============================================================================== + + class Schema + attr_reader :impl + def initialize(arg, packageName="", className="", kwargs={}) + if arg.class == Cqmf2::Schema + @impl = arg + else + @impl = Cqmf2::Schema.new(arg, packageName, className) + @impl.setDesc(kwargs[:desc]) if kwargs.include?(:desc) + @impl.setDefaultSeverity(kwargs[:sev]) if kwargs.include?(:sev) + end + end + + def finalize() @impl.finalize end + def schema_id() SchemaId.new(@impl.getSchemaId) end + def desc() @impl.getDesc end + def sev() @impl.getDefaultSeverity end + def add_property(prop) @impl.addProperty(prop.impl) end + def add_method(meth) @impl.addMethod(meth.impl) end + + def properties + result = [] + count = @impl.getPropertyCount + for i in 0...count + result << SchemaProperty.new(@impl.getProperty(i)) + end + return result + end + + def methods + result = [] + count = @impl.getMethodCount + for i in 0...count + result << SchemaMethod.new(@impl.getMethod(i)) + end + return result + end + end + + ##============================================================================== + ## SCHEMA PROPERTY + ##============================================================================== + + class SchemaProperty + attr_reader :impl + + def initialize(arg, dtype=nil, kwargs={}) + if arg.class == Cqmf2::SchemaProperty + @impl = arg + else + @impl = Cqmf2::SchemaProperty.new(arg, dtype) + @impl.setAccess(kwargs[:access]) if kwargs.include?(:access) + @impl.setIndex(kwargs[:index]) if kwargs.include?(:index) + @impl.setOptional(kwargs[:optional]) if kwargs.include?(:optional) + @impl.setUnit(kwargs[:unit]) if kwargs.include?(:unit) + @impl.setDesc(kwargs[:desc]) if kwargs.include?(:desc) + @impl.setSubtype(kwargs[:subtype]) if kwargs.include?(:subtype) + @impl.setDirection(kwargs[:direction]) if kwargs.include?(:direction) + end + end + + def name() @impl.getName end + def access() @impl.getAccess end + def index?() @impl.isIndex end + def optional?() @impl.isOptional end + def unit() @impl.getUnit end + def desc() @impl.getDesc end + def subtype() @impl.getSubtype end + def direction() @impl.getDirection end + + def to_s + name + end + end + + ##============================================================================== + ## SCHEMA METHOD + ##============================================================================== + + class SchemaMethod + attr_reader :impl + + def initialize(arg, kwargs={}) + if arg.class == Cqmf2::SchemaMethod + @impl = arg + else + @impl = Cqmf2::SchemaMethod.new(arg) + @impl.setDesc(kwargs[:desc]) if kwargs.include?(:desc) + end + end + + def name() @impl.getName end + def desc() @impl.getDesc end + def add_argument(arg) @impl.addArgument(arg.impl) end + + def arguments + result = [] + count = @impl.getArgumentCount + for i in 0...count + result << SchemaProperty.new(@impl.getArgument(i)) + end + return result + end + + def to_s + name + end + end +end + + diff --git a/qpid/cpp/bindings/qmf2/ruby/ruby.i b/qpid/cpp/bindings/qmf2/ruby/ruby.i new file mode 100644 index 0000000000..1070c65a44 --- /dev/null +++ b/qpid/cpp/bindings/qmf2/ruby/ruby.i @@ -0,0 +1,35 @@ +/* + * 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. + */ + +%module cqmf2 +%include "std_string.i" +%include "../../swig_ruby_typemaps.i" + +/* Define the general-purpose exception handling */ +%exception { + try { + $action + } + catch (qpid::types::Exception& mex) { + static VALUE qmferror = rb_define_class("QmfError", rb_eStandardError); + rb_raise(qmferror, mex.what()); + } +} + +%include "../qmf2.i" |