summaryrefslogtreecommitdiff
path: root/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQuery.java
blob: c0555cc91f3ff3a0c00c77c95983a1c0554d92bb (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
/*
 *
 * 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.qmf2.common;

// Misc Imports
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

// Reuse this class as it provides a handy mechanism to parse an predicate String into a Map
import org.apache.qpid.messaging.util.AddressParser;

/**
 * A Query is a mechanism for interrogating the management database. A Query represents a selector which is sent to
 * an Agent. The Agent applies the Query against its management database, and returns those objects which meet the  
 * constraints described in the query.
 * <p>
 * A Query must specify the class of information it is selecting. This class of information is considered the target
 * of the query. Any data objects selected by the query will be of the type indicated by the target.
 * <p>
 * A Query may also specify a selector which is used as a filter against the set of all target instances. Only those 
 * instances accepted by the filter will be returned in response to the query.
 * <p>
 * N.B. There appear to be a number of differences in the description of the map encoding of a Query between the
 * QMF2 API specified at <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a> and the
 * QMF2 protocol that is specified at <a href=https://cwiki.apache.org/qpid/qmf-map-message-protocol.html>QMF Map
 * Message Protocol</a> in particular the use of the underscore to specify key names e.g. "_what", "_where",
 * "_object_id", "_schema_id".
 * <p>
 * This implementation trusts the protocol specification more than the API specification as the underscores are more  
 * consistent with the rest of the protocol and the underscored variants are what have been observed when querying
 * the broker ManagementAgent.
 * <p>
 * A QmfQuery may be constructed as either an "ID" query (to query for a specific ObjectId or SchemaClassId) or a
 * "PREDICATE" query (to query based upon an expression). Note that QMF considers string arguments in boolean
 * expressions to be names of data values in the target object. When evaluating a predicate expression, QMF will fetch
 * the value of the named data item from each candidate target object. The value is then used in the boolean expression.
 * In other words, QMF considers string arguments to be variables in the expression. In order to indicate that a string 
 * should be treated as a literal instead, the string must be quoted using the "quote" expression.
 * <p>
 * <b>Examples</b>
 * <p>
 * Assume a QmfData type defines fields named "name", "address" and "town". The following predicate expression matches
 * any instance with a name field set to "tross", or any instance where the name field is "jross", the address field is
 * "1313 Spudboy Lane" and the town field is "Utopia":
 * <p>
 * <pre>
 * ["or" ["eq" "name" ["quote" "tross"]]
 *       ["and" ["eq" "name" ["quote" "jross"]]
 *              ["eq" "address" ["quote" "1313 Spudboy Lane"]]
 *              ["eq" ["quote" "Utopia"] "town"]
 *     ]
 * ]
 * </pre>
 * Assume a QmfData type with fields "name" and "age". A predicate to find all instances with name matching the regular 
 * expression "?ross" with an optional age field that is greater than the value 29 or less than 12 would be:
 * <pre>
 * ["and" ["re_match" "name" ["quote" "?ross"]]
 *        ["and" ["exists" "age"]
 *               ["or" ["gt" "age" 27] ["lt" "age" 12]]
 *        ]
 * ]
 * </pre>
 * <p>
 * The Expression structure is illustrated below in the context of its relationship with QmfQuery. 
 * <img src="doc-files/QmfQuery.png"/>
 *
 *
 * @author Fraser Adams
 */
public final class QmfQuery extends QmfData
{
    public static final QmfQuery ID = new QmfQuery();
    public static final QmfQuery PREDICATE = new QmfQuery();

    private QmfQueryTarget _target;
    private SchemaClassId  _classId;
    private String         _packageName;
    private String         _className;
    private ObjectId       _objectId;
    private List           _predicate;
    private Expression     _expression;

    /**
     * This Constructor is only used to construct the ID and PREDICATE objects
     */
    private QmfQuery()
    {
    }

    /**
     * Construct an QmfQuery with no Selector from a QmfQueryTarget
     * @param target the query target
     */
    public QmfQuery(final QmfQueryTarget target)
    {
        _target = target;
        setValue("_what", _target.toString());
    }

    /**
     * Construct an ID QmfQuery from a QmfQueryTarget and SchemaClassId
     * @param target the query target
     * @param classId the SchemaClassId to evaluate against
     */
    public QmfQuery(final QmfQueryTarget target, final SchemaClassId classId)
    {
        _target = target;
        _classId = classId;
        _packageName = _classId.getPackageName();
        _className = _classId.getClassName();
        setValue("_what", _target.toString());
        setValue("_schema_id", _classId.mapEncode());
    }

    /**
     * Construct an ID QmfQuery from a QmfQueryTarget and ObjectId
     * @param target the query target
     * @param objectId the ObjectId to evaluate against
     */
    public QmfQuery(final QmfQueryTarget target, final ObjectId objectId)
    {
        _target = target;
        _objectId = objectId;
        setValue("_what", _target.toString());
        setValue("_object_id", _objectId.mapEncode());
    }

    /**
     * Construct a PREDICATE QmfQuery from a QmfQueryTarget and predicate String
     * @param target the query target
     * @param predicateString the predicate to evaluate against
     */
    public QmfQuery(final QmfQueryTarget target, final String predicateString) throws QmfException
    {
        _target = target;

        if (predicateString.charAt(0) == '[')
        {
            Map predicateMap = new AddressParser("{'_where': " + predicateString + "}").map();
            _predicate = (List)predicateMap.get("_where");
            _expression = Expression.createExpression(_predicate);
        }
        else
        {
            throw new QmfException("Invalid predicate format");
        }

        setValue("_what", _target.toString());
        setValue("_where", _predicate);
    }

    /**
     * Construct a QmfQuery from a Map encoding
     * @param m encoding the query
     */
    public QmfQuery(final Map m) throws QmfException
    {
        super(m);

        _target = QmfQueryTarget.valueOf(getStringValue("_what"));

        if (hasValue("_object_id"))
        {
            _objectId = getRefValue("_object_id");
        }

        if (hasValue("_schema_id"))
        {
            _classId = new SchemaClassId((Map)getValue("_schema_id"));
            _packageName = _classId.getPackageName();
            _className = _classId.getClassName();
        }

        if (hasValue("_where"))
        {
            _predicate = (List)getValue("_where");
            _expression = Expression.createExpression(_predicate);
        }
    }

    /**
     * Return target name.
     * @return target name.
     */
    public QmfQueryTarget getTarget()
    {
        return _target;
    }

    /**
     * Undefined by QMF2 API.
     * <p>
     * According to <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Specification</a>
     * "The value of the <target name string> map entry is ignored for now, its use is TBD."
     * so this method returns a null Map.
     */
    public Map getTargetParam()
    {
        return null;
    }

    /**
     * Return QmfQuery.ID or QmfQuery.PREDICATE or null if there is no Selector
     * @return QmfQuery.ID or QmfQuery.PREDICATE or null if there is no Selector
     */
    public QmfQuery getSelector()
    {
        if (_predicate == null)
        {
            if (_objectId == null && _classId == null)
            {
                return null;
            }
            return ID;
        }
        return PREDICATE;
    }

    /**
     * Return predicate expression if selector type is QmfQuery.PREDICATE
     * @return predicate expression if selector type is QmfQuery.PREDICATE
     */
    public List getPredicate()
    {
        return _predicate;
    }

    /**
     * Return the SchemaClassId if selector type is QmfQuery.ID
     * @return the SchemaClassId if selector type is QmfQuery.ID
     */
    public SchemaClassId getSchemaClassId()
    {
        return _classId;
    }

    /**
     * Return the ObjectId if selector type is QmfQuery.ID
     * @return the ObjectId if selector type is QmfQuery.ID
     */
    public ObjectId getObjectId()
    {
        return _objectId;
    }

    /**
     * Evaluate query against a QmfData instance.
     * @return true if query matches the QmfData instance, else false.
     */
    public boolean evaluate(final QmfData data)
    {
        if (_predicate == null)
        {
            if (data instanceof QmfManaged)
            {
                QmfManaged managedData = (QmfManaged)data;
                // Evaluate an ID query on Managed Data
                if (_objectId != null && _objectId.equals(managedData.getObjectId()))
                {
                    return true;
                }
                else if (_classId != null)
                {
                    SchemaClassId dataClassId = managedData.getSchemaClassId();
                    String dataClassName = dataClassId.getClassName();
                    String dataPackageName = dataClassId.getPackageName();

                    // Wildcard the package name if it hasn't been specified when checking class name
                    if (_className.equals(dataClassName) &&
                        (_packageName.length() == 0 || _packageName.equals(dataPackageName)))
                    {
                        return true;
                    }

                    // Wildcard the class name if it hasn't been specified when checking package name
                    if (_packageName.equals(dataPackageName) &&
                        (_className.length() == 0 || _className.equals(dataClassName)))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        else
        {
            // Evaluate a PREDICATE query by evaluating against the expression created from the predicate
            if (_predicate.size() == 0)
            {
                return true;
            }

            return _expression.evaluate(data);
        }
    }

    /**
     * Helper/debug method to list the QMF Object properties and their type.
     */
    @Override
    public void listValues()
    {
        System.out.println("QmfQuery:");
        System.out.println("target: " + _target);
        if (_predicate != null)
        {
            System.out.println("selector: QmfQuery.PREDICATE");
            System.out.println("predicate: " + _predicate);
        }
        else if (_classId != null)
        {
            System.out.println("selector: QmfQuery.ID");
            _classId.listValues();
        }
        else if (_objectId != null)
        {
            System.out.println("selector: QmfQuery.ID");
            System.out.println(_objectId);
        }
    }
}