From 31c83799d67d0bf4012aefedaba5356ebfaad8ab Mon Sep 17 00:00:00 2001 From: Prasanth Pallamreddy Date: Wed, 14 May 2014 10:03:37 -0700 Subject: [PATCH] Support for FIQL queries on REST retreive operations - Retreive operations can be filtered based on FIQL queries. - Spec at http://tools.ietf.org/html/draft-nottingham-atompub-fiql-00 - Details of the feature can be found at https://pad.opendaylight.org/p/rest-search Change-Id: I3d7cc4350e890148fff5bd086f76ec51e63f2c3b Signed-off-by: Prasanth Pallamreddy --- opendaylight/commons/opendaylight/pom.xml | 15 + opendaylight/northbound/commons/pom.xml | 43 +- .../commons/NorthboundApplication.java | 2 + .../northbound/commons/query/Accessor.java | 55 +++ .../commons/query/CompareExpression.java | 56 +++ .../northbound/commons/query/Expression.java | 12 + .../commons/query/ExpressionBuilder.java | 41 ++ .../commons/query/IteratableTypeInfo.java | 67 ++++ .../commons/query/LogicalExpression.java | 50 +++ .../northbound/commons/query/Query.java | 52 +++ .../commons/query/QueryContext.java | 25 ++ .../commons/query/QueryContextImpl.java | 33 ++ .../commons/query/QueryContextProvider.java | 27 ++ .../commons/query/QueryException.java | 30 ++ .../northbound/commons/query/QueryImpl.java | 237 +++++++++++ .../northbound/commons/query/TypeInfo.java | 294 ++++++++++++++ .../northbound/commons/query/Visitor.java | 16 + .../commons/query/WrapperTypeInfo.java | 59 +++ .../commons/src/main/javacc/fiql.jj | 124 ++++++ .../northbound/commons/query/BookBean.java | 92 +++++ .../commons/query/ExpresssionTest.java | 207 ++++++++++ .../northbound/commons/query/Library.java | 29 ++ .../northbound/commons/query/PersonBean.java | 50 +++ .../commons/query/QueryContextTest.java | 269 +++++++++++++ .../northbound/commons/query/ReviewBean.java | 41 ++ .../commons/query/XMLAccessorTypeTest.java | 374 ++++++++++++++++++ .../commons/src/test/resources/logback.xml | 16 + .../northbound/connectionmanager/pom.xml | 2 + .../ConnectionManagerNorthbound.java | 19 +- .../northbound/containermanager/pom.xml | 2 + .../ContainerManagerNorthbound.java | 34 +- ...ntainerManagerNorthboundRSApplication.java | 4 + .../northbound/controllermanager/pom.xml | 2 + .../ControllerManagerNorthbound.java | 21 +- .../northbound/flowprogrammer/pom.xml | 2 + .../northbound/FlowProgrammerNorthbound.java | 36 +- opendaylight/northbound/hosttracker/pom.xml | 2 + .../northbound/HostTrackerNorthbound.java | 35 +- opendaylight/northbound/staticrouting/pom.xml | 2 + .../northbound/StaticRoutingNorthbound.java | 21 +- opendaylight/northbound/statistics/pom.xml | 2 + .../northbound/StatisticsNorthbound.java | 41 +- opendaylight/northbound/subnets/pom.xml | 2 + .../subnets/northbound/SubnetsNorthbound.java | 25 +- opendaylight/northbound/switchmanager/pom.xml | 2 + .../northbound/SwitchNorthbound.java | 38 +- opendaylight/northbound/topology/pom.xml | 2 + .../northbound/TopologyNorthboundJAXRS.java | 51 ++- 48 files changed, 2602 insertions(+), 59 deletions(-) create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Accessor.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/CompareExpression.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Expression.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/ExpressionBuilder.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/IteratableTypeInfo.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/LogicalExpression.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Query.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContext.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextImpl.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextProvider.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryException.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryImpl.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/TypeInfo.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Visitor.java create mode 100644 opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/WrapperTypeInfo.java create mode 100644 opendaylight/northbound/commons/src/main/javacc/fiql.jj create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/BookBean.java create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ExpresssionTest.java create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/Library.java create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/PersonBean.java create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/QueryContextTest.java create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ReviewBean.java create mode 100644 opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/XMLAccessorTypeTest.java create mode 100644 opendaylight/northbound/commons/src/test/resources/logback.xml diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index 58dbef67d5..c83bf751ee 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -2008,6 +2008,21 @@ + + + org.codehaus.mojo + javacc-maven-plugin + [0.0,) + + javacc + + + + + false + + + diff --git a/opendaylight/northbound/commons/pom.xml b/opendaylight/northbound/commons/pom.xml index 09c075735a..a2d2dac112 100644 --- a/opendaylight/northbound/commons/pom.xml +++ b/opendaylight/northbound/commons/pom.xml @@ -12,7 +12,6 @@ 0.4.2-SNAPSHOT bundle - com.fasterxml.jackson.core jackson-databind @@ -65,6 +64,13 @@ org.opendaylight.controller usermanager + + + ch.qos.logback + logback-classic + test + + @@ -78,6 +84,7 @@ org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.types, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.northbound.commons javax.ws.rs, javax.ws.rs.ext, @@ -105,6 +112,40 @@ ${project.basedir}/META-INF + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + + add-source + + generate-sources + + + ${project.build.directory}/generated-sources/javacc + + + + + + + + org.codehaus.mojo + javacc-maven-plugin + 2.6 + + + javacc + + javacc + + + + + diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/NorthboundApplication.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/NorthboundApplication.java index 4393b79f64..87f51364ba 100644 --- a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/NorthboundApplication.java +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/NorthboundApplication.java @@ -20,6 +20,7 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.XmlRootElement; import org.opendaylight.controller.northbound.bundlescanner.IBundleScanService; +import org.opendaylight.controller.northbound.commons.query.QueryContextProvider; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleReference; @@ -58,6 +59,7 @@ public class NorthboundApplication extends Application { } ); _singletons.add(getJsonProvider()); _singletons.add(new JacksonJsonProcessingExceptionMapper()); + _singletons.add(new QueryContextProvider()); } //////////////////////////////////////////////////////////////// diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Accessor.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Accessor.java new file mode 100644 index 0000000000..2d910edc6d --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Accessor.java @@ -0,0 +1,55 @@ +package org.opendaylight.controller.northbound.commons.query; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/*package*/ class Accessor { + protected final AccessibleObject _accessorObj; + + public Accessor(AccessibleObject accessor) { + _accessorObj = accessor; + _accessorObj.setAccessible(true); + } + + public AccessibleObject getAccessibleObject() { + return _accessorObj; + } + + public Annotation[] getAnnotations() { + return _accessorObj.getAnnotations(); + } + + public Object getValue(Object parent) throws QueryException { + try { + if (_accessorObj instanceof Field) { + return ((Field)_accessorObj).get(parent); + } else { + // assume method + return ((Method)_accessorObj).invoke(parent); + } + } catch (Exception e) { + throw new QueryException("Failure in retrieving value", e); + } + } + public Type getGenericType() { + if (_accessorObj instanceof Field) { + return ((Field)_accessorObj).getGenericType(); + } else { + // assume method + return ((Method)_accessorObj).getGenericReturnType(); + } + } + public Class getType() { + + if (_accessorObj instanceof Field) { + return ((Field)_accessorObj).getType(); + } else { + // assume method + return ((Method)_accessorObj).getReturnType(); + } + } + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/CompareExpression.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/CompareExpression.java new file mode 100644 index 0000000000..6b972e641c --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/CompareExpression.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +/*package*/ class CompareExpression implements Expression { + + public static enum OP { RE, EQ, NE, GT, GE, LT, LE }; + + private final OP _operation; + private final String _selector; + private final String _arg; + + public CompareExpression(OP op, String selector, String arg) { + _operation = op; + _selector = selector; + _arg = unQuote(arg); + } + + + public OP getOperator() { + return _operation; + } + + public String getSelector() { + return _selector; + } + + public String getArgument() { + return _arg; + } + + @Override + public boolean accept(Visitor visitor) throws QueryException { + return visitor.visit(this); + } + + @Override + public String toString() { + return "[" + _selector + " " + _operation + " " + _arg + "]"; + } + + private static String unQuote(String s) { + if (s.startsWith("\"") && s.endsWith("\"")) { + s = s.substring(1, s.length()-1); + } else if (s.startsWith("\'") && s.endsWith("\'")) { + s = s.substring(1, s.length()-1); + } + return s; + } + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Expression.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Expression.java new file mode 100644 index 0000000000..fbc22a016d --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Expression.java @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +/*package*/ interface Expression { + boolean accept(Visitor visitor) throws QueryException; +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/ExpressionBuilder.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/ExpressionBuilder.java new file mode 100644 index 0000000000..f1b2999b77 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/ExpressionBuilder.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.Stack; + +/*package*/ class ExpressionBuilder { + private final Stack _stack = new Stack(); + private LogicalExpression.OP _lastOp = null; + + public ExpressionBuilder() {} + + public ExpressionBuilder withAnd() { + _lastOp = LogicalExpression.OP.AND; + return this; + } + + public ExpressionBuilder withOr() { + _lastOp = LogicalExpression.OP.OR; + return this; + } + + public ExpressionBuilder withTerm(Expression exp) { + if (_lastOp != null) { + exp = new LogicalExpression(_lastOp, _stack.pop(), exp); + _lastOp = null; + } + _stack.push(exp); + return this; + } + + public Expression build() { + return _stack.pop(); + } + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/IteratableTypeInfo.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/IteratableTypeInfo.java new file mode 100644 index 0000000000..3977837c7f --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/IteratableTypeInfo.java @@ -0,0 +1,67 @@ +package org.opendaylight.controller.northbound.commons.query; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + */ +/*package*/ class IteratableTypeInfo extends TypeInfo { + + public IteratableTypeInfo(String name, Accessor accessor) { + super(name, accessor.getType(), accessor); + } + + @Override + public Object retrieve(Object target, String[] query, int index) throws QueryException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("retrieve collection: {}/{} type:{}", index, query.length, + target.getClass()); + } + explore(); + Collection c = (Collection) target; + Iterator it = c.iterator(); + List objects = new ArrayList(); + while (it.hasNext()) { + Object item = it.next(); + for (TypeInfo child : _types.values()) { + Object val = child.retrieve(item, query, index); + if (val != null) objects.add(val); + } + } + return objects; + + } + + @Override + public synchronized void explore() { + if (_explored) return; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("exploring iteratable type: {} gtype: {}", _class, + _accessor.getGenericType()); + } + Type t = _accessor.getGenericType(); + if (t instanceof ParameterizedType) { + Type[] pt = ((ParameterizedType) t).getActualTypeArguments(); + // First type is a child, ignore rest + if (pt.length > 0) { + _types.put(_name, new TypeInfo(_name, (Class)pt[0], null)); + } + } + _explored = true; + } + + @Override + public TypeInfo getCollectionChild(Class childType) { + explore(); + for (TypeInfo ti : _types.values()) { + if (ti.getType().equals(childType)) { + return ti; + } + } + return null; + } +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/LogicalExpression.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/LogicalExpression.java new file mode 100644 index 0000000000..4e99820983 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/LogicalExpression.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +/*package*/ class LogicalExpression implements Expression { + + public static enum OP { AND, OR } + + private final OP _op; + private final Expression _arg1; + private final Expression _arg2; + + public LogicalExpression(OP op, Expression first, Expression second) { + _op = op; + _arg1 = first; + _arg2 = second; + } + + public OP getOperator() { + return _op; + } + + public Expression getFirst() { + return _arg1; + } + + public Expression getSecond() { + return _arg2; + } + + @Override + public boolean accept(Visitor visitor) throws QueryException { + return visitor.visit(this); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(_arg1.toString()) + .append(_op.toString()) + .append(_arg2.toString()); + return sb.toString(); + } + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Query.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Query.java new file mode 100644 index 0000000000..15dcaebe27 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Query.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.Collection; +import java.util.List; + + +/** + * Represents a parsed query used in filtering of collections. + */ +public interface Query { + + /** + * Find items in the given collection and return them as a new list. The + * original collection is not changed. + * + * @param collection to search in. + * @return list of items which match the query. + * @throws QueryException + */ + public List find(Collection collection) throws QueryException; + + /** + * Apply the query on the given collection. Note that this method will modify + * the given object by removing any items which don't match the query criteria. + * If the collection is 'singleton' or unmodifiable, invocation will result in + * an exception. + * + * @param collection + * @return the number matched items + * @throws QueryException + */ + public int filter(Collection collection) throws QueryException; + + /** + * Search the given root for a child collection and them apply the query on. + * Note that this method will modify the given object by removing any items + * which don't match the query criteria. + * + * @param root - top level object to search in + * @param childType - the child type which represents the collection. + * @return the number of matched items + * @throws QueryException + */ + public int filter(T root, Class childType) throws QueryException; +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContext.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContext.java new file mode 100644 index 0000000000..59a78ac113 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContext.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +/** + * Query context + */ +public interface QueryContext { + + /** + * Create a Query + * @param queryString - query string to parse + * @param clazz - The class which represents the top level jaxb object + * @return a query object + * @throws QueryException if the query cannot be parsed. + */ + Query createQuery(String queryString, Class clazz) + throws QueryException; + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextImpl.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextImpl.java new file mode 100644 index 0000000000..13d70b1cb0 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextImpl.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/*package*/ class QueryContextImpl implements QueryContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(QueryContext.class); + + @Override + public Query createQuery(String queryString, Class type) throws QueryException { + if (queryString == null || queryString.trim().length() == 0) return null; + try { + if (LOGGER.isDebugEnabled()) LOGGER.debug("Processing query: {}", queryString); + // FiqlParser is a parser generated by javacc + Expression expression = FiqlParser.parse(queryString); + if (LOGGER.isDebugEnabled()) LOGGER.debug("Query expression: {}", expression); + // create Query and return; + return new QueryImpl(type, expression); + } catch (Exception ex) { + if (LOGGER.isDebugEnabled()) LOGGER.error("Query processing failed = {}", + queryString, ex); + throw new QueryException("Unable to parse query.", ex); + } + } +} \ No newline at end of file diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextProvider.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextProvider.java new file mode 100644 index 0000000000..65a232c245 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryContextProvider.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; + +/** + * A provider for getting hold of the QueryContext. + */ +@Provider +public class QueryContextProvider implements ContextResolver { + + // Singleton Query Context instance + private static final QueryContext queryContext = new QueryContextImpl(); + + @Override + public QueryContext getContext(Class type) { + return queryContext; + } + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryException.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryException.java new file mode 100644 index 0000000000..9ff78eddfc --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryException.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Signals that an error happened during the parsing or processing of a query. + */ +public class QueryException extends WebApplicationException { + + private static final long serialVersionUID = 1L; + + public QueryException(String msg) { + super(Response.status(Response.Status.BAD_REQUEST) + .entity(msg).type(MediaType.TEXT_PLAIN).build()); + } + + public QueryException(String msg, Throwable cause) { + super(cause, Response.status(Response.Status.BAD_REQUEST) + .entity(msg).type(MediaType.TEXT_PLAIN).build()); + } +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryImpl.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryImpl.java new file mode 100644 index 0000000000..a520f98fc0 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/QueryImpl.java @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + +import org.opendaylight.controller.northbound.commons.query.CompareExpression.OP; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +/*package*/ class QueryImpl implements Query { + public static final Logger LOGGER = LoggerFactory.getLogger(QueryImpl.class); + private static final boolean ALLOW_OBJECT_STRING_COMPARE = true; + + private final Expression expression; + private final TypeInfo rootType ; + /** + * Set the expression and cache + * @param type + * @param expression + */ + public QueryImpl(Class type, Expression expression) { + this.expression = expression; + this.rootType = TypeInfo.createRoot(null, type); + } + + @Override + public List find(Collection collection) throws QueryException { + // new arraylist for result + List result = new ArrayList(); + for (T item : collection) { + if (match(item, rootType)) { + result.add(item); + } + } + return result; + } + + @Override + public int filter(Collection collection) throws QueryException { + // find items + List matched = new ArrayList(); + for (T item : collection) { + if (match(item, rootType)) { + matched.add(item); + } + } + collection.clear(); + collection.addAll(matched); + return matched.size(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public int filter(T rootObject, Class childClass) throws QueryException { + // retrieve underlying collection + TypeInfo childType = rootType.getCollectionChild(childClass); + if (childType == null || !(childType instanceof IteratableTypeInfo)) { + return 0; + } + Collection collection = (Collection) + childType.getAccessor().getValue(rootObject); + // get the child type of the collection type + TypeInfo ti = childType.getCollectionChild(childClass); + List matched = new ArrayList(); + for (Object item : collection) { + if (match(item, ti)) { + matched.add(item); + } + } + collection.clear(); + collection.addAll(matched); + return matched.size(); + } + + private boolean match(final Object object, final TypeInfo rootType) + throws QueryException { + return expression.accept(new Visitor () { + @Override + public boolean visit(LogicalExpression le) throws QueryException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Logical exp {}|{}|{}", le.getOperator(), le.getFirst(), + le.getSecond()); + } + return (le.getOperator() == LogicalExpression.OP.AND) ? + le.getFirst().accept(this) && le.getSecond().accept(this) : + le.getFirst().accept(this) || le.getSecond().accept(this); + } + + @Override + public boolean visit(CompareExpression ce) throws QueryException { + boolean result = visitInternal(ce); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("=== Compare exp {}|{}|{} == {}", ce.getOperator(), + ce.getSelector(), ce.getArgument(), result); + } + return result; + } + + public boolean visitInternal(CompareExpression ce) throws QueryException { + String[] selector = ce.getSelector().split("\\."); + if (!rootType.getName().equals(selector[0])) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Root name mismatch: {} != {}", + rootType.getName(), selector[0]); + } + return false; + } + Object value = rootType.retrieve(object, selector, 1); + if(value == null){ // nothing to compare against + return false; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Comparing [{}] {} [{}]", ce.getArgument(), + ce.getOperator(), value.toString()); + } + if (value instanceof Collection) { + Collection collection = (Collection) value; + if(collection.size() == 0 && ce.getOperator() == OP.NE) { + // collection doesn't contain query string + return true; + } + // If there are elements iterate + Iterator it = collection.iterator(); + OP operator = ce.getOperator(); + if (operator == OP.NE) { + // negate the operator + operator = OP.EQ; + } + while (it.hasNext()) { + Object item = it.next(); + if (compare(parse(ce.getArgument(), item), item, operator)) { + // if match found check the operator and return false for NE + return (ce.getOperator() != OP.NE); + } + } + // return true for NE and false for rest + return (ce.getOperator() == OP.NE); + } else { + return compare(parse(ce.getArgument(), value), value, + ce.getOperator()); + } + } + + }); + } + + private boolean compare(Object valueToMatch, Object actualValue, OP operator) { + if (valueToMatch == null || actualValue == null) return false; + if (ALLOW_OBJECT_STRING_COMPARE && (valueToMatch instanceof String) + && !(actualValue instanceof String)) { + actualValue = actualValue.toString(); + } + + int compareResult = -1; + if (valueToMatch instanceof Comparable) { + compareResult = ((Comparable)actualValue).compareTo(valueToMatch); + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Not a comparable type: {} {}", + valueToMatch.getClass().getName(), + actualValue.getClass().getName()); + } + return false; + } + switch(operator) { + case EQ : + return compareResult == 0; + case RE : + // Regex match, + if (valueToMatch instanceof String) { + return Pattern.matches((String)valueToMatch, actualValue.toString()); + } else { + return compareResult == 0; + } + case NE: + return compareResult != 0; + case GT : + return compareResult > 0; + case GE : + return compareResult >= 0; + case LT : + return compareResult < 0; + case LE : + return compareResult <= 0; + default: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unrecognized comparator - {}", operator); + } + return false; + } + } + private Object parse(String arg, Object value) { + if (value == null) return null; + + try { + if (value instanceof String) { + return arg; + } else if (value instanceof Byte) { + return Byte.decode(arg); + } else if (value instanceof Double) { + return Double.parseDouble(arg); + } else if (value instanceof Float) { + return Float.parseFloat(arg); + } else if (value instanceof Integer) { + return Integer.parseInt(arg); + } else if (value instanceof Long) { + return Long.parseLong(arg); + } else if (value instanceof Short) { + return Short.parseShort(arg); + } + } catch (NumberFormatException ignore) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exception parsing {}", arg, value); + } + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Using string comparision for type - {}", + value.getClass().getName()); + } + // Not a number or string. Convert to a string and compare as last resort + return ALLOW_OBJECT_STRING_COMPARE ? arg.toString() : null; + } + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/TypeInfo.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/TypeInfo.java new file mode 100644 index 0000000000..91f01d8ad7 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/TypeInfo.java @@ -0,0 +1,294 @@ +package org.opendaylight.controller.northbound.commons.query; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.XmlType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A wrapper over a JAXB type to allow traversal of the object graph and + * search for specific values in the object tree. + */ +/*package*/ class TypeInfo { + + public static final Logger LOGGER = LoggerFactory.getLogger(TypeInfo.class); + public static final String DEFAULT_NAME = "##default"; + + protected final String _name; // the jaxb name + protected Class _class; // jaxb type class + protected final XmlAccessType _accessType; // jaxb access type + protected final Accessor _accessor; // accessor to access object value + protected Map _types = new HashMap(); + protected volatile boolean _explored = false; + /** + * Create a TypeInfo with a name and a class type. The accessor will be null + * for a root node. + */ + protected TypeInfo(String name, Class clz, Accessor accessor) { + _name = name; + _class = clz; + _accessor = accessor; + XmlAccessorType accessorType = null; + if(clz == null) { + throw new NullPointerException("Type class can not be null"); + } + accessorType = clz.getAnnotation(XmlAccessorType.class); + _accessType = (accessorType == null ? + XmlAccessType.PUBLIC_MEMBER : accessorType.value()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Created type info name:{} type:{}", _name, _class); + } + } + + /** + * @return the Accessor to access the value + */ + public final Accessor getAccessor() { + return _accessor; + } + + /** + * @return get the child by name + */ + public final TypeInfo getChild(String name) { + return _types.get(name); + } + + public TypeInfo getCollectionChild(Class childType) { + explore(); + for (TypeInfo ti : _types.values()) { + if (Collection.class.isAssignableFrom(ti.getType())) { + ParameterizedType p = (ParameterizedType) + ti.getAccessor().getGenericType(); + Type[] pts = p.getActualTypeArguments(); + if (pts.length == 1 && pts[0].equals(childType)) { + return ti; + } + } + } + return null; + } + + public Class getType() { + return _class; + } + + public String getName() { + return _name; + } + + /** + * @return the object value by a selector query + */ + public Object retrieve(Object target, String[] query, int index) + throws QueryException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("retrieve: {}/{} type:{}", index, query.length, target.getClass()); + } + if (index >= query.length) return null; + explore(); + if (!target.getClass().equals(_class)) { + if (_class.isAssignableFrom(target.getClass())) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Handling subtype {} of {} ", target.getClass(), _class); + } + // explore the subtype + TypeInfo subTypeInfo = new TypeInfo(getRootName(target.getClass()), + target.getClass(), _accessor); + return subTypeInfo.retrieve(target, query, index); + } else { + // non compatible object; bail out + return null; + } + } + TypeInfo child = getChild(query[index]); + if (child == null) return null; + target = child.getAccessor().getValue(target); + if (index+1 == query.length) { + // match found + return target; + } + return child.retrieve(target, query, index+1); + } + + /** + * Explore the type info for children. + */ + public synchronized void explore() { + if (_explored) return; + for (Class c = _class; c != null; c = c.getSuperclass()) { + if (c.equals(Object.class)) break; + // Currently only fields and methods annotated with JAXB annotations are + // considered as valid for search purposes. + //check methods first + for (Method m : c.getDeclaredMethods()) { + String tn = getTypeName(m, _accessType); + if (tn != null) { + if (LOGGER.isDebugEnabled()) LOGGER.debug( + "exploring type: {} name: {} method: {}", + _class.getSimpleName(), tn, m); + _types.put(tn, createTypeInfo(tn, new Accessor(m))); + } + } + for (Field f : c.getDeclaredFields()) { + String tn = getTypeName(f, _accessType); + if (tn != null) { + if (LOGGER.isDebugEnabled()) LOGGER.debug( + "exploring type: {} name: {} field: {}", + _class.getSimpleName(), tn, f); + _types.put(tn, createTypeInfo(tn, new Accessor(f))); + } + } + } + _explored = true; + } + + public static final String getTypeName(Field f, XmlAccessType access) { + // ignore static, transient and xmltransient fields + if (Modifier.isTransient(f.getModifiers()) || + Modifier.isStatic(f.getModifiers()) || + f.getAnnotation(XmlTransient.class) != null ) { + return null; + } + // try to read annotation + String name = getTypeName(f.getAnnotations(), f.getName()); + if (name != null) return name; + // no annotation present check accesstype + else if (access == XmlAccessType.NONE) { // none return name + return name; + } else if (access == XmlAccessType.FIELD) { + // return field name if no annotation present + return f.getName(); + } else if (access == XmlAccessType.PUBLIC_MEMBER + && Modifier.isPublic(f.getModifiers())) { // look for public access + return f.getName(); + } + // return annotated name ( if any ) + return null; + } + + public static final String getTypeName(Method m, XmlAccessType access) { + // ignore static, transient and xmltransient fields + if (Modifier.isStatic(m.getModifiers()) || + m.getAnnotation(XmlTransient.class) != null ) { + return null; + } + // try to read annotation + String name = getTypeName(m.getAnnotations(), m.getName()); + if (name != null) return name; + //check acces type + else if (access == XmlAccessType.NONE) { // none return name + return name; + } else if (access == XmlAccessType.PROPERTY) { + // return bean property name if no annotation present + return getBeanPropertyName(m); + } else if (access == XmlAccessType.PUBLIC_MEMBER + && Modifier.isPublic(m.getModifiers())) { // look for public access + return getBeanPropertyName(m); + } + return null; + } + + private static String getBeanPropertyName(Method m){ + try + { + Class clazz=m.getDeclaringClass(); + BeanInfo info = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (PropertyDescriptor pd : props) + { + if (m.equals(pd.getReadMethod())) return pd.getName(); + } + } + catch (IntrospectionException e) + { + LOGGER.error("Could not read bean property name for method = {}", + m.getName(), e); + } + return null; + } + + public static TypeInfo createRoot(String name, Class clz) { + // root is always a composite type + // FIXME assert its a JAXB type + XmlRootElement root = clz.getAnnotation(XmlRootElement.class); + if (root == null) throw new IllegalArgumentException("Not a JAXB type: " + clz); + if (name == null) name = getRootName(clz); + return new TypeInfo(name, clz, null); + } + + public static TypeInfo createTypeInfo(String name, Accessor accessor) { + if (accessor.getAccessibleObject().getAnnotation(XmlElementWrapper.class) != null) { + //XmlElementWrapperType + return new WrapperTypeInfo(name, accessor); + } else if (Collection.class.isAssignableFrom(accessor.getType())) { + // collection type + return new IteratableTypeInfo(name, accessor); + } + return new TypeInfo(name, accessor.getType(), accessor); + } + + public static String getRootName(Class cls) { + XmlRootElement root = cls.getAnnotation(XmlRootElement.class); + if (root == null) return null; + String rootName = root.name(); + if (DEFAULT_NAME.equals(rootName)) { + String clsName = cls.getSimpleName(); + rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1); + } + return rootName; + } + + protected static String getTypeName(Annotation[] annotations, String dflt) { + String name = null; + for (Annotation a : annotations) { + if (a.annotationType() == XmlAttribute.class) { + name = ((XmlAttribute)a).name(); + } else if (a.annotationType() == XmlElement.class) { + name = ((XmlElement)a).name(); + } else if (a.annotationType() == XmlElementRef.class) { + name = ((XmlElementRef)a).name(); + } else if (a.annotationType() == XmlElementWrapper.class) { + name = ((XmlElementWrapper)a).name(); + // break the loop as we don't want name to be overwritten by XmlElement + break; + } else if (a.annotationType() == XmlType.class) { + name = ((XmlType)a).name(); + } else if (a.annotationType() == XmlTransient.class) { + // transient type + return null; + } + } + if (DEFAULT_NAME.equals(name)) return dflt; + return name; + } + + @Override + public String toString() { + return " TypeInfo [_name=" + _name + ", _class=" + _class + + ", _accessType=" + _accessType + ", _accessor=" + _accessor + + ", _types=" + _types + ", _explored=" + _explored + " ] "; + } +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Visitor.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Visitor.java new file mode 100644 index 0000000000..0c1d2be236 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/Visitor.java @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +/*package*/ interface Visitor { + + boolean visit(LogicalExpression exp) throws QueryException; + + boolean visit(CompareExpression exp) throws QueryException; + +} diff --git a/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/WrapperTypeInfo.java b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/WrapperTypeInfo.java new file mode 100644 index 0000000000..a8172f2add --- /dev/null +++ b/opendaylight/northbound/commons/src/main/java/org/opendaylight/controller/northbound/commons/query/WrapperTypeInfo.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import javax.xml.bind.annotation.XmlElement; + +public class WrapperTypeInfo extends TypeInfo { + + protected WrapperTypeInfo(String name, Accessor accessor) { + super(name, accessor.getType(), accessor); + } + + @Override + public Object retrieve(Object target, String[] query, int index) throws QueryException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("retrieve collection: {}/{}", index, query.length); + } + if (index >= query.length) return null; + explore(); + TypeInfo child = getChild(query[index]); + if (child == null) return null; + if (query.length == index+1) { // skipping this node + return target; + }else { // if list of list go to next node to get value + return child.retrieve(target, query, index+1); + } + } + + @Override + public synchronized void explore() { + if (_explored) return; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("exploring wrapper type: {} gtype: {}", _class, + _accessor.getGenericType()); + } + String tn = null; + AccessibleObject accessibleObject = _accessor.getAccessibleObject(); + XmlElement xmlElement = accessibleObject.getAnnotation(XmlElement.class); + if (accessibleObject instanceof Field) { + Field f = (Field) accessibleObject; + tn = DEFAULT_NAME.equals(xmlElement.name())?f.getName() : xmlElement.name(); + }else if (accessibleObject instanceof Method) { + Method m = (Method) accessibleObject; + tn = DEFAULT_NAME.equals(xmlElement.name())?m.getName() : xmlElement.name(); + } + this._types.put(tn, new IteratableTypeInfo(tn, this._accessor)); + _explored = true; + } + +} diff --git a/opendaylight/northbound/commons/src/main/javacc/fiql.jj b/opendaylight/northbound/commons/src/main/javacc/fiql.jj new file mode 100644 index 0000000000..b447a32a54 --- /dev/null +++ b/opendaylight/northbound/commons/src/main/javacc/fiql.jj @@ -0,0 +1,124 @@ + +options { + STATIC = false; +} + +PARSER_BEGIN(FiqlParser) +package org.opendaylight.controller.northbound.commons.query; + +import java.util.regex.*; + +/*package*/ class FiqlParser { + public static Expression parse(String query) throws ParseException { + FiqlParser parser = new FiqlParser(new java.io.StringReader(query)); + return parser.START(); + } +} + +PARSER_END(FiqlParser) + +/* whitespace */ +SKIP : +{ + " " | "\t" +} + +TOKEN : { + <#ALPHA : ( ["a"-"z", "A"-"Z", "0"-"9"] )+ > +} + +TOKEN : { + + | + + | + +} + +/* comparision ops */ +TOKEN : { + + | + ") > + | + =") > +} + +/* ops */ +TOKEN : { + + | + +} + +/* strings */ +TOKEN : { + ", "!", "~", " "] )+ > + | + + | + +} + +/* Root production */ +Expression START() : +{ + Expression e; +} +{ + e = EXPR() + + { + return e; + } +} + +Expression EXPR(): +{ + ExpressionBuilder builder = new ExpressionBuilder(); + Expression t; +} +{ + t = TERM() { builder.withTerm(t); } + ( + ( t = TERM()) { builder.withAnd().withTerm(t); } + | + ( t = TERM() ) { builder.withOr().withTerm(t); } + )* + { + return builder.build(); + } +} + +Expression TERM() : +{ + Token selector, arg; + Expression exp; + CompareExpression.OP op; +} +{ + selector = + ( + ( {op=CompareExpression.OP.EQ;} | + {op=CompareExpression.OP.RE;} | + {op=CompareExpression.OP.NE;} | + {op=CompareExpression.OP.LT;} | + {op=CompareExpression.OP.LE;} | + {op=CompareExpression.OP.GT;} | + {op=CompareExpression.OP.GE;} + ) + ( arg = | arg = | arg = | arg = ) + ) { return new CompareExpression(op, selector.image, arg.image); } + | + ( + exp = EXPR() + ) { return exp; } +} diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/BookBean.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/BookBean.java new file mode 100644 index 0000000000..5d518f684d --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/BookBean.java @@ -0,0 +1,92 @@ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import org.opendaylight.controller.northbound.commons.types.StringList; + +/** + */ + +@XmlRootElement(name="book") +public class BookBean { + + @XmlElement(name="name") + private String _name; // simple type + + private String _isbn; // method annotation + + @XmlElement(name="author") + private PersonBean _author; // composite type + + @XmlElementWrapper//for XMLWrapper iterative composite types + @XmlElement(name="review") + private final List reviews = new ArrayList(); + + @XmlElement + private List soldBy; //Iterative Type + + @XmlElementWrapper(name="test") + @XmlElement + private final List testList = new ArrayList(); //XMLWrapper list of list + + @XmlElementWrapper(name="parent") + @XmlElement(name="child") + private final List wrapperList = new ArrayList(); // XMLWrapper of XMLWrapper + + public BookBean(){} + + public BookBean(String name, String id, PersonBean person) { + _name = name; + _isbn = id; + _author = person; + soldBy = new ArrayList(); + } + + public BookBean addReview(ReviewBean review) { + reviews.add(review); + return this; + } + + public void setSellerInfo(List sellers) { + soldBy = new ArrayList(sellers); + } + + public void addWrapperList(WrapperList list){ + wrapperList.add(list); + } + + public void addToTestList(StringList testList){ + this.testList.add(testList); + } + public String getName() { + return "1"+_name; + } + + @XmlElement(name="isbn") + public String get_isbn() { + return "pre"+_isbn; + } + + public PersonBean getauthor() { + return _author; + } + + @Override + public String toString() { + return "BookBean [_name=" + _name + ", _isbn=" + _isbn + ", _author=" + + _author + ", reviews=" + reviews + ", soldBy=" + soldBy + + ", testList=" + testList + ", wrapperList=" + wrapperList + "]"; + } + +} + +class WrapperList { + @XmlElementWrapper(name="items") + @XmlElement + public List item = new ArrayList(); +} diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ExpresssionTest.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ExpresssionTest.java new file mode 100644 index 0000000000..3e2e1538e2 --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ExpresssionTest.java @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.northbound.commons.query.CompareExpression.OP; + +public class ExpresssionTest { + + private static final List people = new ArrayList(); + private static final ArrayList books = new ArrayList(); + + public static void p(String msg) { + //System.out.println("======= " + msg); + } + + public static boolean matches(Expression exp, final PersonBean person) throws Exception { + + boolean result = exp.accept(new Visitor() { + @Override + public boolean visit(LogicalExpression le) throws QueryException { + p("=== LE " + le.getOperator() + "|" + le.getFirst() + "|" + le.getSecond()); + return (le.getOperator() == LogicalExpression.OP.AND) ? + le.getFirst().accept(this) && le.getSecond().accept(this) : + le.getFirst().accept(this) || le.getSecond().accept(this); + } + + @Override + public boolean visit(CompareExpression ce) { + p("=== CE " + ce.getOperator() + "|" + ce.getSelector() + "|" + ce.getArgument()); + if (person == null) { + return false; + } + try { + // check if the selector matches any of the fields + Field field = PersonBean.class.getDeclaredField(ce.getSelector()); + if (field == null) { + p("No field found by name : " + ce.getSelector()); + return false; + } + Object value = field.get(person); + if (value instanceof String) { + p("Comparing [" + ce.getArgument() + "] "+ ce.getOperator() + " [" + value.toString() + "]"); + if (ce.getOperator() == OP.EQ) { + return ce.getArgument().equals(value.toString()); + } else if (ce.getOperator() == OP.RE) { + return Pattern.matches(ce.getArgument(), value.toString()); + } else if (ce.getOperator() == OP.NE) { + return !ce.getArgument().equals(value.toString()); + } else { + p("Comparator : " + ce.getOperator() + " cannot apply to Strings"); + return false; + } + } else { + // assume its a # + int valToMatch = Integer.parseInt(ce.getArgument()); + int actualValue = (Integer)value; + p("Comparing: " + valToMatch + " " + ce.getOperator() + " " + actualValue); + switch(ce.getOperator()) { + case EQ : + case RE : + return actualValue == valToMatch; + case NE : + return actualValue != valToMatch; + case GT : + return actualValue > valToMatch; + case GE : + return actualValue >= valToMatch; + case LT : + return actualValue < valToMatch; + case LE : + return actualValue <= valToMatch; + default: + p("Unrecognized compare operator: " + ce.getOperator()); + return false; + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + }); + p("RESULT: " + result); + return result; + } + + @BeforeClass + public static void load() { + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug"); + + people.add(new PersonBean(100, "John", "Doe", "San Jose")); + people.add(new PersonBean(200, "Foo", "Bar", "San Francisco")); + people.add(new PersonBean(300, "A", "B", "San Francisco")); + people.add(new PersonBean(400, "X", "Y", "New York")); + + books.add(new BookBean("Book1", "A001", people.get(0))); + books.add(new BookBean("Book2", "A002", people.get(1))); + books.add(new BookBean("Book3", "A003", people.get(2))); + + ReviewBean review1 = new ReviewBean("cool", people.get(2)); + ReviewBean review2 = new ReviewBean("kewl", people.get(3)); + ReviewBean review3 = new ReviewBean("+++", people.get(0)); + ReviewBean review4 = new ReviewBean("---", people.get(1)); + + books.get(0).addReview(review1).addReview(review2).addReview(review3).addReview(review4); + books.get(1).addReview(review1).addReview(review2).addReview(review3).addReview(review4); + books.get(2).addReview(review1).addReview(review2).addReview(review3).addReview(review4); + } + + @Test + public void testCXFQueries() throws Exception { + // following queries copied from apache cxf + Assert.assertFalse(matches(parseQuery("id=gt=100;name=Fred"), null)); + Assert.assertFalse(matches(parseQuery("id=gt=100;name==Fred"), null)); + Assert.assertFalse(matches(parseQuery("id=lt=123"), null)); + Assert.assertFalse(matches(parseQuery("date=le=2010-03-11"), null)); + Assert.assertFalse(matches(parseQuery("time=le=2010-03-11T18:00:00"), null)); + Assert.assertFalse(matches(parseQuery("name==CXF;version=ge=2.2"), null)); + Assert.assertFalse(matches(parseQuery("(age=lt=25,age=gt=35);city==London"), null)); + Assert.assertFalse(matches(parseQuery("date=lt=2000-01-01;date=gt=1999-01-01;(sub==math,sub==physics)"), null)); + } + + public Expression parseQuery(String query) throws Exception { + p("PARSING query: " + query); + // FiqlParser is a parser generated by javacc + Expression exp = FiqlParser.parse(query); + p(exp.toString()); + return exp; + } + + public int find(String query) throws Exception { + int found = 0; + Expression exp = parseQuery(query); + TypeInfo.createRoot("person", PersonBean.class); + for (PersonBean person : people) { + if (matches(exp, person)) found++; + } + return found; + } + + @Test + public void testPeopleQueries() throws Exception { + Assert.assertTrue(find("id==200") == 1); + Assert.assertTrue(find("id!=100;(city='San.*')") == 2); + Assert.assertTrue(find("id>200;(city='San.*')") == 1); + Assert.assertTrue(find("city='San.*'") == 3); + } + + @Test + public void testTypeTree() throws Exception { + TypeInfo bookType = TypeInfo.createRoot("book", BookBean.class); + Assert.assertEquals("John", bookType.retrieve(books.get(0), + "book.author.firstName".split("\\."), 1)); + Object result = bookType.retrieve(books.get(0), + "book.reviews.review.comment".split("\\."), 1); + Assert.assertTrue( result instanceof List); + List commentList = (List) result; + Assert.assertTrue(commentList.contains("cool")); + } + + @Test + public void testQueryAPI() throws Exception { + QueryContext qc = new QueryContextImpl(); + + // find all books written by author with firstName "John" + Query q1 = qc.createQuery("book.author.firstName==John", BookBean.class); + Collection r1 = q1.find(books); + p("Filtered books: " + r1.size()); + Assert.assertEquals(1, r1.size()); + + // find all books reviewed by people in a city "San*" + Query q2 = qc.createQuery("book.reviews.review.reviewer.city=San.*", BookBean.class); + Collection r2 = q2.find(books); + + p("Filtered books: " + r2.size()); + Assert.assertEquals(3, r2.size()); + + // find all books reviewed by people in a city "San*" + Query q3 = qc.createQuery("book==foo", BookBean.class); + Collection r3 = q3.find(books); + Assert.assertEquals(0, r3.size()); + } + + @Test + public void testFilter() throws Exception { + Library library = new Library((List)books.clone()); + QueryContext qc = new QueryContextImpl(); + // find all books written by author with firstName "John" + Query q1 = qc.createQuery("book.author.firstName==John", Library.class); + int sizeBefore = library.getList().size(); + System.out.println(q1.filter(library, BookBean.class)); + Assert.assertEquals(1, library.getList().size()); + } +} diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/Library.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/Library.java new file mode 100644 index 0000000000..c54b0e719e --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/Library.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name="library") +public class Library { + + @XmlElement(name="book") + private final List _list; + + public Library(List list) { + _list = list; + } + + public List getList() { + return _list; + } + +} diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/PersonBean.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/PersonBean.java new file mode 100644 index 0000000000..b4b9ed5599 --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/PersonBean.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name="person") + +public class PersonBean { + + @XmlElement + public String firstName; + @XmlElement + public String lastName; + @XmlElement + public String city; + @XmlElement + public int id; + + @XmlElementWrapper(name="emails") // ElementWrapper iteratable type + @XmlElement + public List email; + + public PersonBean(){} + public PersonBean(int n, String f, String l, String c) { + firstName = f; + lastName = l; + city = c; + id = n; + } + + public void setEmail(List emails){ + email = emails; + } + @Override + public String toString() { + return "PersonBean [firstName=" + firstName + ", lastName=" + lastName + + ", city=" + city + ", id=" + id + "]"; + } + +} diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/QueryContextTest.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/QueryContextTest.java new file mode 100644 index 0000000000..b6e582ba50 --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/QueryContextTest.java @@ -0,0 +1,269 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.northbound.commons.query; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.northbound.commons.types.StringList; + +public class QueryContextTest { + + protected static final List people = new ArrayList(); + protected static final List books = new ArrayList(); + + public static void p(String msg) { + System.out.println("=== " + msg); + } + + @BeforeClass + public static void load() { + people.add(new PersonBean(100, "John", "Doe", "San Jose")); + people.add(new PersonBean(200, "Foo", "Bar", "San Francisco")); + people.add(new PersonBean(300, "A", "B", "San Francisco")); + people.add(new PersonBean(400, "X", "Y", "New York")); + + books.add(new BookBean("Book1", "A001", people.get(0))); + books.add(new BookBean("Book2", "A002", people.get(1))); + books.add(new BookBean("Book3", "A003", people.get(2))); + + ReviewBean review1 = new ReviewBean("cool", people.get(2)); + ReviewBean review2 = new ReviewBean("kewl", people.get(3)); + + books.get(0).addReview(review1).addReview(review2); + books.get(1).addReview(review1); + books.get(2).addReview(review2).addReview(review1); + + } + + @Test + public void testQueryContext() { + QueryContext queryContext = new QueryContextImpl(); + Assert.assertNotNull(queryContext); + } + + @Test + public void testSimpleQuery() throws QueryException { + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "person.id==200", PersonBean.class); + Assert.assertNotNull(query); + + List found = query.find(people); + Assert.assertNotNull(found); + Assert.assertTrue(found.size() == 1); + Assert.assertEquals("Foo", found.get(0).firstName); + } + + @Test + public void testAndQuery() throws QueryException { + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "person.id!=200;(person.city='San.*')", PersonBean.class); + Assert.assertNotNull(query); + + List found = query.find(people); + Assert.assertNotNull(found); + Assert.assertTrue(found.size() == 2); + Assert.assertEquals("John", found.get(0).firstName); + Assert.assertEquals("A", found.get(1).firstName); + } + + @Test + public void testOrQuery() throws QueryException { + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "person.id==200,(person.city='San.*')", PersonBean.class); + Assert.assertNotNull(query); + + List found = query.find(people); + Assert.assertNotNull(found); + Assert.assertTrue(found.size() == 3); + Assert.assertEquals("John", found.get(0).firstName); + Assert.assertEquals("Foo", found.get(1).firstName); + Assert.assertEquals("A", found.get(2).firstName); + } + + @Test + public void testXmlElementWrapper() throws QueryException { + List emails = new ArrayList(); + emails.add("john@cisco.com"); + emails.add("john@gmail.com"); + people.get(0).setEmail(emails); + + p(toXml(people.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "person.emails.email==john@cisco.com", PersonBean.class); + Assert.assertNotNull(query); + + List found = query.find(people); + Assert.assertNotNull(found); + Assert.assertEquals(1,found.size()); + Assert.assertEquals("John", found.get(0).firstName); + } + + @Test + public void testXmlWrapperOfWrapper() throws QueryException{ + WrapperList wrapper = new WrapperList(); + wrapper.item.add("Test1"); + wrapper.item.add("Test2"); + + books.get(0).addWrapperList(wrapper); + books.get(1).addWrapperList(wrapper); + + System.out.println(toXml(books.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "book.parent.child.items.item==Test1", BookBean.class); + Assert.assertNotNull(query); + } + + @Test + public void testXmlElementWrapperListofList() throws QueryException { + // create Stringlist + List testList = new ArrayList(); + testList.add("A"); + testList.add("B"); + StringList itemList = new StringList(testList); + books.get(0).addToTestList(itemList); + + System.out.println(toXml(books.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "book.test.testList.item==A", BookBean.class); + Assert.assertNotNull(query); + } + + @Test + public void testPrimitiveIteratableTypes() throws QueryException { + // Load data for this test + List sellers = new ArrayList(); + sellers.add("Amazon"); + + books.get(0).setSellerInfo(sellers); + sellers.add("Barners & Nobles"); + books.get(1).setSellerInfo(sellers); + sellers.add("Borders"); + sellers.remove("Amazon"); + sellers.add("BookShop"); + books.get(2).setSellerInfo(sellers); + + System.out.println(toXml(books.get(0))); + + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "book.soldBy==Amazon", BookBean.class); + Assert.assertNotNull(query); + + List found = query.find(books); + Assert.assertNotNull(found); + Assert.assertEquals(2,found.size()); + Assert.assertEquals("John", found.get(0).getauthor().firstName); + + query = queryContext.createQuery( + "book.soldBy!=Amazon", BookBean.class); + Assert.assertNotNull(query); + + found = query.find(books); + System.out.println("books" +found); + Assert.assertNotNull(found); + Assert.assertEquals(1,found.size()); + Assert.assertEquals("A", found.get(0).getauthor().firstName); + } + + @Test + public void testCompositeIteratableTypes() throws QueryException { + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery("book.reviews.review.reviewer.firstName==X", + BookBean.class); + Assert.assertNotNull(query); + + List found = query.find(books); + Assert.assertNotNull(found); + Assert.assertEquals(2, found.size()); + Assert.assertEquals("John", found.get(0).getauthor().firstName); + + query = queryContext.createQuery("book.reviews.review.comment==kewl", + BookBean.class); + Assert.assertNotNull(query); + + found = query.find(books); + Assert.assertNotNull(found); + Assert.assertEquals(2, found.size()); + p("Book 0" + found.get(0)); + Assert.assertEquals("John", found.get(0).getauthor().firstName); + + query = queryContext.createQuery("book.reviews.review.reviewer.id>300", + BookBean.class); + Assert.assertNotNull(query); + + found = query.find(books); + Assert.assertNotNull(found); + Assert.assertEquals(2, found.size()); + p("Book 0" + found.get(0)); + Assert.assertEquals("John", found.get(0).getauthor().firstName); + + query = queryContext.createQuery("book.reviews.review.reviewer.firstName!=X", + BookBean.class); + Assert.assertNotNull(query); + + found = query.find(books); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + p("Book 0" + found.get(0)); + Assert.assertEquals("Foo", found.get(0).getauthor().firstName); + } + + @Test + public void testXMLAccessorType() { + //Assert.fail("implement"); + } + + @Test + public void testMethodAnnotation() throws QueryException { + System.out.println(toXml(books.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Query query = queryContext.createQuery( + "book.isbn==preA003", BookBean.class); + Assert.assertNotNull(query); + + List found = query.find(books); + Assert.assertNotNull(found); + Assert.assertEquals(1,found.size()); + Assert.assertEquals("A", found.get(0).getauthor().firstName); + } + + public static String toXml(Object element) { + try { + JAXBContext jc = JAXBContext.newInstance(element.getClass()); + Marshaller marshaller = jc.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + marshaller.marshal(element, baos); + return baos.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + @Test + public void testXMLElementWrapperForCompositeTypes(){ + //Assert.fail("implement"); + } + +} \ No newline at end of file diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ReviewBean.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ReviewBean.java new file mode 100644 index 0000000000..ea2f873ff6 --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/ReviewBean.java @@ -0,0 +1,41 @@ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.Date; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + */ +@XmlRootElement(name="review") +public class ReviewBean { + @XmlElement(name="date") + private Date _publishedDate; + @XmlElement(name="comment") + private String _comment; + @XmlElement(name="reviewer") + private PersonBean _reviewer; + @XmlElement + private int _upVotes; + @XmlElement + private int _downVotes; + public ReviewBean(){} + + public ReviewBean(String comment, PersonBean user) { + _comment = comment; + _reviewer = user; + _publishedDate = new Date(); + } + + public void vote(int up, int down) { + _upVotes += up; + _downVotes += down; + } + + @Override + public String toString() { + return "ReviewBean " + _publishedDate + " " + + _comment + " " + _reviewer + " " + _upVotes + + " " + _downVotes + ""; + } +} diff --git a/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/XMLAccessorTypeTest.java b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/XMLAccessorTypeTest.java new file mode 100644 index 0000000000..25cb69214e --- /dev/null +++ b/opendaylight/northbound/commons/src/test/java/org/opendaylight/controller/northbound/commons/query/XMLAccessorTypeTest.java @@ -0,0 +1,374 @@ +package org.opendaylight.controller.northbound.commons.query; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + +import org.junit.Assert; +import org.junit.Test; + +public class XMLAccessorTypeTest { + + @Test + public void testPublicAccessType() throws Exception { + // create bean + List testList = new ArrayList(); + testList.add(new PublicAccessBean("John", "Scott", "private", 1, + "transient", "elem1")); + testList.add(new PublicAccessBean("Foo", "Bar", "private1", 2, + "transient1", "elem2")); + QueryContextTest.p(QueryContextTest.toXml(testList.get(0))); + + QueryContext queryContext = new QueryContextImpl(); + Assert.assertNotNull(queryContext); + // search for public field + Query query = queryContext.createQuery( + "publicbean.firstName==Foo", PublicAccessBean.class); + Assert.assertNotNull(query); + + List found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("Foo", found.get(0).firstName); + + // search for public getter + query = queryContext.createQuery("publicbean.privateGetterField<2", + PublicAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).firstName); + + // test for transient field + query = queryContext.createQuery("publicbean.transientField='trans*'", + PublicAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(0, found.size()); + + // test for private field + query = queryContext.createQuery("publicbean.privateField==private", + PublicAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(0, found.size()); + + // test for XML Element + query = queryContext.createQuery("publicbean.element==elem1", + PublicAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).firstName); + } + + @Test + public void testFieldAccessType() throws QueryException { + // create bean + List testList = new ArrayList(); + testList.add(new FieldAccessBean("John", "Scott", "private", 1, "elem1")); + testList.add(new FieldAccessBean("Foo", "Bar", "private1", 2, "elem2")); + + QueryContextTest.p(QueryContextTest.toXml(testList.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Assert.assertNotNull(queryContext); + // test private field + Query query = queryContext.createQuery( + "field.privateField==private", FieldAccessBean.class); + Assert.assertNotNull(query); + + List found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).firstName); + + // test public field + query = queryContext.createQuery("field.firstName==Foo", + FieldAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("Foo", found.get(0).firstName); + + // test annotated field + query = queryContext.createQuery("field.element==elem2", + FieldAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("Foo", found.get(0).firstName); + + // test annotated method + query = queryContext.createQuery("field.privateGetterField==11", + FieldAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).firstName); + } + + @Test + public void testPropertyAccessType() throws QueryException { + // create bean + List testList = new ArrayList(); + testList.add(new PropertyAccessBean("John", "Scott", "private", 1, "elem1", + "transient1")); + testList.add(new PropertyAccessBean("Foo", "Bar", "private1", 2, "elem2", + "transient2")); + + QueryContextTest.p(QueryContextTest.toXml(testList.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Assert.assertNotNull(queryContext); + // test public getter public field + Query query = queryContext.createQuery( + "property.firstName==John", PropertyAccessBean.class); + Assert.assertNotNull(query); + + List found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).firstName); + + // test public field no getter + query = queryContext.createQuery("property.lastName==Bar", + PropertyAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(0, found.size()); + + // test annotated field + query = queryContext.createQuery("property.element==elem2", + PropertyAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("Foo", found.get(0).firstName); + + // test annotated method + query = queryContext.createQuery("property.field==private", + PropertyAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).firstName); + + // test transient method + query = queryContext.createQuery("property.transientField==transient1", + PropertyAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(0, found.size()); + } + + @Test + public void testNoneAccessType() throws QueryException { + // create bean + List testList = new ArrayList(); + testList.add(new NoneAccessBean("John", "Scott", "private")); + testList.add(new NoneAccessBean("Foo", "Bar", "private1")); + + QueryContextTest.p(QueryContextTest.toXml(testList.get(0))); + QueryContext queryContext = new QueryContextImpl(); + Assert.assertNotNull(queryContext); + // test annotated field + Query query = queryContext.createQuery( + "test.firstName==John", NoneAccessBean.class); + Assert.assertNotNull(query); + + List found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).getFirstName()); + // test unannotated field + query = queryContext + .createQuery("test.lastName==Bar", NoneAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(0, found.size()); + // test annotated method + query = queryContext.createQuery("test.testField==private", + NoneAccessBean.class); + Assert.assertNotNull(query); + + found = query.find(testList); + Assert.assertNotNull(found); + Assert.assertEquals(1, found.size()); + Assert.assertEquals("John", found.get(0).getFirstName()); + + } + +} + +// default ( public memeber ) +@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER) +@XmlRootElement(name = "publicbean") +class PublicAccessBean { + + public String firstName; + public String lastName; + private String privateField; + private int privateGetterField; + @XmlTransient + public String transientField; + @XmlElement(name = "element") + private String xmlElem; + + public PublicAccessBean() { + } + + public PublicAccessBean(String firstName, String lastName, + String privateField, int privateGetterField, String transientField, + String xmlElem) { + this.firstName = firstName; + this.lastName = lastName; + this.privateField = privateField; + this.privateGetterField = privateGetterField; + this.transientField = transientField; + this.xmlElem = xmlElem; + } + + public int getPrivateGetterField() { + return privateGetterField; + } + + public void setPrivateGetterField(int field) { + this.privateGetterField = field; + } +} + +// default ( public memeber ) +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "field") +class FieldAccessBean { + + public String firstName; + public String lastName; + private String privateField; + private int test; + @XmlElement(name = "element") + private String xmlElem; + + public FieldAccessBean() { + } + + public FieldAccessBean(String firstName, String lastName, + String privateField, int privateGetterField, String xmlElem) { + this.firstName = firstName; + this.lastName = lastName; + this.privateField = privateField; + this.xmlElem = xmlElem; + this.test = privateGetterField; + } + + public String getPrivateField() { + return privateField; + } + + @XmlElement(name = "privateGetterField") + public int getPrivateGetterField() { + return test + 10; + } +} + +// default ( public memeber ) +@XmlAccessorType(XmlAccessType.PROPERTY) +@XmlRootElement(name = "property") +class PropertyAccessBean { + + public String firstName; + public String lastName; + private String privateField; + private int privateGetterField; + @XmlElement(name = "element") + private String xmlElem; + private String transientField; + + public PropertyAccessBean() { + } + + public PropertyAccessBean(String firstName, String lastName, + String privateField, int privateGetterField, String xmlElem, + String transientField) { + this.firstName = firstName; + this.lastName = lastName; + this.privateField = privateField; + this.privateGetterField = privateGetterField; + this.xmlElem = xmlElem; + this.transientField = transientField; + } + + public int getPrivateGetterField() { + return privateGetterField; + } + + @XmlElement(name = "field") + public String getPrivateField() { + return privateField; + } + + public String getFirstName() { + return firstName; + } + + @XmlTransient + public String getTransientField() { + return transientField; + } +} + +// default ( public memeber ) +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = "test") +class NoneAccessBean { + @XmlElement + private String firstName; + public String lastName; + private String testField; + + public NoneAccessBean() { + } + + public NoneAccessBean(String firstName, String lastName, String testField) { + this.firstName = firstName; + this.lastName = lastName; + this.testField = testField; + } + + @XmlElement(name = "testField") + public String getTestField() { + return testField; + } + + public String getFirstName() { + return firstName; + } +} diff --git a/opendaylight/northbound/commons/src/test/resources/logback.xml b/opendaylight/northbound/commons/src/test/resources/logback.xml new file mode 100644 index 0000000000..97e15deb94 --- /dev/null +++ b/opendaylight/northbound/commons/src/test/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/opendaylight/northbound/connectionmanager/pom.xml b/opendaylight/northbound/connectionmanager/pom.xml index a0940e3428..ad315ac008 100644 --- a/opendaylight/northbound/connectionmanager/pom.xml +++ b/opendaylight/northbound/connectionmanager/pom.xml @@ -54,11 +54,13 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.sal.authorization, org.opendaylight.controller.connectionmanager, org.opendaylight.controller.sal.connection, org.slf4j, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/connectionmanager/src/main/java/org/opendaylight/controller/connectionmanager/northbound/ConnectionManagerNorthbound.java b/opendaylight/northbound/connectionmanager/src/main/java/org/opendaylight/controller/connectionmanager/northbound/ConnectionManagerNorthbound.java index e2c1b32c4b..dde3210928 100644 --- a/opendaylight/northbound/connectionmanager/src/main/java/org/opendaylight/controller/connectionmanager/northbound/ConnectionManagerNorthbound.java +++ b/opendaylight/northbound/connectionmanager/src/main/java/org/opendaylight/controller/connectionmanager/northbound/ConnectionManagerNorthbound.java @@ -27,6 +27,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -36,6 +37,7 @@ import org.opendaylight.controller.northbound.commons.exception.NotAcceptableExc import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.connection.ConnectionConstants; @@ -50,7 +52,14 @@ import org.opendaylight.controller.sal.utils.Status; @Path("/") public class ConnectionManagerNorthbound { private String username; + private QueryContext queryContext; + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { if (context != null && context.getUserPrincipal() != null) username = context.getUserPrincipal().getName(); @@ -115,7 +124,8 @@ public class ConnectionManagerNorthbound { @ResponseCode(code = 406, condition = "Invalid Controller IP Address passed."), @ResponseCode(code = 503, condition = "Connection Manager Service not available")}) - public Nodes getNodes(@DefaultValue("") @QueryParam("controller") String controllerAddress) { + public Nodes getNodes(@DefaultValue("") @QueryParam("controller") String controllerAddress, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container"); } @@ -140,7 +150,12 @@ public class ConnectionManagerNorthbound { } else { nodeSet = connectionManager.getLocalNodes(); } - return new Nodes(nodeSet); + Nodes nodes = new Nodes(nodeSet); + if (queryString != null) { + queryContext.createQuery(queryString, Nodes.class) + .filter(nodes, Node.class); + } + return nodes; } /** diff --git a/opendaylight/northbound/containermanager/pom.xml b/opendaylight/northbound/containermanager/pom.xml index 457b1bd6a4..2e6bb7d40c 100644 --- a/opendaylight/northbound/containermanager/pom.xml +++ b/opendaylight/northbound/containermanager/pom.xml @@ -56,8 +56,10 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, com.sun.jersey.spi.container.servlet, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthbound.java b/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthbound.java index fe38361cca..754167814d 100644 --- a/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthbound.java +++ b/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthbound.java @@ -21,11 +21,13 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -41,6 +43,7 @@ import org.opendaylight.controller.northbound.commons.exception.ResourceConflict import org.opendaylight.controller.northbound.commons.exception.ResourceForbiddenException; import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.authorization.UserLevel; @@ -68,6 +71,14 @@ import org.opendaylight.controller.usermanager.IUserManager; @Path("/") public class ContainerManagerNorthbound { private String username; + private QueryContext queryContext; + + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { @@ -172,13 +183,18 @@ public class ContainerManagerNorthbound { @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User is not authorized to perform this operation"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) - public ContainerConfigs viewAllContainers() { + public ContainerConfigs viewAllContainers(@QueryParam("_q") String queryString) { handleNetworkAuthorization(getUserName()); IContainerManager containerManager = getContainerManager(); - - return new ContainerConfigs(containerManager.getContainerConfigList()); + ContainerConfigs result = new ContainerConfigs( + containerManager.getContainerConfigList()); + if (queryString != null) { + queryContext.createQuery(queryString, ContainerConfigs.class) + .filter(result, ContainerConfig.class); + } + return result; } /** @@ -481,7 +497,8 @@ public class ContainerManagerNorthbound { @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 404, condition = "The container is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) - public FlowSpecConfigs viewContainerFlowSpecs(@PathParam(value = "container") String container) { + public FlowSpecConfigs viewContainerFlowSpecs(@PathParam(value = "container") String container, + @QueryParam("_q") String queryString) { handleContainerAuthorization(container, getUserName()); handleForbiddenOnDefault(container); @@ -489,8 +506,13 @@ public class ContainerManagerNorthbound { handleContainerNotExists(container); IContainerManager containerManager = getContainerManager(); - - return new FlowSpecConfigs(containerManager.getContainerFlows(container)); + FlowSpecConfigs result = new FlowSpecConfigs( + containerManager.getContainerFlows(container)); + if (queryString != null) { + queryContext.createQuery(queryString, FlowSpecConfigs.class) + .filter(result, ContainerFlowConfig.class); + } + return result; } /** diff --git a/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthboundRSApplication.java b/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthboundRSApplication.java index b9d2200180..db3d543a69 100644 --- a/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthboundRSApplication.java +++ b/opendaylight/northbound/containermanager/src/main/java/org/opendaylight/controller/containermanager/northbound/ContainerManagerNorthboundRSApplication.java @@ -11,8 +11,11 @@ package org.opendaylight.controller.containermanager.northbound; import java.util.HashSet; import java.util.Set; + import javax.ws.rs.core.Application; +import org.opendaylight.controller.northbound.commons.query.QueryContextProvider; + import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; /** @@ -28,6 +31,7 @@ public class ContainerManagerNorthboundRSApplication extends Application { Set> classes = new HashSet>(); classes.add(ContainerManagerNorthbound.class); classes.add(JacksonJaxbJsonProvider.class); + classes.add(QueryContextProvider.class); return classes; } } diff --git a/opendaylight/northbound/controllermanager/pom.xml b/opendaylight/northbound/controllermanager/pom.xml index 07d34cb6d4..89d2b99cad 100644 --- a/opendaylight/northbound/controllermanager/pom.xml +++ b/opendaylight/northbound/controllermanager/pom.xml @@ -67,8 +67,10 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.sal.authorization, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/controllermanager/src/main/java/org/opendaylight/controller/controllermanager/northbound/ControllerManagerNorthbound.java b/opendaylight/northbound/controllermanager/src/main/java/org/opendaylight/controller/controllermanager/northbound/ControllerManagerNorthbound.java index 003f8b3b95..aaf93d1f4b 100644 --- a/opendaylight/northbound/controllermanager/src/main/java/org/opendaylight/controller/controllermanager/northbound/ControllerManagerNorthbound.java +++ b/opendaylight/northbound/controllermanager/src/main/java/org/opendaylight/controller/controllermanager/northbound/ControllerManagerNorthbound.java @@ -25,6 +25,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -36,6 +37,7 @@ import org.opendaylight.controller.northbound.commons.exception.BadRequestExcept import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Property; @@ -55,6 +57,14 @@ import org.opendaylight.controller.switchmanager.ISwitchManager; public class ControllerManagerNorthbound { private String username; + private QueryContext queryContext; + + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { @@ -122,7 +132,8 @@ public class ControllerManagerNorthbound { @ResponseCode(code = 404, condition = "The containerName or property is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public ControllerProperties getControllerProperties(@PathParam("containerName") String containerName, - @QueryParam("propertyName") String propertyName) { + @QueryParam("propertyName") String propertyName, + @QueryParam("_q") String queryString) { if (!isValidContainer(containerName)) { throw new ResourceNotFoundException("Container " + containerName + " does not exist."); @@ -147,8 +158,12 @@ public class ControllerManagerNorthbound { throw new ResourceNotFoundException("Unable to find property with name: " + propertyName); } properties.add(property); - - return new ControllerProperties(properties); + ControllerProperties result = new ControllerProperties(properties); + if (queryString != null) { + queryContext.createQuery(queryString, ControllerProperties.class) + .filter(result, Property.class); + } + return result; } diff --git a/opendaylight/northbound/flowprogrammer/pom.xml b/opendaylight/northbound/flowprogrammer/pom.xml index 205b68a91c..43797f5c65 100644 --- a/opendaylight/northbound/flowprogrammer/pom.xml +++ b/opendaylight/northbound/flowprogrammer/pom.xml @@ -57,11 +57,13 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.sal.authorization, org.opendaylight.controller.usermanager, com.sun.jersey.spi.container.servlet, org.apache.catalina.filters, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java index 4928ddef3b..42bd59ea45 100644 --- a/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java +++ b/opendaylight/northbound/flowprogrammer/src/main/java/org/opendaylight/controller/flowprogrammer/northbound/FlowProgrammerNorthbound.java @@ -19,10 +19,12 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -37,6 +39,7 @@ import org.opendaylight.controller.northbound.commons.exception.NotAcceptableExc import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Node; @@ -62,6 +65,14 @@ public class FlowProgrammerNorthbound { private String username; + private QueryContext queryContext; + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } + @Context public void setSecurityContext(SecurityContext context) { if (context != null && context.getUserPrincipal() != null) { @@ -194,15 +205,21 @@ public class FlowProgrammerNorthbound { @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName is not found"), - @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) - public FlowConfigs getStaticFlows(@PathParam("containerName") String containerName) { + @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable"), + @ResponseCode(code = 400, condition = "Incorrect query syntex")}) + public FlowConfigs getStaticFlows(@PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " + containerName); } - List flowConfigs = getStaticFlowsInternal(containerName, null); - return new FlowConfigs(flowConfigs); + FlowConfigs result = new FlowConfigs(getStaticFlowsInternal(containerName, null)); + if (queryString != null) { + queryContext.createQuery(queryString, FlowConfigs.class) + .filter(result, FlowConfig.class); + } + return result; } /** @@ -272,7 +289,8 @@ public class FlowProgrammerNorthbound { @ResponseCode(code = 404, condition = "The containerName or nodeId is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public FlowConfigs getStaticFlows(@PathParam("containerName") String containerName, - @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId) { + @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " + containerName); @@ -281,8 +299,12 @@ public class FlowProgrammerNorthbound { if (node == null) { throw new ResourceNotFoundException(nodeId + " : " + RestMessages.NONODE.toString()); } - List flows = getStaticFlowsInternal(containerName, node); - return new FlowConfigs(flows); + FlowConfigs flows = new FlowConfigs(getStaticFlowsInternal(containerName, node)); + if (queryString != null) { + queryContext.createQuery(queryString, FlowConfigs.class) + .filter(flows, FlowConfig.class); + } + return flows; } /** diff --git a/opendaylight/northbound/hosttracker/pom.xml b/opendaylight/northbound/hosttracker/pom.xml index bf1b082cfe..c8415f8b4f 100644 --- a/opendaylight/northbound/hosttracker/pom.xml +++ b/opendaylight/northbound/hosttracker/pom.xml @@ -60,11 +60,13 @@ com.sun.jersey.spi.container.servlet, org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.northbound.commons.utils, org.opendaylight.controller.sal.authorization, org.opendaylight.controller.sal.packet.address, javax.ws.rs, javax.ws.rs.core, + javax.ws.rs.ext, javax.xml.bind.annotation, javax.xml.bind, org.slf4j, diff --git a/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java b/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java index 74c13d11f1..d7579c82e1 100644 --- a/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java +++ b/opendaylight/northbound/hosttracker/src/main/java/org/opendaylight/controller/hosttracker/northbound/HostTrackerNorthbound.java @@ -21,11 +21,13 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -39,6 +41,7 @@ import org.opendaylight.controller.northbound.commons.exception.ResourceConflict import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Node; @@ -69,6 +72,14 @@ import org.opendaylight.controller.switchmanager.ISwitchManager; public class HostTrackerNorthbound { private String username; + private QueryContext queryContext; + + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { @@ -107,7 +118,7 @@ public class HostTrackerNorthbound { return hostTracker; } - private Hosts convertHosts(Set hostNodeConnectors) { + private Set convertHosts(Set hostNodeConnectors) { if(hostNodeConnectors == null) { return null; } @@ -115,7 +126,7 @@ public class HostTrackerNorthbound { for(HostNodeConnector hnc : hostNodeConnectors) { hosts.add(HostConfig.convert(hnc)); } - return new Hosts(hosts); + return hosts; } /** @@ -194,14 +205,20 @@ public class HostTrackerNorthbound { @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) - public Hosts getActiveHosts(@PathParam("containerName") String containerName) { + public Hosts getActiveHosts(@PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " + containerName); } IfIptoHost hostTracker = getIfIpToHostService(containerName); - return convertHosts(hostTracker.getAllHosts()); + Hosts hosts = new Hosts(convertHosts(hostTracker.getAllHosts())); + if (queryString != null) { + queryContext.createQuery(queryString, Hosts.class) + .filter(hosts, HostConfig.class); + } + return hosts; } /** @@ -281,13 +298,19 @@ public class HostTrackerNorthbound { @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public Hosts getInactiveHosts( - @PathParam("containerName") String containerName) { + @PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " + containerName); } IfIptoHost hostTracker = getIfIpToHostService(containerName); - return convertHosts(hostTracker.getInactiveStaticHosts()); + Hosts hosts = new Hosts(convertHosts(hostTracker.getInactiveStaticHosts())); + if (queryString != null) { + queryContext.createQuery(queryString, Hosts.class) + .filter(hosts, HostConfig.class); + } + return hosts; } /** diff --git a/opendaylight/northbound/staticrouting/pom.xml b/opendaylight/northbound/staticrouting/pom.xml index dd2a2e6223..83f8191e02 100644 --- a/opendaylight/northbound/staticrouting/pom.xml +++ b/opendaylight/northbound/staticrouting/pom.xml @@ -55,9 +55,11 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.sal.authorization, org.slf4j, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java b/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java index e765af524d..20f6cb40a5 100644 --- a/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java +++ b/opendaylight/northbound/staticrouting/src/main/java/org/opendaylight/controller/forwarding/staticrouting/northbound/StaticRoutingNorthbound.java @@ -19,11 +19,13 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -37,6 +39,7 @@ import org.opendaylight.controller.northbound.commons.exception.NotAcceptableExc import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException; import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.utils.GlobalConstants; @@ -74,6 +77,14 @@ import org.opendaylight.controller.sal.utils.Status; public class StaticRoutingNorthbound { private String username; + private QueryContext queryContext; + + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { @@ -148,7 +159,8 @@ public class StaticRoutingNorthbound { @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 404, condition = "The containerName passed was not found") }) public StaticRoutes getStaticRoutes( - @PathParam("containerName") String containerName) { + @PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if(!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)){ @@ -156,7 +168,12 @@ public class StaticRoutingNorthbound { UnauthorizedException("User is not authorized to perform this operation on container " + containerName); } - return new StaticRoutes(getStaticRoutesInternal(containerName)); + StaticRoutes result = new StaticRoutes(getStaticRoutesInternal(containerName)); + if (queryString != null) { + queryContext.createQuery(queryString, StaticRoutes.class) + .filter(result, StaticRoute.class); + } + return result; } /** diff --git a/opendaylight/northbound/statistics/pom.xml b/opendaylight/northbound/statistics/pom.xml index 76ce062424..7e2919bc44 100644 --- a/opendaylight/northbound/statistics/pom.xml +++ b/opendaylight/northbound/statistics/pom.xml @@ -64,7 +64,9 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java index 5338849a62..4175f1e3c4 100644 --- a/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java +++ b/opendaylight/northbound/statistics/src/main/java/org/opendaylight/controller/statistics/northbound/StatisticsNorthbound.java @@ -15,9 +15,11 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -29,6 +31,7 @@ import org.opendaylight.controller.northbound.commons.exception.ResourceConflict import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Node; @@ -57,7 +60,14 @@ import org.opendaylight.controller.switchmanager.ISwitchManager; public class StatisticsNorthbound { private String username; + private QueryContext queryContext; + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { if (context != null && context.getUserPrincipal() != null) username = context.getUserPrincipal().getName(); @@ -234,7 +244,8 @@ public class StatisticsNorthbound { @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public AllFlowStatistics getFlowStatistics( - @PathParam("containerName") String containerName) { + @PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized( getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException( @@ -265,7 +276,12 @@ public class StatisticsNorthbound { FlowStatistics stat = new FlowStatistics(node, flowStats); statistics.add(stat); } - return new AllFlowStatistics(statistics); + AllFlowStatistics result = new AllFlowStatistics(statistics); + if (queryString != null) { + queryContext.createQuery(queryString, AllFlowStatistics.class) + .filter(result, FlowStatistics.class); + } + return result; } /** @@ -610,7 +626,8 @@ public class StatisticsNorthbound { @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public AllPortStatistics getPortStatistics( - @PathParam("containerName") String containerName) { + @PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized( getUserName(), containerName, Privilege.READ, this)) { @@ -638,7 +655,13 @@ public class StatisticsNorthbound { PortStatistics portStat = new PortStatistics(node, stat); statistics.add(portStat); } - return new AllPortStatistics(statistics); + + AllPortStatistics result = new AllPortStatistics(statistics); + if (queryString != null) { + queryContext.createQuery(queryString, AllPortStatistics.class) + .filter(result, PortStatistics.class); + } + return result; } /** @@ -924,7 +947,8 @@ public class StatisticsNorthbound { @ResponseCode(code = 404, condition = "The containerName is not found"), @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) public AllTableStatistics getTableStatistics( - @PathParam("containerName") String containerName) { + @PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " @@ -952,7 +976,12 @@ public class StatisticsNorthbound { TableStatistics tableStat = new TableStatistics(node, stat); statistics.add(tableStat); } - return new AllTableStatistics(statistics); + AllTableStatistics allstats = new AllTableStatistics(statistics); + if (queryString != null) { + queryContext.createQuery(queryString, AllTableStatistics.class) + .filter(allstats, TableStatistics.class); + } + return allstats; } /** diff --git a/opendaylight/northbound/subnets/pom.xml b/opendaylight/northbound/subnets/pom.xml index 5aa2f7f202..630221fcc2 100644 --- a/opendaylight/northbound/subnets/pom.xml +++ b/opendaylight/northbound/subnets/pom.xml @@ -53,12 +53,14 @@ org.opendaylight.controller.switchmanager, org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.northbound.commons.utils, com.sun.jersey.spi.container.servlet, org.opendaylight.controller.sal.authorization, org.opendaylight.controller.usermanager, javax.ws.rs, javax.ws.rs.core, + javax.ws.rs.ext, javax.xml.bind, javax.xml.bind.annotation, org.slf4j, diff --git a/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthbound.java b/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthbound.java index 3465dc95ad..b6274795df 100644 --- a/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthbound.java +++ b/opendaylight/northbound/subnets/src/main/java/org/opendaylight/controller/subnets/northbound/SubnetsNorthbound.java @@ -19,11 +19,13 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -35,6 +37,7 @@ import org.opendaylight.controller.northbound.commons.exception.ResourceConflict import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.NodeConnector; @@ -63,6 +66,14 @@ public class SubnetsNorthbound { protected static final Logger logger = LoggerFactory.getLogger(SubnetsNorthbound.class); private String username; + private QueryContext queryContext; + + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { @@ -160,9 +171,11 @@ public class SubnetsNorthbound { @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @StatusCodes({ @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName passed was not found"), - @ResponseCode(code = 503, condition = "Service unavailable") }) + @ResponseCode(code = 503, condition = "Service unavailable"), + @ResponseCode(code = 400, condition = "Incorrect query syntex") }) @TypeHint(SubnetConfigs.class) - public SubnetConfigs listSubnets(@PathParam("containerName") String containerName) { + public SubnetConfigs listSubnets(@PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { handleContainerDoesNotExist(containerName); if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { @@ -174,7 +187,13 @@ public class SubnetsNorthbound { if (switchManager == null) { throw new ServiceUnavailableException("SwitchManager " + RestMessages.SERVICEUNAVAILABLE.toString()); } - return new SubnetConfigs(switchManager.getSubnetsConfigList()); + List subnets = switchManager.getSubnetsConfigList(); + if (queryString != null) { + subnets = queryContext.createQuery(queryString, SubnetConfig.class) + .find(subnets); + + } + return new SubnetConfigs(subnets); } /** diff --git a/opendaylight/northbound/switchmanager/pom.xml b/opendaylight/northbound/switchmanager/pom.xml index 590f0bb533..614ec88476 100644 --- a/opendaylight/northbound/switchmanager/pom.xml +++ b/opendaylight/northbound/switchmanager/pom.xml @@ -58,7 +58,9 @@ org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, org.opendaylight.controller.sal.authorization, + org.opendaylight.controller.northbound.commons.query, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind.annotation, javax.xml.bind, diff --git a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java index e30dad24ab..dab5d7d1b2 100644 --- a/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java +++ b/opendaylight/northbound/switchmanager/src/main/java/org/opendaylight/controller/switchmanager/northbound/SwitchNorthbound.java @@ -23,11 +23,13 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -38,6 +40,7 @@ import org.opendaylight.controller.northbound.commons.exception.InternalServerEr import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Node; @@ -60,6 +63,14 @@ import org.opendaylight.controller.switchmanager.SwitchConfig; public class SwitchNorthbound { private String username; + private QueryContext queryContext; + + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { @@ -200,8 +211,9 @@ public class SwitchNorthbound { @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName is not found"), - @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) - public Nodes getNodes(@PathParam("containerName") String containerName) { + @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable"), + @ResponseCode(code = 400, condition = "Incorrect query syntex") }) + public Nodes getNodes(@PathParam("containerName") String containerName, @QueryParam("_q") String queryString) { if (!isValidContainer(containerName)) { throw new ResourceNotFoundException("Container " + containerName + " does not exist."); @@ -233,8 +245,12 @@ public class SwitchNorthbound { NodeProperties nodeProps = new NodeProperties(node, props); res.add(nodeProps); } - - return new Nodes(res); + Nodes result = new Nodes(res); + if (queryString != null) { + queryContext.createQuery(queryString, Nodes.class) + .filter(result, NodeProperties.class); + } + return result; } /** @@ -564,9 +580,11 @@ public class SwitchNorthbound { @StatusCodes({ @ResponseCode(code = 200, condition = "Operation successful"), @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), @ResponseCode(code = 404, condition = "The containerName is not found"), - @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") }) + @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable"), + @ResponseCode(code = 400, condition = "Incorrect query syntex") }) public NodeConnectors getNodeConnectors(@PathParam("containerName") String containerName, - @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId) { + @PathParam("nodeType") String nodeType, @PathParam("nodeId") String nodeId, + @QueryParam("_q") String queryString) { if (!isValidContainer(containerName)) { throw new ResourceNotFoundException("Container " + containerName + " does not exist."); @@ -598,8 +616,12 @@ public class SwitchNorthbound { NodeConnectorProperties ncProps = new NodeConnectorProperties(nc, props); res.add(ncProps); } - - return new NodeConnectors(res); + NodeConnectors result = new NodeConnectors(res); + if (queryString != null) { + queryContext.createQuery(queryString, NodeConnectors.class) + .filter(result, NodeConnectorProperties.class); + } + return result; } /** diff --git a/opendaylight/northbound/topology/pom.xml b/opendaylight/northbound/topology/pom.xml index 4a1142bb18..3f1a770110 100644 --- a/opendaylight/northbound/topology/pom.xml +++ b/opendaylight/northbound/topology/pom.xml @@ -51,6 +51,7 @@ org.opendaylight.controller.northbound.commons, org.opendaylight.controller.northbound.commons.exception, org.opendaylight.controller.northbound.commons.utils, + org.opendaylight.controller.northbound.commons.query, org.opendaylight.controller.sal.core, org.opendaylight.controller.sal.packet, org.opendaylight.controller.sal.authorization, @@ -62,6 +63,7 @@ com.sun.jersey.spi.container.servlet, com.fasterxml.jackson.annotation, javax.ws.rs, + javax.ws.rs.ext, javax.ws.rs.core, javax.xml.bind, javax.xml.bind.annotation, diff --git a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java index 427aa1c1de..3773070504 100644 --- a/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java +++ b/opendaylight/northbound/topology/src/main/java/org/opendaylight/controller/topology/northbound/TopologyNorthboundJAXRS.java @@ -21,10 +21,12 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.ContextResolver; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; @@ -33,6 +35,7 @@ import org.opendaylight.controller.northbound.commons.RestMessages; import org.opendaylight.controller.northbound.commons.exception.InternalServerErrorException; import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException; import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.query.QueryContext; import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Edge; @@ -59,7 +62,14 @@ import org.opendaylight.controller.topologymanager.TopologyUserLinkConfig; public class TopologyNorthboundJAXRS { private String username; + private QueryContext queryContext; + @Context + public void setQueryContext(ContextResolver queryCtxResolver) { + if (queryCtxResolver != null) { + queryContext = queryCtxResolver.getContext(QueryContext.class); + } + } @Context public void setSecurityContext(SecurityContext context) { if (context != null && context.getUserPrincipal() != null) { @@ -240,7 +250,8 @@ public class TopologyNorthboundJAXRS { @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(Topology.class) @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name was not found") }) - public Topology getTopology(@PathParam("containerName") String containerName) { + public Topology getTopology(@PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " @@ -253,16 +264,21 @@ public class TopologyNorthboundJAXRS { } Map> topo = topologyManager.getEdges(); - if (topo != null) { - List res = new ArrayList(); - for (Map.Entry> entry : topo.entrySet()) { - EdgeProperties el = new EdgeProperties(entry.getKey(), entry.getValue()); - res.add(el); - } - return new Topology(res); + if (topo == null) { + return null; + } + List res = new ArrayList(); + for (Map.Entry> entry : topo.entrySet()) { + EdgeProperties el = new EdgeProperties(entry.getKey(), entry.getValue()); + res.add(el); } + Topology result = new Topology(res); - return null; + if (queryString != null) { + queryContext.createQuery(queryString, Topology.class) + .filter(result, EdgeProperties.class); + } + return result; } /** @@ -311,7 +327,8 @@ public class TopologyNorthboundJAXRS { @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @TypeHint(TopologyUserLinks.class) @StatusCodes({ @ResponseCode(code = 404, condition = "The Container Name was not found") }) - public TopologyUserLinks getUserLinks(@PathParam("containerName") String containerName) { + public TopologyUserLinks getUserLinks(@PathParam("containerName") String containerName, + @QueryParam("_q") String queryString) { if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) { throw new UnauthorizedException("User is not authorized to perform this operation on container " @@ -324,12 +341,16 @@ public class TopologyNorthboundJAXRS { } ConcurrentMap userLinks = topologyManager.getUserLinks(); - if ((userLinks != null) && (userLinks.values() != null)) { - List res = new ArrayList(userLinks.values()); - return new TopologyUserLinks(res); + if ((userLinks == null) || (userLinks.values() == null)) { + return null; } - - return null; + TopologyUserLinks result = new TopologyUserLinks( + new ArrayList(userLinks.values())); + if (queryString != null) { + queryContext.createQuery(queryString, TopologyUserLinks.class) + .filter(result, TopologyUserLinkConfig.class); + } + return result; } /** -- 2.36.6