You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2014/02/10 01:48:04 UTC
svn commit: r1566482 - in /cxf/trunk: parent/ rt/rs/extensions/search/
rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/
rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/
rt/rs/extensions/search/src/main/j...
Author: reta
Date: Mon Feb 10 00:48:04 2014
New Revision: 1566482
URL: http://svn.apache.org/r1566482
Log:
[CXF-5430]: Added initial support for OData 2.0 query language. Covered basic use cases.
Added:
cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java
cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/odata/
cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/odata/OData2Parser.java
cxf/trunk/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/
cxf/trunk/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/OData2ParserTest.java
Modified:
cxf/trunk/parent/pom.xml
cxf/trunk/rt/rs/extensions/search/pom.xml
cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
Modified: cxf/trunk/parent/pom.xml
URL: http://svn.apache.org/viewvc/cxf/trunk/parent/pom.xml?rev=1566482&r1=1566481&r2=1566482&view=diff
==============================================================================
--- cxf/trunk/parent/pom.xml (original)
+++ cxf/trunk/parent/pom.xml Mon Feb 10 00:48:04 2014
@@ -178,6 +178,7 @@
<cxf.oro.bundle.version>2.0.8_6</cxf.oro.bundle.version>
<cxf.dom4j.bundle.version>1.6.1_5</cxf.dom4j.bundle.version>
<cxf.jdom.bundle.version>1.1_4</cxf.jdom.bundle.version>
+ <cxf.olingo.version>1.0.0</cxf.olingo.version>
<cxf.checkstyle.extension />
<cxf.jaxb.context.class />
<cxf.spring.validation.mode>VALIDATION_AUTO</cxf.spring.validation.mode>
@@ -1085,6 +1086,11 @@
<version>${cxf.spring.security.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.olingo</groupId>
+ <artifactId>olingo-odata2-core-incubating</artifactId>
+ <version>${cxf.olingo.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${cxf.spring.security.version}</version>
Modified: cxf/trunk/rt/rs/extensions/search/pom.xml
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/rs/extensions/search/pom.xml?rev=1566482&r1=1566481&r2=1566482&view=diff
==============================================================================
--- cxf/trunk/rt/rs/extensions/search/pom.xml (original)
+++ cxf/trunk/rt/rs/extensions/search/pom.xml Mon Feb 10 00:48:04 2014
@@ -67,6 +67,11 @@
<optional>true</optional>
</dependency>
<dependency>
+ <groupId>org.apache.olingo</groupId>
+ <artifactId>olingo-odata2-core-incubating</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${cxf.lucene.version}</version>
Added: cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java?rev=1566482&view=auto
==============================================================================
--- cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java (added)
+++ cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/AbstractSearchConditionParser.java Mon Feb 10 00:48:04 2014
@@ -0,0 +1,251 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+
+import org.apache.cxf.jaxrs.ext.search.Beanspector.TypeInfo;
+import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheck;
+import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckInfo;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+
+public abstract class AbstractSearchConditionParser<T> implements SearchConditionParser<T> {
+ public static final String EXTENSION_COUNT = "count";
+ protected static final String EXTENSION_COUNT_OPEN = EXTENSION_COUNT + "(";
+
+ protected final Map<String, String> contextProperties;
+ protected final Class<T> conditionClass;
+
+ protected AbstractSearchConditionParser(Class<T> tclass) {
+ this(tclass, Collections.<String, String>emptyMap());
+ }
+
+ protected AbstractSearchConditionParser(Class<T> tclass, Map<String, String> contextProperties) {
+ this.conditionClass = tclass;
+ this.contextProperties = contextProperties == null
+ ? Collections.<String, String>emptyMap() : contextProperties;
+ }
+
+ protected String getSetter(String setter) {
+ int index = getDotIndex(setter);
+ if (index != -1) {
+ return setter.substring(0, index).toLowerCase();
+ } else {
+ return setter;
+ }
+ }
+
+ protected Object parseType(String originalPropName,
+ Object ownerBean,
+ Object lastCastedValue,
+ String setter,
+ TypeInfo typeInfo,
+ String value) throws SearchParseException {
+ Class<?> valueType = typeInfo.getTypeClass();
+ boolean isCollection = InjectionUtils.isSupportedCollectionOrArray(valueType);
+ Class<?> actualType = isCollection ? InjectionUtils.getActualType(typeInfo.getGenericType()) : valueType;
+
+ int index = getDotIndex(setter);
+ if (index == -1) {
+ Object castedValue = value;
+ if (Date.class.isAssignableFrom(valueType)) {
+ castedValue = convertToDate(value);
+ } else {
+ boolean isPrimitive = InjectionUtils.isPrimitive(valueType);
+ boolean isPrimitiveOrEnum = isPrimitive || valueType.isEnum();
+ if (ownerBean == null || isPrimitiveOrEnum) {
+ try {
+ CollectionCheck collCheck = getCollectionCheck(originalPropName, isCollection, actualType);
+ if (collCheck == null) {
+ castedValue = InjectionUtils.convertStringToPrimitive(value, actualType);
+ }
+ if (collCheck == null && isCollection) {
+ castedValue = getCollectionSingleton(valueType, castedValue);
+ } else if (isCollection) {
+ typeInfo.setCollectionCheckInfo(new CollectionCheckInfo(collCheck, castedValue));
+ castedValue = getEmptyCollection(valueType);
+ }
+ } catch (Exception e) {
+ throw new SearchParseException("Cannot convert String value \"" + value
+ + "\" to a value of class " + valueType.getName(), e);
+ }
+ } else {
+ Class<?> classType = isCollection ? valueType : value.getClass();
+ try {
+ Method setterM = valueType.getMethod("set" + getMethodNameSuffix(setter),
+ new Class[]{classType});
+ Object objectValue = !isCollection ? value : getCollectionSingleton(valueType, value);
+ setterM.invoke(ownerBean, new Object[]{objectValue});
+ castedValue = objectValue;
+ } catch (Throwable ex) {
+ throw new SearchParseException("Cannot convert String value \"" + value
+ + "\" to a value of class " + valueType.getName(), ex);
+ }
+
+ }
+ }
+ if (lastCastedValue != null) {
+ castedValue = lastCastedValue;
+ }
+ return castedValue;
+ } else {
+ String[] names = setter.split("\\.");
+ try {
+ String nextPart = getMethodNameSuffix(names[1]);
+ Method getterM = actualType.getMethod("get" + nextPart, new Class[]{});
+ Class<?> returnType = getterM.getReturnType();
+ boolean returnCollection = InjectionUtils.isSupportedCollectionOrArray(returnType);
+ Class<?> actualReturnType = !returnCollection ? returnType
+ : InjectionUtils.getActualType(getterM.getGenericReturnType());
+
+ boolean isPrimitive = InjectionUtils.isPrimitive(returnType) || returnType.isEnum();
+ boolean lastTry = names.length == 2
+ && (isPrimitive || returnType == Date.class || returnCollection);
+
+ Object valueObject = ownerBean != null ? ownerBean
+ : actualType.isInterface()
+ ? Proxy.newProxyInstance(this.getClass().getClassLoader(),
+ new Class[]{actualType},
+ new InterfaceProxy())
+ : actualType.newInstance();
+ Object nextObject;
+
+ if (lastTry) {
+ if (!returnCollection) {
+ nextObject = isPrimitive ? InjectionUtils.convertStringToPrimitive(value, returnType)
+ : convertToDate(value);
+ } else {
+ CollectionCheck collCheck = getCollectionCheck(originalPropName, true, actualReturnType);
+ if (collCheck == null) {
+ nextObject = getCollectionSingleton(valueType, value);
+ } else {
+ typeInfo.setCollectionCheckInfo(new CollectionCheckInfo(collCheck, value));
+ nextObject = getEmptyCollection(valueType);
+ }
+ }
+ } else {
+ nextObject = returnType.newInstance();
+ }
+
+ Method setterM = actualType.getMethod("set" + nextPart, new Class[]{returnType});
+ setterM.invoke(valueObject, new Object[]{nextObject});
+
+ if (lastTry) {
+ lastCastedValue = lastCastedValue == null ? valueObject : lastCastedValue;
+ return isCollection ? getCollectionSingleton(valueType, lastCastedValue) : lastCastedValue;
+ } else {
+ lastCastedValue = valueObject;
+ }
+
+ TypeInfo nextTypeInfo = new TypeInfo(nextObject.getClass(), getterM.getGenericReturnType());
+ Object response = parseType(originalPropName,
+ nextObject,
+ lastCastedValue,
+ setter.substring(index + 1),
+ nextTypeInfo,
+ value);
+ if (ownerBean == null) {
+ return isCollection ? getCollectionSingleton(valueType, lastCastedValue) : lastCastedValue;
+ } else {
+ return response;
+ }
+ } catch (Throwable e) {
+ throw new SearchParseException("Cannot convert String value \"" + value
+ + "\" to a value of class " + valueType.getName(), e);
+ }
+ }
+ }
+
+ private CollectionCheck getCollectionCheck(String propName, boolean isCollection, Class<?> actualCls) {
+ if (isCollection) {
+ if (InjectionUtils.isPrimitive(actualCls)) {
+ if (propName.startsWith(EXTENSION_COUNT_OPEN)) {
+ return CollectionCheck.SIZE;
+ }
+ } else {
+ return CollectionCheck.SIZE;
+ }
+ }
+ return null;
+ }
+
+ private Object getCollectionSingleton(Class<?> collectionCls, Object value) {
+ if (Set.class.isAssignableFrom(collectionCls)) {
+ return Collections.singleton(value);
+ } else {
+ return Collections.singletonList(value);
+ }
+ }
+
+ private Object getEmptyCollection(Class<?> collectionCls) {
+ if (Set.class.isAssignableFrom(collectionCls)) {
+ return Collections.emptySet();
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ private Object convertToDate(String value) throws SearchParseException {
+ try {
+ DateFormat df = SearchUtils.getDateFormat(contextProperties);
+ String dateValue = value;
+ if (SearchUtils.isTimeZoneSupported(contextProperties, Boolean.FALSE)) {
+ // zone in XML is "+01:00" in Java is "+0100"; stripping semicolon
+ int idx = value.lastIndexOf(':');
+ if (idx != -1) {
+ dateValue = value.substring(0, idx) + value.substring(idx + 1);
+ }
+ }
+ return df.parse(dateValue);
+ } catch (ParseException e) {
+ // is that duration?
+ try {
+ Date now = new Date();
+ DatatypeFactory.newInstance().newDuration(value).addTo(now);
+ return now;
+ } catch (DatatypeConfigurationException e1) {
+ throw new SearchParseException(e1);
+ } catch (IllegalArgumentException e1) {
+ throw new SearchParseException("Can parse " + value + " neither as date nor duration", e);
+ }
+ }
+ }
+
+ private String getMethodNameSuffix(String name) {
+ if (name.length() == 1) {
+ return name.toUpperCase();
+ } else {
+ return Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ }
+ }
+
+ private int getDotIndex(String setter) {
+ return this.conditionClass == SearchBean.class ? -1 : setter.indexOf(".");
+ }
+}
Modified: cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java?rev=1566482&r1=1566481&r2=1566482&view=diff
==============================================================================
--- cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java (original)
+++ cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java Mon Feb 10 00:48:04 2014
@@ -17,39 +17,26 @@
* under the License.
*/
package org.apache.cxf.jaxrs.ext.search.fiql;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.text.DateFormat;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeFactory;
-
+import org.apache.cxf.jaxrs.ext.search.AbstractSearchConditionParser;
import org.apache.cxf.jaxrs.ext.search.AndSearchCondition;
import org.apache.cxf.jaxrs.ext.search.Beanspector;
import org.apache.cxf.jaxrs.ext.search.Beanspector.TypeInfo;
import org.apache.cxf.jaxrs.ext.search.ConditionType;
-import org.apache.cxf.jaxrs.ext.search.InterfaceProxy;
import org.apache.cxf.jaxrs.ext.search.OrSearchCondition;
import org.apache.cxf.jaxrs.ext.search.PropertyNotFoundException;
import org.apache.cxf.jaxrs.ext.search.SearchBean;
import org.apache.cxf.jaxrs.ext.search.SearchCondition;
-import org.apache.cxf.jaxrs.ext.search.SearchConditionParser;
import org.apache.cxf.jaxrs.ext.search.SearchParseException;
import org.apache.cxf.jaxrs.ext.search.SearchUtils;
import org.apache.cxf.jaxrs.ext.search.SimpleSearchCondition;
-import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheck;
-import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckInfo;
-import org.apache.cxf.jaxrs.utils.InjectionUtils;
import org.apache.cxf.message.MessageUtils;
@@ -61,7 +48,7 @@ import org.apache.cxf.message.MessageUti
*
* @param <T> type of search condition.
*/
-public class FiqlParser<T> implements SearchConditionParser<T> {
+public class FiqlParser<T> extends AbstractSearchConditionParser<T> {
public static final String OR = ",";
public static final String AND = ";";
@@ -75,10 +62,8 @@ public class FiqlParser<T> implements Se
public static final Map<ConditionType, String> CONDITION_MAP;
- public static final String EXTENSION_COUNT = "count";
- public static final String SUPPORT_SINGLE_EQUALS = "fiql.support.single.equals.operator";
- private static final String EXTENSION_COUNT_OPEN = EXTENSION_COUNT + "(";
+ public static final String SUPPORT_SINGLE_EQUALS = "fiql.support.single.equals.operator";
private static final Map<String, ConditionType> OPERATORS_MAP;
private static final Pattern COMPARATORS_PATTERN;
@@ -112,9 +97,7 @@ public class FiqlParser<T> implements Se
COMPARATORS_PATTERN_SINGLE_EQUALS = Pattern.compile(s2);
}
- private Beanspector<T> beanspector;
- private Class<T> conditionClass;
- private Map<String, String> contextProperties;
+ private Beanspector<T> beanspector;
private Map<String, String> beanPropertiesMap;
private Map<String, ConditionType> operatorsMap = OPERATORS_MAP;
@@ -150,11 +133,11 @@ public class FiqlParser<T> implements Se
public FiqlParser(Class<T> tclass,
Map<String, String> contextProperties,
Map<String, String> beanProperties) {
+ super(tclass, contextProperties);
+
beanspector = SearchBean.class.isAssignableFrom(tclass)
? null : new Beanspector<T>(tclass);
- conditionClass = tclass;
- this.contextProperties = contextProperties == null
- ? Collections.<String, String>emptyMap() : contextProperties;
+
this.beanPropertiesMap = beanProperties;
if (MessageUtils.isTrue(this.contextProperties.get(SUPPORT_SINGLE_EQUALS))) {
operatorsMap = new HashMap<String, ConditionType>(operatorsMap);
@@ -315,195 +298,9 @@ public class FiqlParser<T> implements Se
}
}
-
- private Object parseType(String originalPropName,
- Object ownerBean,
- Object lastCastedValue,
- String setter,
- TypeInfo typeInfo,
- String value) throws SearchParseException {
- Class<?> valueType = typeInfo.getTypeClass();
- boolean isCollection = InjectionUtils.isSupportedCollectionOrArray(valueType);
- Class<?> actualType = isCollection ? InjectionUtils.getActualType(typeInfo.getGenericType()) : valueType;
-
- int index = getDotIndex(setter);
- if (index == -1) {
- Object castedValue = value;
- if (Date.class.isAssignableFrom(valueType)) {
- castedValue = convertToDate(value);
- } else {
- boolean isPrimitive = InjectionUtils.isPrimitive(valueType);
- boolean isPrimitiveOrEnum = isPrimitive || valueType.isEnum();
- if (ownerBean == null || isPrimitiveOrEnum) {
- try {
- CollectionCheck collCheck = getCollectionCheck(originalPropName, isCollection, actualType);
- if (collCheck == null) {
- castedValue = InjectionUtils.convertStringToPrimitive(value, actualType);
- }
- if (collCheck == null && isCollection) {
- castedValue = getCollectionSingleton(valueType, castedValue);
- } else if (isCollection) {
- typeInfo.setCollectionCheckInfo(new CollectionCheckInfo(collCheck, castedValue));
- castedValue = getEmptyCollection(valueType);
- }
- } catch (Exception e) {
- throw new SearchParseException("Cannot convert String value \"" + value
- + "\" to a value of class " + valueType.getName(), e);
- }
- } else {
- Class<?> classType = isCollection ? valueType : value.getClass();
- try {
- Method setterM = valueType.getMethod("set" + getMethodNameSuffix(setter),
- new Class[]{classType});
- Object objectValue = !isCollection ? value : getCollectionSingleton(valueType, value);
- setterM.invoke(ownerBean, new Object[]{objectValue});
- castedValue = objectValue;
- } catch (Throwable ex) {
- throw new SearchParseException("Cannot convert String value \"" + value
- + "\" to a value of class " + valueType.getName(), ex);
- }
-
- }
- }
- if (lastCastedValue != null) {
- castedValue = lastCastedValue;
- }
- return castedValue;
- } else {
- String[] names = setter.split("\\.");
- try {
- String nextPart = getMethodNameSuffix(names[1]);
- Method getterM = actualType.getMethod("get" + nextPart, new Class[]{});
- Class<?> returnType = getterM.getReturnType();
- boolean returnCollection = InjectionUtils.isSupportedCollectionOrArray(returnType);
- Class<?> actualReturnType = !returnCollection ? returnType
- : InjectionUtils.getActualType(getterM.getGenericReturnType());
-
- boolean isPrimitive = InjectionUtils.isPrimitive(returnType) || returnType.isEnum();
- boolean lastTry = names.length == 2
- && (isPrimitive || returnType == Date.class || returnCollection);
-
- Object valueObject = ownerBean != null ? ownerBean
- : actualType.isInterface()
- ? Proxy.newProxyInstance(this.getClass().getClassLoader(),
- new Class[]{actualType},
- new InterfaceProxy())
- : actualType.newInstance();
- Object nextObject;
-
- if (lastTry) {
- if (!returnCollection) {
- nextObject = isPrimitive ? InjectionUtils.convertStringToPrimitive(value, returnType)
- : convertToDate(value);
- } else {
- CollectionCheck collCheck = getCollectionCheck(originalPropName, true, actualReturnType);
- if (collCheck == null) {
- nextObject = getCollectionSingleton(valueType, value);
- } else {
- typeInfo.setCollectionCheckInfo(new CollectionCheckInfo(collCheck, value));
- nextObject = getEmptyCollection(valueType);
- }
- }
- } else {
- nextObject = returnType.newInstance();
- }
-
- Method setterM = actualType.getMethod("set" + nextPart, new Class[]{returnType});
- setterM.invoke(valueObject, new Object[]{nextObject});
-
- if (lastTry) {
- lastCastedValue = lastCastedValue == null ? valueObject : lastCastedValue;
- return isCollection ? getCollectionSingleton(valueType, lastCastedValue) : lastCastedValue;
- } else {
- lastCastedValue = valueObject;
- }
-
- TypeInfo nextTypeInfo = new TypeInfo(nextObject.getClass(), getterM.getGenericReturnType());
- Object response = parseType(originalPropName,
- nextObject,
- lastCastedValue,
- setter.substring(index + 1),
- nextTypeInfo,
- value);
- if (ownerBean == null) {
- return isCollection ? getCollectionSingleton(valueType, lastCastedValue) : lastCastedValue;
- } else {
- return response;
- }
- } catch (Throwable e) {
- throw new SearchParseException("Cannot convert String value \"" + value
- + "\" to a value of class " + valueType.getName(), e);
- }
- }
- }
- private CollectionCheck getCollectionCheck(String propName, boolean isCollection, Class<?> actualCls) {
- if (isCollection) {
- if (InjectionUtils.isPrimitive(actualCls)) {
- if (propName.startsWith(EXTENSION_COUNT_OPEN)) {
- return CollectionCheck.SIZE;
- }
- } else {
- return CollectionCheck.SIZE;
- }
- }
- return null;
- }
-
- private Object getCollectionSingleton(Class<?> collectionCls, Object value) {
- if (Set.class.isAssignableFrom(collectionCls)) {
- return Collections.singleton(value);
- } else {
- return Collections.singletonList(value);
- }
- }
-
- private Object getEmptyCollection(Class<?> collectionCls) {
- if (Set.class.isAssignableFrom(collectionCls)) {
- return Collections.emptySet();
- } else {
- return Collections.emptyList();
- }
- }
-
- private Object convertToDate(String value) throws SearchParseException {
- try {
- DateFormat df = SearchUtils.getDateFormat(contextProperties);
- String dateValue = value;
- if (SearchUtils.isTimeZoneSupported(contextProperties, Boolean.FALSE)) {
- // zone in XML is "+01:00" in Java is "+0100"; stripping semicolon
- int idx = value.lastIndexOf(':');
- if (idx != -1) {
- dateValue = value.substring(0, idx) + value.substring(idx + 1);
- }
- }
- return df.parse(dateValue);
- } catch (ParseException e) {
- // is that duration?
- try {
- Date now = new Date();
- DatatypeFactory.newInstance().newDuration(value).addTo(now);
- return now;
- } catch (DatatypeConfigurationException e1) {
- throw new SearchParseException(e1);
- } catch (IllegalArgumentException e1) {
- throw new SearchParseException("Can parse " + value + " neither as date nor duration", e);
- }
- }
- }
- private int getDotIndex(String setter) {
- return this.conditionClass == SearchBean.class ? -1 : setter.indexOf(".");
- }
- private String getSetter(String setter) {
- int index = getDotIndex(setter);
- if (index != -1) {
- return setter.substring(0, index).toLowerCase();
- } else {
- return setter;
- }
- }
private String unwrapSetter(String setter) {
if (setter.startsWith(EXTENSION_COUNT_OPEN) && setter.endsWith(")")) {
@@ -513,14 +310,6 @@ public class FiqlParser<T> implements Se
}
}
- private String getMethodNameSuffix(String name) {
- if (name.length() == 1) {
- return name.toUpperCase();
- } else {
- return Character.toUpperCase(name.charAt(0)) + name.substring(1);
- }
- }
-
// node of abstract syntax tree
private interface ASTNode<T> {
SearchCondition<T> build() throws SearchParseException;
Added: cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/odata/OData2Parser.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/odata/OData2Parser.java?rev=1566482&view=auto
==============================================================================
--- cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/odata/OData2Parser.java (added)
+++ cxf/trunk/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/odata/OData2Parser.java Mon Feb 10 00:48:04 2014
@@ -0,0 +1,261 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search.odata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cxf.jaxrs.ext.search.AbstractSearchConditionParser;
+import org.apache.cxf.jaxrs.ext.search.AndSearchCondition;
+import org.apache.cxf.jaxrs.ext.search.Beanspector;
+import org.apache.cxf.jaxrs.ext.search.Beanspector.TypeInfo;
+import org.apache.cxf.jaxrs.ext.search.ConditionType;
+import org.apache.cxf.jaxrs.ext.search.OrSearchCondition;
+import org.apache.cxf.jaxrs.ext.search.PrimitiveSearchCondition;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.cxf.jaxrs.ext.search.SearchParseException;
+import org.apache.olingo.odata2.api.edm.EdmLiteral;
+import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
+import org.apache.olingo.odata2.api.edm.EdmSimpleType;
+import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
+import org.apache.olingo.odata2.api.edm.EdmTyped;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.api.exception.ODataMessageException;
+import org.apache.olingo.odata2.api.uri.expression.BinaryExpression;
+import org.apache.olingo.odata2.api.uri.expression.BinaryOperator;
+import org.apache.olingo.odata2.api.uri.expression.ExpressionVisitor;
+import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
+import org.apache.olingo.odata2.api.uri.expression.LiteralExpression;
+import org.apache.olingo.odata2.api.uri.expression.MemberExpression;
+import org.apache.olingo.odata2.api.uri.expression.MethodExpression;
+import org.apache.olingo.odata2.api.uri.expression.MethodOperator;
+import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
+import org.apache.olingo.odata2.api.uri.expression.OrderExpression;
+import org.apache.olingo.odata2.api.uri.expression.PropertyExpression;
+import org.apache.olingo.odata2.api.uri.expression.SortOrder;
+import org.apache.olingo.odata2.api.uri.expression.UnaryExpression;
+import org.apache.olingo.odata2.api.uri.expression.UnaryOperator;
+import org.apache.olingo.odata2.core.uri.expression.FilterParser;
+import org.apache.olingo.odata2.core.uri.expression.FilterParserImpl;
+
+public class OData2Parser<T> extends AbstractSearchConditionParser<T> {
+ private final FilterParser parser;
+
+ private static class TypedProperty {
+ private final TypeInfo typeInfo;
+ private final String propertyName;
+
+ TypedProperty(final TypeInfo typeInfo, final String propertyName) {
+ this.typeInfo = typeInfo;
+ this.propertyName = propertyName;
+ }
+ }
+
+ private static class TypedValue {
+ private final Object value;
+ private final String literal;
+ private final Class< ? > typeClass;
+
+ TypedValue(final Class< ? > typeClass, final String literal, final Object value) {
+ this.literal = literal;
+ this.value = value;
+ this.typeClass = typeClass;
+ }
+ }
+
+ private class FilterExpressionVisitor implements ExpressionVisitor {
+ private final T condition;
+ private final Beanspector< T > beanspector;
+
+ FilterExpressionVisitor(final T condition) {
+ this.condition = condition;
+ this.beanspector = new Beanspector<T>(condition);
+ }
+
+ @Override
+ public Object visitFilterExpression(FilterExpression filterExpression, String expressionString,
+ Object expression) {
+ return expression;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator,
+ Object leftSide, Object rightSide) {
+
+ // AND / OR operate on search conditions
+ if (operator == BinaryOperator.AND || operator == BinaryOperator.OR) {
+ if (leftSide instanceof SearchCondition && rightSide instanceof SearchCondition) {
+ final List< SearchCondition< T > > conditions = new ArrayList< SearchCondition< T > >(2);
+ conditions.add((SearchCondition< T >)leftSide);
+ conditions.add((SearchCondition< T >)rightSide);
+
+ if (operator == BinaryOperator.AND) {
+ return new AndSearchCondition< T >(conditions);
+ } else if (operator == BinaryOperator.OR) {
+ return new OrSearchCondition< T >(conditions);
+ }
+ } else {
+ throw new SearchParseException(
+ "Unsupported binary operation arguments (SearchCondition expected): "
+ + leftSide + ", " + rightSide);
+ }
+ }
+
+ // Property could be either on left side (Name eq 'Tom') or
+ // right side ('Tom' eq Name)
+ TypedValue value = null;
+ TypedProperty property = null;
+
+ if (leftSide instanceof TypedProperty && rightSide instanceof TypedValue) {
+ property = (TypedProperty)leftSide;
+ value = (TypedValue)rightSide;
+ } else if (rightSide instanceof TypedProperty && leftSide instanceof TypedValue) {
+ property = (TypedProperty)rightSide;
+ value = (TypedValue)leftSide;
+ } else {
+ throw new SearchParseException(
+ "Unsupported binary operation arguments (TypedValue or TypedProperty expected): "
+ + leftSide + ", " + rightSide);
+ }
+
+ ConditionType conditionType = null;
+ switch (operator) {
+ case EQ:
+ conditionType = ConditionType.EQUALS;
+ break;
+ case NE:
+ conditionType = ConditionType.NOT_EQUALS;
+ break;
+ case LT:
+ conditionType = ConditionType.LESS_THAN;
+ break;
+ case LE:
+ conditionType = ConditionType.LESS_OR_EQUALS;
+ break;
+ case GT:
+ conditionType = ConditionType.GREATER_THAN;
+ break;
+ case GE:
+ conditionType = ConditionType.GREATER_OR_EQUALS;
+ break;
+ default:
+ throw new SearchParseException("Unsupported binary operation: " + operator);
+ }
+
+ Object typedValue = null;
+ // If property type and value type are compatible, just use them
+ if (property.typeInfo.getTypeClass().isAssignableFrom(value.typeClass)) {
+ typedValue = value.value;
+ } else { // Property type and value type are not compatible and convert / cast are required
+ typedValue = parseType(property.propertyName, null, null, property.propertyName,
+ property.typeInfo, value.literal);
+ }
+
+ return new PrimitiveSearchCondition< T >(property.propertyName,
+ typedValue, conditionType, condition);
+ }
+
+ @Override
+ public Object visitLiteral(LiteralExpression literal, EdmLiteral edmLiteral) {
+ try {
+ final EdmSimpleType type = edmLiteral.getType();
+
+ final Object value = type.valueOfString(edmLiteral.getLiteral(),
+ EdmLiteralKind.DEFAULT, null, type.getDefaultType());
+
+ return new TypedValue(type.getDefaultType(), edmLiteral.getLiteral(), value);
+ } catch (EdmSimpleTypeException ex) {
+ throw new SearchParseException("Failed to convert literal to a typed form: " + literal, ex);
+ }
+ }
+
+ @Override
+ public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmProperty) {
+ try {
+ final TypeInfo typeInfo = beanspector.getAccessorTypeInfo(uriLiteral);
+ return new TypedProperty(typeInfo, uriLiteral);
+ } catch (Exception ex) {
+ throw new SearchParseException("Failed to get type information from property path: " + uriLiteral, ex);
+ }
+ }
+
+ @Override
+ public Object visitMethod(MethodExpression methodExpression, MethodOperator method, List<Object> parameters) {
+ throw new SearchParseException("Unsupported operation visitMethod: " + methodExpression
+ + "," + method + "," + parameters);
+ }
+
+ @Override
+ public Object visitMember(MemberExpression memberExpression, Object path, Object property) {
+ throw new SearchParseException("Unsupported operation visitMember: "
+ + memberExpression + "," + path + "," + property);
+ }
+
+ @Override
+ public Object visitUnary(UnaryExpression unaryExpression, UnaryOperator operator, Object operand) {
+ throw new SearchParseException("Unsupported operation visitUnary: " + unaryExpression
+ + "," + operator + "," + operand);
+ }
+
+ @Override
+ public Object visitOrderByExpression(OrderByExpression orderByExpression, String expressionString,
+ List<Object> orders) {
+ throw new SearchParseException("Unsupported operation visitOrderByExpression: "
+ + orderByExpression + "," + expressionString + "," + orders);
+ }
+
+ @Override
+ public Object visitOrder(OrderExpression orderExpression, Object filterResult, SortOrder sortOrder) {
+ throw new SearchParseException("Unsupported operation visitOrder: " + orderExpression
+ + "," + filterResult + "," + sortOrder);
+ }
+ }
+
+ /**
+ * Creates OData 2.0 parser.
+ *
+ * @param conditionClass - class of T used to create condition objects. Class T must have
+ * accessible no-arguments constructor and complementary setters to these used in
+ * OData 2.0 $filter expressions.
+ */
+ public OData2Parser(final Class< T > conditionClass) {
+ super(conditionClass);
+ this.parser = new FilterParserImpl(null);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public SearchCondition<T> parse(String searchExpression) throws SearchParseException {
+ try {
+ final T condition = conditionClass.newInstance();
+ final FilterExpression expression = parser.parseFilterString(searchExpression);
+ final FilterExpressionVisitor visitor = new FilterExpressionVisitor(condition);
+ return (SearchCondition< T >)expression.accept(visitor);
+ } catch (ODataMessageException ex) {
+ throw new SearchParseException(ex);
+ } catch (ODataApplicationException ex) {
+ throw new SearchParseException(ex);
+ } catch (InstantiationException ex) {
+ throw new SearchParseException(ex);
+ } catch (IllegalAccessException ex) {
+ throw new SearchParseException(ex);
+ }
+ }
+}
Added: cxf/trunk/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/OData2ParserTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/OData2ParserTest.java?rev=1566482&view=auto
==============================================================================
--- cxf/trunk/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/OData2ParserTest.java (added)
+++ cxf/trunk/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/odata/OData2ParserTest.java Mon Feb 10 00:48:04 2014
@@ -0,0 +1,122 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search.odata;
+
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.cxf.jaxrs.ext.search.SearchParseException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class OData2ParserTest extends Assert {
+ private OData2Parser<Person> parser;
+
+ public static class Person {
+ private String firstName;
+ private String lastName;
+ private int age;
+
+ public Person() {
+ }
+
+ public Person(final String firstName, final String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ Person withAge(int newAge) {
+ setAge(newAge);
+ return this;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ parser = new OData2Parser<Person>(Person.class);
+ }
+
+ @Test
+ public void testFilterByFirstNameEqualsValue() throws SearchParseException {
+ SearchCondition< Person > filter = parser.parse("FirstName eq 'Tom'");
+ assertTrue(filter.isMet(new Person("Tom", "Bombadil")));
+ assertFalse(filter.isMet(new Person("Peter", "Bombadil")));
+ }
+
+ @Test
+ public void testFilterByFirstAndLastNameEqualValue() throws SearchParseException {
+ SearchCondition< Person > filter = parser.parse("FirstName eq 'Tom' and LastName eq 'Bombadil'");
+ assertTrue(filter.isMet(new Person("Tom", "Bombadil")));
+ assertFalse(filter.isMet(new Person("Peter", "Bombadil")));
+ }
+
+ @Test
+ public void testFilterByFirstOrLastNameEqualValue() throws SearchParseException {
+ SearchCondition< Person > filter =
+ parser.parse("FirstName eq 'Tom' or FirstName eq 'Peter' and LastName eq 'Bombadil'");
+ assertTrue(filter.isMet(new Person("Tom", "Bombadil")));
+ assertTrue(filter.isMet(new Person("Peter", "Bombadil")));
+ }
+
+ @Test
+ public void testFilterByFirstAndLastNameEqualValueWithAlternative() throws SearchParseException {
+ SearchCondition< Person > filter = parser.parse(
+ "(FirstName eq 'Tom' and LastName eq 'Tommyknocker')"
+ + " or (FirstName eq 'Peter' and LastName eq 'Bombadil')");
+ assertTrue(filter.isMet(new Person("Tom", "Tommyknocker")));
+ assertTrue(filter.isMet(new Person("Peter", "Bombadil")));
+ assertFalse(filter.isMet(new Person("Tom", "Bombadil")));
+ }
+
+ @Test
+ public void testFilterByValueEqualsFirstName() throws SearchParseException {
+ SearchCondition< Person > filter = parser.parse("'Tom' eq FirstName");
+ assertTrue(filter.isMet(new Person("Tom", "Bombadil")));
+ }
+
+ @Test
+ public void testFilterByAgeGreatThanValue() throws SearchParseException {
+ SearchCondition< Person > filter = parser.parse("Age gt 17");
+ assertTrue(filter.isMet(new Person("Tom", "Bombadil").withAge(18)));
+ assertFalse(filter.isMet(new Person("Tom", "Bombadil").withAge(16)));
+ }
+}