summaryrefslogtreecommitdiff
path: root/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Broker.java
blob: 9602d8eb4917d28e683ff155e18a7e99e0f966da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
/*
 *
 * 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.qmf2.agentdata;

// Misc Imports

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.qpid.qmf2.agent.Agent;
import org.apache.qpid.qmf2.agent.QmfAgentData;
import org.apache.qpid.qmf2.common.Handle;
import org.apache.qpid.qmf2.common.ObjectId;
import org.apache.qpid.qmf2.common.QmfData;
import org.apache.qpid.qmf2.common.SchemaObjectClass;
import org.apache.qpid.server.model.Binding;
import org.apache.qpid.server.model.Exchange;
import org.apache.qpid.server.model.LifetimePolicy;
import org.apache.qpid.server.model.Port;
import org.apache.qpid.server.model.Protocol;
import org.apache.qpid.server.model.Queue;
import org.apache.qpid.server.model.State;
import org.apache.qpid.server.model.Transport;
import org.apache.qpid.server.model.VirtualHost;
import org.apache.qpid.server.model.VirtualHostNode;

// Simple Logging Facade 4 Java
// QMF2 Imports
/*import org.apache.qpid.qmf2.common.QmfEvent;
import org.apache.qpid.qmf2.common.QmfEventListener;
import org.apache.qpid.qmf2.common.QmfException;
import org.apache.qpid.qmf2.common.QmfType;*/
// Java Broker model Imports

/**
 * This class provides a concrete implementation of QmfAgentData for the Broker Management Object.
 * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class
 * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful
 * to sub-class as we need to map between the properties/statistics as specified in the Java Broker
 * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker 
 * uses. This class retains a reference to its peer org.apache.qpid.server.model.Broker and does the
 * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData).
 *
 * @author Fraser Adams
 */
public class Broker extends QmfAgentData
{
    private static final Logger _log = LoggerFactory.getLogger(Broker.class);

    /**
     * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find
     * QmfAgentData Objects of a given type.
     */
    private static final SchemaObjectClass _schema;
    public static SchemaObjectClass getSchema()
    {
        return _schema;
    }

    static
    {
        // Declare the schema for the QMF2 broker class.
        _schema = new SchemaObjectClass("org.apache.qpid.broker", "broker");

        // TODO
        //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING));
    }

    private final org.apache.qpid.server.model.Broker<?> _broker; // Passed in by Plugin bootstrapping.
    private final String _defaultVirtualHost; // Pulled from the broker attributes.

    /**
     * This inner class parses the name String that was passed in as a QMF method argument.
     * There are a few quirks with this name. In the first instance it may be prefixed with Virtual Host information
     * e.g. [vhost:<vhost-name>/]<queue-name> this is because in order to allow Java Broker QMF to work with things like
     * qpid-config which are not particularly Virtual Host aware prefixing the names seemed to be the most natural,
     * if slightly ugly approach. In addition the way bindings are named is of the form:
     * <exchange-name>/<queue-name>[/<binding-key>] so we need a mechanism to parse the relevant information.
     * <p>
     * N.B. the parsing that takes place in this class makes an assumption that there are no valid exchange or queue
     * names that contain a "/". This is probably a reasonable assumption given the way that a binding name is
     * constructed, but it's worth recording the restriction here in case such a beast crops up.
     * <p>
     * This class also provides accessors that allow the Exchange, Queue and Binding ConfiguredObjects for the
     * name parsed in the Constructor to be retrieved. This is generally useful because although in QMF the create
     * and delete methods are invoked on the Broker object in the Java Broker ConfiguredObject model the underlying
     * methods are distributed across a number of different classes.
     */
    private class NameParser
    {
        private String _vhostName;
        private VirtualHost<?,?,?> _vhost = null;
        private String _exchangeName = "";
        private Exchange<?> _exchange = null;
        private String _queueName = "";
        private Queue _queue = null;
        private String _bindingKey = "";
        private Binding _binding = null;

        /**
         * NameParser Constructor.
         * The Constructor actually does the majority of the parsing, the remaining method are largely just accessors.
         *
         * @param name the name argument that was retrieved from the QMF method inArgs. This will be the exchange name,
         * the queue name or the binding name (which is of the form <exchange-name>/<queue-name>[/<binding-key>])
         * exchange and queue names may be prefixed by a Virtual Host name e.g. [vhost:<vhost-name>/]<queue-name>
         * @param type the type argument that was retrieved from the QMF method inArgs. Valid types are "exchange, 
         * "queue" or "binding".
         */
        public NameParser(final String name, final String type)
        {
            boolean malformedVHostName = false;
            String[] splitName = name.split("/"); // A slash is used as a separator in a couple of scenarios.
            if (name.startsWith("vhost:"))
            {
                if (splitName.length == 1) // If it starts with vhost: the name should also contain at least one "/".
                {
                    malformedVHostName = true;
                    _vhostName = name;
                }
                else
                {
                    _vhostName = splitName[0];
                    _vhostName = _vhostName.substring(6, _vhostName.length());
                }
            }
            else
            {
                _vhostName = _defaultVirtualHost;
            }

            // If the vhostName isn't malformed then try to find the actual Virtual Host that it relates to.
            // If it is malformed the vhost stays set to null, which will cause an exception to be returned later.
            if (!malformedVHostName)
            {
                for (VirtualHostNode<?> vhostNode : _broker.getVirtualHostNodes())
                {
                    VirtualHost<?,?,?> vhost = vhostNode.getVirtualHost();
                    if (vhost.getName().equals(_vhostName))
                    {
                        _vhost = vhost;
                        break;
                    }
                }
            }

            // Populate the exchange, queue and binding names. We only populate the names in the constructor
            // when we actually want to find the Object associated with the name we do it "on demand" in the
            // relevant accessor and cache the result.
            if (type.equals("exchange"))
            {
                _exchangeName = splitName[splitName.length - 1];
            }
            else if (type.equals("queue"))
            {
                _queueName = splitName[splitName.length - 1];
            }
            else if (type.equals("binding"))
            { // TODO is there a way to make this parse less nasty and a bit more elegant....
                int i = 0;
                String vhost1Name = _defaultVirtualHost; // The exchange and queue vhostName need to be the same.
                if (splitName[i].startsWith("vhost:")) // Does the exchange name specify a vhost?
                {
                    vhost1Name = splitName[i];
                    i++;
                }

                if (i < splitName.length) // Extract the exchange name sans vhost part.
                {
                    _exchangeName = splitName[i];
                    i++;
                }

                String vhost2Name = _defaultVirtualHost;
                if (i < splitName.length && splitName[i].startsWith("vhost:")) // Does the queue name specify a vhost?
                {
                    vhost2Name = splitName[i];
                    i++;
                }

                // If the exchange and queue vhost names differ set _vhost and _vhostName to null which causes
                // an exception that says "VirtualHost names for exchange and queue must match.".
                if (!vhost2Name.equals(vhost1Name))
                {
                    _vhost = null;
                    _vhostName = null;
                }

                if (i < splitName.length) // Extract the queue name sans vhost part.
                {
                    _queueName = splitName[i];
                    i++;
                }

                if (i < splitName.length) // Extract the binding key if present (it's optional).
                {
                    _bindingKey = splitName[i];
                    i++;
                }
            } // End of binding name parse.
        }

        // NameParser accessors.

        /**
         * Retrieves the name of the Virtual Host that was parsed from the name supplied in the Constructor.
         * @return the parsed Virtual Host name (may be an empty String).
         */
        public String getVirtualHostName()
        {
            return _vhostName;
        }

        /**
         * Retrieves the Virtual Host with the name that was parsed from the name supplied in the Constructor.
         * @return the Virtual Host with the name that was parsed from the name supplied in the Constructor (may be null).
         */
        public VirtualHost getVirtualHost()
        {
            return _vhost;
        }

        /**
         * Retrieves the name of the Exchange that was parsed from the name supplied in the Constructor.
         * @return the parsed Exchange name (may be an empty String).
         */
        public String getExchangeName()
        {
            return _exchangeName;
        }

        /**
         * Retrieves the Exchange with the name that was parsed from the name supplied in the Constructor.
         * @return the Exchange with the name that was parsed from the name supplied in the Constructor (may be null).
         */
        public Exchange getExchange()
        {
            // If we've not previously cached the _exchange and the previously parsed Virtual Host isn't null we do a 
            // look up for the actual Exchange with the name _exchangeName and cache it.
            if (_exchange == null && _vhost != null)
            {
                for (Exchange exchange : _vhost.getExchanges())
                {
                    if (exchange.getName().equals(_exchangeName))
                    {
                        _exchange = exchange;
                        break;
                    }
                }
            }

            return _exchange;
        }

        /**
         * Retrieves the name of the Queue that was parsed from the name supplied in the Constructor.
         * @return the parsed Queue name (may be an empty String).
         */
        public String getQueueName()
        {
            return _queueName;
        }

        /**
         * Retrieves the Queue with the name that was parsed from the name supplied in the Constructor.
         * @return the Queue with the name that was parsed from the name supplied in the Constructor (may be null).
         */
        public Queue getQueue()
        {
            // If we've not previously cached the _queue and the previously parsed Virtual Host isn't null we do a 
            // look up for the actual Queue with the name _queueName and cache it.
            if (_queue == null && _vhost != null)
            {
                for (Queue queue : _vhost.getQueues())
                {
                    if (queue.getName().equals(_queueName))
                    {
                        _queue = queue;
                        break;
                    }
                }
            }

            return _queue;
        }

        /**
         * Retrieves the name of the Binding that was parsed from the name supplied in the Constructor.
         * @return the parsed Binding name (may be an empty String).
         */
        public String getBindingKey()
        {
            return _bindingKey;
        }

        /**
         * Retrieves the Binding with the name that was parsed from the name supplied in the Constructor.
         * @return the Binding with the name that was parsed from the name supplied in the Constructor (may be null).
         */
        public Binding getBinding()
        {
            // In order to retrieve a Binding it's first necessary to get the Exchange (or Queue) ConfiguredObject.
            _exchange = getExchange(); // Need to get it via the accessor as it's initialised by lazy evaluation.

            // If we've not previously cached the _binding and the previously retrieved Exchange isn't null we do a 
            // look up for the actual Binding with the name _bindingKey and cache it.
            if (_binding == null && _exchange != null)
            {
                for (Binding binding : _exchange.getBindings())
                {
                    if (binding.getName().equals(_bindingKey))
                    {
                        _binding = binding;
                        break;
                    }
                }
            }

            return _binding;
        }
    } // End of class NameParser

    /**
     * Broker Constructor.
     * @param broker the root Broker Management Object from which the other Management Objects may be obtained.
     */
    public Broker(final org.apache.qpid.server.model.Broker broker)
    {
        super(getSchema());

        _broker = broker;
        _defaultVirtualHost = broker.findDefautVirtualHostNode().getVirtualHost().getName();
        int amqpPort = 5672; // Default AMQP Port.

        // Search through the available Ports on this Broker looking for the AMQP Port using the TCP Transport
        // and record that in amqpPort. N.B. The Java Broker supports several Protocol and Transport types so
        // it might be good to return more detailed info, but for QMF the "port" property description for the
        // Broker Object says "TCP Port for AMQP Service" so doing anything fancier might break consumers.
        // TODO The C++ and Java Brokers should really return consistent information.
        for (Port<?> port : _broker.getPorts())
        {
            boolean isAMQP = false;
            boolean isTCP  = false;

            for (Protocol protocol : port.getProtocols())
            {
                isAMQP = protocol.isAMQP();
                if (isAMQP)
                {
                    break;
                }
            }

            for (Transport transport : port.getTransports())
            {
                isTCP = (transport == Transport.TCP);
                if (isTCP)
                {
                    break;
                }
            }

            if (isAMQP && isTCP)
            {
                amqpPort = port.getPort();
                break;
            }
        }

        // systemRef is ignored in this implementation.
        // stagingThreshold is ignored in this implementation (it's deprecated anyway I believe).

        // Use this name to be fairly consistent with C++ broker which uses "amqp-broker".
        // N.B. although it's useful to be able to distinguish between C++ and Java brokers note that the
        // _object_name in the ObjectId that we set below uses actually uses "amqp-broker" vice "amqp-java-broker",
        // this is because qpid-config uses a "hardcoded" ObjectId to invoke methods so we need to use the same name.
        setValue("name", "amqp-java-broker");
        setValue("port", amqpPort);

        // workerThreads doesn't *appear* to be configurable in the Java Broker, looks like there's no pool and the
        // Threads just grow with the number of Connections?
        setValue("workerThreads", 0);

        // maxConns doesn't *appear* to be configurable in the Java Broker.
        setValue("maxConns", 0);

        // The Java Broker ServerSocket seems to be created in org.apache.qpid.transport.network.io.IoNetworkTransport
        // In AcceptingThread. The does't appear to use any configuration for ServerSocket, which suggests that the
        // backlog is the default value which is assumed to be 10.
        setValue("connBacklog", 10);

        // "Technically" this isn't quite the same as for the C++ broker, which pushes management data to a particular 
        // topic - the subscription "qmf.default.topic/agent.ind.#" grabs that plus heartbeats for the C++ broker.
        // This Agent allows the use of the QMF2 Query Subscriptions (which the C++ broker does not!! - the Console
        // class fakes this client side for the C++ broker. TODO make sure that the Console does not fake for Java broker.
        setValue("mgmtPublish", true);
        setValue("mgmtPubInterval", 10);

        setValue("version", broker.getProductVersion());
        setValue("dataDir", System.getProperty("QPID_WORK"));

        // ObjectId needs to be set here in Broker because the QMF2 version of qpid-config uses a hardcoded
        // _object_name of "org.apache.qpid.broker:broker:amqp-broker" in the _object_id that it sets.
        // It *shouldn't* do this and should really use the _object_id of the broker object returned by
        // getObjects("broker"), but it does. The following line causes the Agent to use the explicit
        // ObjectId below rather than constructing its own, which fixes the qpid-config issue.
        // Note we use "amqp-broker" in the ObjectId to be compatible with qpid-config but we set the actual
        // name to "amqp-java-broker" as it's useful to be able to distinguish between C++ and Java Brokers.
        setObjectId(new ObjectId("", "org.apache.qpid.broker:broker:amqp-broker", 0));
    }

    /**
     * This helper method checks the supplied properties Map for the "alternate-exchange" property, if it is present
     * the property is removed from the map and the alternate exchange is parsed to recover the Virtual Host name
     * and the actual alternate exchange name. If the alternate exchange Virtual Host name is not the same as the
     * supplied vhostName this method returns "invalid" otherwise it returns the alternate exchange name or null.
     *
     * @param vhostName the Virtual Host name that we want to compare the alternate exchange's Virtual Host name with.
     * @param properties a Map of properties that might contain "alternate-exchange".
     * @return the alternate exchange name if present, null if not present or "invalid" if the Virtual Host name that
     * was parsed from the alternate exchange doesn't match the name supplied in the vhostName parameter.
     */
    private String parseAlternateExchange(String vhostName, Map<String, Object> properties)
    {
        String alternateExchange = null;
        Object property = properties.get("alternate-exchange");
        if (property != null && property instanceof String) // Alternate exchange has been specified.
        {
            alternateExchange = property.toString();
            properties.remove("alternate-exchange");

            String altExVhostName = _defaultVirtualHost;
            String[] splitName = alternateExchange.split("/"); 
            if (alternateExchange.startsWith("vhost:"))
            {
                altExVhostName = splitName[0];
                altExVhostName = altExVhostName.substring(6, altExVhostName.length());
            }

            // If the Virtual Hosts differ raise an exception and return.
            if (!altExVhostName.equals(vhostName))
            {
                return "invalid";
            }
        }

        return alternateExchange;
    }

    /**
     * This method acts as a single entry point for QMF methods invoked on the Broker Object.
     *
     * @param agent the org.apache.qpid.qmf2.agent.Agent instance that we call methodResponse() and raiseException() on.
     * @param handle the reply handle used by methodResponse() and raiseException().
     * @param methodName the name of the QMF method being invoked.
     * @param inArgs a Map of input arguments wrapped in a QmfData Object.
     */
    @SuppressWarnings("unchecked")
    public void invokeMethod(Agent agent, Handle handle, String methodName, QmfData inArgs)
    {
        if (methodName.equals("create") || methodName.equals("delete"))
        {
            QmfData outArgs = new QmfData();

            String name = inArgs.getStringValue("name");
            String type = inArgs.getStringValue("type");

            NameParser nameParser = new NameParser(name, type);
            String vhostName = nameParser.getVirtualHostName();
            VirtualHost vhost = nameParser.getVirtualHost();

            if (vhost == null)
            {
                if (vhostName == null)
                {
                    agent.raiseException(handle, "VirtualHost names for exchange and queue must match.");
                }
                else
                {
                    agent.raiseException(handle, "VirtualHost " + vhostName + " not found.");
                }
            }
            else
            {
                if (methodName.equals("create")) // method = create
                {
                    try
                    {
                        //boolean strict = inArgs.getBooleanValue("strict");
                        Map<String, Object> properties = inArgs.getValue("properties");

                        boolean durable = false;
                        Object property = properties.get("durable");
                        if (property != null && property instanceof Boolean)
                        {
                            Boolean durableProperty = (Boolean)property;
                            durable = durableProperty.booleanValue();
                            properties.remove("durable");
                        }

                        if (type.equals("exchange")) // create exchange.
                        {
/*
System.out.println("Create Exchange");
System.out.println("vhostName = " + vhostName);
System.out.println("exchange name = " + nameParser.getExchangeName());
System.out.println("properties = " + properties);
*/
                            String exchangeType = "";
                            property = properties.get("exchange-type");
                            if (property != null && property instanceof String)
                            {
                                exchangeType = property.toString();
                                properties.remove("exchange-type");
                            }

                            String alternateExchange = parseAlternateExchange(vhostName, properties);
                            if (alternateExchange != null && alternateExchange.equals("invalid"))
                            {
                                agent.raiseException(handle, "Alternate Exchange must belong to the same Virtual Host as the Exchange being added.");
                                return;
                            }

                            // Note that for Qpid 0.20 the "qpid.msg_sequence=1" and "qpid.ive=1" properties are
                            // not suppored, indeed no exchange properties seem to be supported yet.
                            Map<String,Object> attributes = new HashMap<>();
                            attributes.put(Exchange.NAME, nameParser.getExchangeName());
                            attributes.put(Exchange.STATE, State.ACTIVE);
                            attributes.put(Exchange.DURABLE, durable);
                            attributes.put(Exchange.LIFETIME_POLICY, LifetimePolicy.PERMANENT);
                            attributes.put(Exchange.TYPE, exchangeType);
                            attributes.put(Exchange.ALTERNATE_EXCHANGE, alternateExchange);


                            vhost.createExchange(attributes);

                        } // End of create exchange.
                        else if (type.equals("queue")) // create queue.
                        {
/*
System.out.println("Create Queue");
System.out.println("vhostName = " + vhostName);
System.out.println("queue name = " + nameParser.getQueueName());
System.out.println("properties = " + properties);
*/

                            // TODO Try to map from the QMF create queue properties to the closest equivalents on
                            // the Java Broker. Unfortunately there are a *lot* of frustrating little differences.


                            String alternateExchange = parseAlternateExchange(vhostName, properties);
                            if (alternateExchange != null && alternateExchange.equals("invalid"))
                            {
                                agent.raiseException(handle, "Alternate Exchange must belong to the same Virtual Host as the Queue being added.");
                                return;
                            }

                            // I don't *think* that it make sense to allow setting exclusive or autoDelete to
                            // a queue created from config.
                            Map<String,Object> attributes = new HashMap<String,Object>(properties);
                            attributes.put(Queue.NAME, nameParser.getQueueName());
                            attributes.put(Queue.DURABLE, durable);
                            attributes.put(Queue.LIFETIME_POLICY, LifetimePolicy.PERMANENT);


                            // Set the queue's alternateExchange, which is just a little bit involved......
                            // The queue.setAttribute() method needs an org.apache.qpid.server.model.Exchange instance
                            // not just a name, so we look up org.apache.qpid.server.qmf2.agentdata.Exchange by ID
                            // and get its associated org.apache.qpid.server.model.Exchange. We can do a look up by ID
                            // because we needed to use ObjectIds that were based on names in order to allow qpid-config
                            // to work, so we may as well make use of this convenience here too.
                            if (alternateExchange != null)
                            {
                                ObjectId objectId =
                                        new ObjectId("", "org.apache.qpid.broker:exchange:" + alternateExchange, 0);

                                // Look up Exchange QmfAgentData by ObjectId from the Agent's internal Object store.
                                QmfAgentData object = agent.getObject(objectId);
                                if (object != null)
                                {
                                    org.apache.qpid.server.qmf2.agentdata.Exchange ex =
                                        (org.apache.qpid.server.qmf2.agentdata.Exchange)object;

                                    Exchange altEx = ex.getExchange();
                                    attributes.put(Queue.ALTERNATE_EXCHANGE, altEx.getId());
                                }
                            }
                            Queue queue = vhost.createQueue(attributes);
                        }
                        else if (type.equals("binding")) // create binding.
                        {
                            Exchange exchange = nameParser.getExchange();
                            if (exchange == null)
                            {
                                agent.raiseException(handle, "Cannot create binding on Exchange " +
                                                     nameParser.getExchangeName());
                                return;
                            }
                            else
                            {
                                Map<String, Object> attributes = Collections.emptyMap();
                                exchange.createBinding(nameParser.getBindingKey(), nameParser.getQueue(),
                                                       properties, attributes);
                            }
                        }

                        agent.methodResponse(methodName, handle, outArgs, null);
                    }
                    catch (Exception e)
                    {
                        agent.raiseException(handle, e.getMessage());
                    }
                }
                else // method = delete
                {
                    try
                    {
                        if (type.equals("exchange")) // delete exchange.
                        {
                            Exchange exchange = nameParser.getExchange();
                            if (exchange != null)
                            {
                                exchange.delete();
                            }
                        }
                        else if (type.equals("queue")) // delete queue.
                        {
                            Queue queue = nameParser.getQueue();
                            if (queue != null)
                            {
                                queue.deleteAndReturnCount();
                            }
                        }
                        else if (type.equals("binding")) // delete binding.
                        {
                            Binding binding = nameParser.getBinding();
                            if (binding != null)
                            {
                                binding.delete();
                            }
                        }

                        agent.methodResponse(methodName, handle, outArgs, null);
                    }
                    catch (Exception e)
                    {
                        agent.raiseException(handle, e.getMessage());
                    }
                }
            }
        }
        else // If methodName is not create or delete.
        {
            agent.raiseException(handle, methodName + " not yet implemented on Broker.");
        }
    } // End of invokeMethod.

    /**
     * This method maps the org.apache.qpid.server.model.Broker to QMF2 broker properties where possible then
     * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest()
     * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour.
     * 
     * @return the underlying map. 
     */
    @Override
    public Map<String, Object> mapEncode()
    {
        update(); // Need to do update before setting uptime in order to get the latest getUpdateTime() value.

        // Not sure if there's an "official" broker uptime anywhere, but as the QmfManagementAgent is created when
        // the broker is and the Broker object is created then too the following approach should be good enough.
        setValue("uptime", getUpdateTime() - getCreateTime());

        return super.mapEncode();
    }
}