/** * 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.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Time; import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; import java.util.Collection; 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.common.util.PropertyUtils; 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.provider.ServerProviderFactory; import org.apache.cxf.jaxrs.utils.InjectionUtils; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; public abstract class AbstractSearchConditionParser<T> implements SearchConditionParser<T> { private static final Annotation[] EMPTY_ANNOTTAIONS = new Annotation[]{}; protected final Map<String, String> contextProperties; protected final Class<T> conditionClass; protected Beanspector<T> beanspector; protected Map<String, String> beanPropertiesMap; protected AbstractSearchConditionParser(Class<T> tclass) { this(tclass, Collections.<String, String>emptyMap(), null); } protected AbstractSearchConditionParser(Class<T> tclass, Map<String, String> contextProperties, Map<String, String> beanProperties) { this.conditionClass = tclass; this.contextProperties = contextProperties == null ? Collections.<String, String>emptyMap() : contextProperties; beanspector = SearchBean.class.isAssignableFrom(tclass) ? null : new Beanspector<T>(tclass); this.beanPropertiesMap = beanProperties; } protected String getActualSetterName(String setter) { String beanPropertyName = beanPropertiesMap == null ? null : beanPropertiesMap.get(setter); if (beanPropertyName == null) { Message m = JAXRSUtils.getCurrentMessage(); if (m != null) { Object converterProp = m.getContextualProperty(SearchUtils.BEAN_PROPERTY_CONVERTER); if (converterProp != null) { PropertyNameConverter converter = (PropertyNameConverter)converterProp; beanPropertyName = converter.getPropertyName(setter); } } } return beanPropertyName != null ? beanPropertyName : setter; } protected Boolean isDecodeQueryValues() { return PropertyUtils.isTrue(contextProperties.get(SearchUtils.DECODE_QUERY_VALUES)); } protected TypeInfo getTypeInfo(String setter, String value) throws SearchParseException, PropertyNotFoundException { String name = getSetter(setter); TypeInfo typeInfo = null; try { typeInfo = beanspector != null ? beanspector.getAccessorTypeInfo(name) : new TypeInfo(String.class, String.class); } catch (Exception e) { // continue } if (typeInfo == null && !MessageUtils.isTrue(contextProperties.get(SearchUtils.LAX_PROPERTY_MATCH))) { throw new PropertyNotFoundException(name, value); } return typeInfo; } 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(valueType, 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 = !returnCollection && InjectionUtils.isPrimitive(returnType) || returnType.isEnum(); boolean lastTry = names.length == 2 && (isPrimitive || Date.class.isAssignableFrom(returnType) || returnCollection || paramConverterAvailable(returnType)); 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(returnType, 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 if (!returnCollection) { nextObject = returnType.newInstance(); } else { nextObject = actualReturnType.newInstance(); } Method setterM = actualType.getMethod("set" + nextPart, new Class[]{returnType}); Object valueObjectValue = null; if (lastTry || !returnCollection) { valueObjectValue = nextObject; } else { Class<?> collCls = Collection.class.isAssignableFrom(valueType) ? valueType : returnType; valueObjectValue = getCollectionSingleton(collCls, nextObject); } setterM.invoke(valueObject, new Object[]{valueObjectValue}); if (lastTry) { lastCastedValue = lastCastedValue == null ? valueObject : lastCastedValue; return isCollection ? getCollectionSingleton(valueType, lastCastedValue) : lastCastedValue; } else { lastCastedValue = valueObject; } TypeInfo nextTypeInfo = new TypeInfo(valueObjectValue.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 boolean paramConverterAvailable(Class<?> pClass) { Message m = JAXRSUtils.getCurrentMessage(); ServerProviderFactory pf = m == null ? null : ServerProviderFactory.getInstance(m); return pf != null && pf.createParameterHandler(pClass, pClass, EMPTY_ANNOTTAIONS, m) != null; } private CollectionCheck getCollectionCheck(String propName, boolean isCollection, Class<?> actualCls) { if (isCollection) { if (InjectionUtils.isPrimitive(actualCls)) { if (isCount(propName)) { return CollectionCheck.SIZE; } } else { return CollectionCheck.SIZE; } } return null; } protected boolean isCount(String propName) { return false; } 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(Class<?> valueType, String value) throws SearchParseException { Message m = JAXRSUtils.getCurrentMessage(); Object obj = InjectionUtils.createFromParameterHandler(value, valueType, valueType, new Annotation[]{}, m); if (obj != null) { return obj; } try { if (Timestamp.class.isAssignableFrom(valueType)) { return convertToTimestamp(value); } else if (Time.class.isAssignableFrom(valueType)) { return convertToTime(value); } else { return convertToDefaultDate(value); } } 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 Timestamp convertToTimestamp(String value) throws ParseException { Date date = convertToDefaultDate(value); return new Timestamp(date.getTime()); } private Time convertToTime(String value) throws ParseException { Date date = convertToDefaultDate(value); return new Time(date.getTime()); } private Date convertToDefaultDate(String value) throws ParseException { 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); } 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("."); } }