/** * Copyright © Microsoft Open Technologies, Inc. * * All Rights Reserved * * Licensed 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 * * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS * OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION * ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A * PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. * * See the Apache License, Version 2.0 for the specific language * governing permissions and limitations under the License. */ package com.msopentech.odatajclient.proxy.utils; import static com.msopentech.odatajclient.engine.data.ODataLinkType.ENTITY_NAVIGATION; import static com.msopentech.odatajclient.engine.data.ODataLinkType.ENTITY_SET_NAVIGATION; import com.msopentech.odatajclient.engine.data.ODataCollectionValue; import com.msopentech.odatajclient.engine.data.ODataComplexValue; import com.msopentech.odatajclient.engine.data.ODataEntity; import com.msopentech.odatajclient.engine.data.ODataFactory; import com.msopentech.odatajclient.engine.data.ODataGeospatialValue; import com.msopentech.odatajclient.engine.data.ODataLink; import com.msopentech.odatajclient.engine.data.ODataLinkType; import com.msopentech.odatajclient.engine.data.ODataPrimitiveValue; import com.msopentech.odatajclient.engine.data.ODataProperty; import com.msopentech.odatajclient.engine.data.ODataValue; import com.msopentech.odatajclient.engine.data.metadata.EdmMetadata; import com.msopentech.odatajclient.engine.data.metadata.EdmType; import com.msopentech.odatajclient.engine.data.metadata.edm.Association; import com.msopentech.odatajclient.engine.data.metadata.edm.AssociationSet; import com.msopentech.odatajclient.engine.data.metadata.edm.AssociationSetEnd; import com.msopentech.odatajclient.engine.data.metadata.edm.EdmSimpleType; import com.msopentech.odatajclient.engine.data.metadata.edm.EntityContainer; import com.msopentech.odatajclient.engine.data.metadata.edm.Schema; import com.msopentech.odatajclient.engine.data.metadata.edm.geospatial.Geospatial; import com.msopentech.odatajclient.proxy.api.AbstractComplexType; import com.msopentech.odatajclient.proxy.api.annotations.ComplexType; import com.msopentech.odatajclient.proxy.api.annotations.CompoundKeyElement; import com.msopentech.odatajclient.proxy.api.annotations.Key; import com.msopentech.odatajclient.proxy.api.annotations.NavigationProperty; import com.msopentech.odatajclient.proxy.api.annotations.Property; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URI; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class EngineUtils { /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger(EngineUtils.class); private EngineUtils() { // Empty private constructor for static utility classes } public static Map.Entry<EntityContainer, AssociationSet> getAssociationSet( final Association association, final String associationNamespace, final EdmMetadata metadata) { final StringBuilder associationName = new StringBuilder(); associationName.append(associationNamespace).append('.').append(association.getName()); for (Schema schema : metadata.getSchemas()) { for (EntityContainer container : schema.getEntityContainers()) { final AssociationSet associationSet = getAssociationSet(associationName.toString(), container); if (associationSet != null) { return new AbstractMap.SimpleEntry<EntityContainer, AssociationSet>(container, associationSet); } } } throw new IllegalStateException("Association set not found"); } public static Association getAssociation(final Schema schema, final String relationship) { return schema.getAssociation(relationship.substring(relationship.lastIndexOf('.') + 1)); } public static AssociationSet getAssociationSet(final String association, final EntityContainer container) { LOG.debug("Search for association set {}", association); for (AssociationSet associationSet : container.getAssociationSets()) { LOG.debug("Retrieved association set '{}:{}'", associationSet.getName(), associationSet.getAssociation()); if (associationSet.getAssociation().equals(association)) { return associationSet; } } return null; } public static String getEntitySetName(final AssociationSet associationSet, final String role) { for (AssociationSetEnd end : associationSet.getEnds()) { if (end.getRole().equals(role)) { return end.getEntitySet(); } } return null; } public static NavigationProperty getNavigationProperty(final Class<?> entityTypeRef, final String relationship) { NavigationProperty res = null; final Method[] methods = entityTypeRef.getClass().getDeclaredMethods(); for (int i = 0; i < methods.length && res == null; i++) { final Annotation ann = methods[i].getAnnotation(NavigationProperty.class); if ((ann instanceof NavigationProperty) && ((NavigationProperty) ann).relationship().equalsIgnoreCase(relationship)) { res = (NavigationProperty) ann; } } return res; } public static ODataLink getNavigationLink(final String name, final ODataEntity entity) { ODataLink res = null; final List<ODataLink> links = entity.getNavigationLinks(); for (int i = 0; i < links.size() && res == null; i++) { if (links.get(i).getName().equalsIgnoreCase(name)) { res = links.get(i); } } return res; } public static ODataLink getNavigationLink(final String name, final URI uri, final ODataLinkType type) { switch (type) { case ENTITY_NAVIGATION: return ODataFactory.newEntityNavigationLink(name, uri); case ENTITY_SET_NAVIGATION: return ODataFactory.newFeedNavigationLink(name, uri); default: throw new IllegalArgumentException("Invalid link type " + type.name()); } } public static ODataValue getODataValue(final EdmMetadata metadata, final EdmType type, final Object obj) { final ODataValue value; if (type.isCollection()) { value = new ODataCollectionValue(type.getTypeExpression()); final EdmType intType = new EdmType(metadata, type.getBaseType()); for (Object collectionItem : (Collection) obj) { if (intType.isSimpleType()) { ((ODataCollectionValue) value).add(getODataValue(metadata, intType, collectionItem).asPrimitive()); } else if (intType.isComplexType()) { ((ODataCollectionValue) value).add(getODataValue(metadata, intType, collectionItem).asComplex()); } else if (intType.isEnumType()) { // TODO: manage enum types throw new UnsupportedOperationException("Usupported enum type " + intType.getTypeExpression()); } else { throw new UnsupportedOperationException("Usupported object type " + intType.getTypeExpression()); } } } else if (type.isComplexType()) { value = new ODataComplexValue(type.getBaseType()); if (obj.getClass().isAnnotationPresent(ComplexType.class)) { for (Method method : obj.getClass().getMethods()) { final Property complexPropertyAnn = method.getAnnotation(Property.class); try { if (complexPropertyAnn != null) { value.asComplex().add( getODataProperty(metadata, complexPropertyAnn.name(), method.invoke(obj))); } } catch (Exception ignore) { // ignore value LOG.warn("Error attaching complex field '{}'", complexPropertyAnn.name(), ignore); } } } else { throw new IllegalArgumentException( "Object '" + obj.getClass().getSimpleName() + "' is not a complex value"); } } else if (type.isEnumType()) { // TODO: manage enum types throw new UnsupportedOperationException("Usupported enum type " + type.getTypeExpression()); } else { final EdmSimpleType simpleType = EdmSimpleType.fromValue(type.getTypeExpression()); if (simpleType.isGeospatial()) { value = new ODataGeospatialValue.Builder().setValue((Geospatial) obj).setType(simpleType).build(); } else { value = new ODataPrimitiveValue.Builder().setValue(obj).setType(simpleType).build(); } } return value; } private static ODataProperty getODataProperty(final EdmMetadata metadata, final String name, final Object obj) { final ODataProperty oprop; final EdmType type = getEdmType(metadata, obj); try { if (type == null || obj == null) { oprop = ODataFactory.newPrimitiveProperty(name, null); } else if (type.isCollection()) { // create collection property oprop = ODataFactory.newCollectionProperty( name, getODataValue(metadata, type, obj).asCollection()); } else if (type.isSimpleType()) { // create a primitive property oprop = ODataFactory.newPrimitiveProperty( name, getODataValue(metadata, type, obj).asPrimitive()); } else if (type.isComplexType()) { // create a complex property oprop = ODataFactory.newComplexProperty( name, getODataValue(metadata, type, obj).asComplex()); } else if (type.isEnumType()) { // TODO: manage enum types throw new UnsupportedOperationException("Usupported enum type " + type.getTypeExpression()); } else { throw new UnsupportedOperationException("Usupported object type " + type.getTypeExpression()); } return oprop; } catch (Exception e) { throw new IllegalStateException(e); } } public static void addProperties( final EdmMetadata metadata, final Map<String, Object> changes, final ODataEntity entity) { for (Map.Entry<String, Object> property : changes.entrySet()) { // if the getter exists and it is annotated as expected then get value/value and add a new property final ODataProperty odataProperty = entity.getProperty(property.getKey()); if (odataProperty != null) { entity.removeProperty(odataProperty); } entity.addProperty(getODataProperty(metadata, property.getKey(), property.getValue())); } } @SuppressWarnings("unchecked") private static void setPropertyValue(final Object bean, final Method getter, final Object value) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // Assumption: setter is always prefixed by 'set' word final String setterName = getter.getName().replaceFirst("get", "set"); bean.getClass().getMethod(setterName, getter.getReturnType()).invoke(bean, value); } public static Object getKey( final EdmMetadata metadata, final Class<?> entityTypeRef, final ODataEntity entity) { final Object res; if (entity.getProperties().isEmpty()) { res = null; } else { final Class<?> keyRef = ClassUtils.getCompoundKeyRef(entityTypeRef); if (keyRef == null) { final ODataProperty property = entity.getProperty(firstValidEntityKey(entityTypeRef)); res = property == null || !property.hasPrimitiveValue() ? null : property.getPrimitiveValue().toValue(); } else { try { res = keyRef.newInstance(); populate(metadata, res, CompoundKeyElement.class, entity.getProperties().iterator()); } catch (Exception e) { LOG.error("Error population compound key {}", keyRef.getSimpleName(), e); throw new IllegalArgumentException("Cannot populate compound key"); } } } return res; } @SuppressWarnings("unchecked") public static void populate( final EdmMetadata metadata, final Object bean, final Class<? extends Annotation> getterAnn, final Iterator<ODataProperty> propItor) { if (bean != null) { while (propItor.hasNext()) { final ODataProperty property = propItor.next(); final Method getter = ClassUtils.findGetterByAnnotatedName(bean.getClass(), getterAnn, property.getName()); if (getter == null) { LOG.warn("Could not find any property annotated as {} in {}", property.getName(), bean.getClass().getName()); } else { try { if (property.hasNullValue()) { setPropertyValue(bean, getter, null); } if (property.hasPrimitiveValue()) { setPropertyValue(bean, getter, property.getPrimitiveValue().toValue()); } if (property.hasComplexValue()) { final Object complex = getter.getReturnType().newInstance(); populate(metadata, complex, Property.class, property.getComplexValue().iterator()); setPropertyValue(bean, getter, complex); } if (property.hasCollectionValue()) { final ParameterizedType collType = (ParameterizedType) getter.getGenericReturnType(); final Class<?> collItemClass = (Class<?>) collType.getActualTypeArguments()[0]; Collection collection = (Collection) getter.invoke(bean); if (collection == null) { collection = new ArrayList(); setPropertyValue(bean, getter, collection); } final Iterator<ODataValue> collPropItor = property.getCollectionValue().iterator(); while (collPropItor.hasNext()) { final ODataValue value = collPropItor.next(); if (value.isPrimitive()) { collection.add(value.asPrimitive().toValue()); } if (value.isComplex()) { final Object collItem = collItemClass.newInstance(); populate(metadata, collItem, Property.class, value.asComplex().iterator()); collection.add(collItem); } } } } catch (Exception e) { LOG.error("Could not set property {} on {}", getter, bean, e); } } } } } @SuppressWarnings("unchecked") public static Object getValueFromProperty(final EdmMetadata metadata, final ODataProperty property) throws InstantiationException, IllegalAccessException { final Object value; if (property == null || property.hasNullValue()) { value = null; } else if (property.hasCollectionValue()) { value = new ArrayList(); final Iterator<ODataValue> collPropItor = property.getCollectionValue().iterator(); while (collPropItor.hasNext()) { final ODataValue odataValue = collPropItor.next(); if (odataValue.isPrimitive()) { ((Collection) value).add(odataValue.asPrimitive().toValue()); } if (odataValue.isComplex()) { final Object collItem = buildComplexInstance(metadata, property.getName(), odataValue.asComplex().iterator()); ((Collection) value).add(collItem); } } } else if (property.hasPrimitiveValue()) { value = property.getPrimitiveValue().toValue(); } else if (property.hasComplexValue()) { value = buildComplexInstance(metadata, property.getComplexValue().getTypeName(), property.getComplexValue(). iterator()); } else { throw new IllegalArgumentException("Invalid property " + property); } return value; } @SuppressWarnings("unchecked") private static <C extends AbstractComplexType> C buildComplexInstance( final EdmMetadata metadata, final String name, final Iterator<ODataProperty> properties) { for (C complex : (Iterable<C>) ServiceLoader.load(AbstractComplexType.class)) { final ComplexType ann = complex.getClass().getAnnotation(ComplexType.class); final String fn = ann == null ? null : ClassUtils.getNamespace(complex.getClass()) + "." + ann.value(); if (name.equals(fn)) { populate(metadata, complex, Property.class, properties); return complex; } } return null; } @SuppressWarnings({"unchecked", "rawtypes"}) public static Object getValueFromProperty( final EdmMetadata metadata, final ODataProperty property, final Type type) throws InstantiationException, IllegalAccessException { final Object value; if (property == null || property.hasNullValue()) { value = null; } else if (property.hasCollectionValue()) { value = new ArrayList(); final ParameterizedType collType = (ParameterizedType) type; final Class<?> collItemClass = (Class<?>) collType.getActualTypeArguments()[0]; final Iterator<ODataValue> collPropItor = property.getCollectionValue().iterator(); while (collPropItor.hasNext()) { final ODataValue odataValue = collPropItor.next(); if (odataValue.isPrimitive()) { ((Collection) value).add(odataValue.asPrimitive().toValue()); } if (odataValue.isComplex()) { final Object collItem = collItemClass.newInstance(); populate(metadata, collItem, Property.class, odataValue.asComplex().iterator()); ((Collection) value).add(collItem); } } } else if (property.hasPrimitiveValue()) { value = property.getPrimitiveValue().toValue(); } else if (property.hasComplexValue()) { value = ((Class<?>) type).newInstance(); populate(metadata, value, Property.class, property.getComplexValue().iterator()); } else { throw new IllegalArgumentException("Invalid property " + property); } return value; } private static String firstValidEntityKey(final Class<?> entityTypeRef) { for (Method method : entityTypeRef.getDeclaredMethods()) { if (method.getAnnotation(Key.class) != null) { final Annotation ann = method.getAnnotation(Property.class); if (ann != null) { return ((Property) ann).name(); } } } return null; } private static EdmType getEdmType(final EdmMetadata metadata, final Object obj) { final EdmType res; if (obj == null) { res = null; } else if (Collection.class.isAssignableFrom(obj.getClass())) { if (((Collection) obj).isEmpty()) { res = new EdmType(metadata, "Collection(" + getEdmType(metadata, "Edm.String")); } else { res = new EdmType(metadata, "Collection(" + getEdmType(metadata, ((Collection) obj).iterator().next()).getTypeExpression() + ")"); } } else if (obj.getClass().isAnnotationPresent(ComplexType.class)) { final String ns = ClassUtils.getNamespace(obj.getClass()); final ComplexType ann = obj.getClass().getAnnotation(ComplexType.class); res = new EdmType(metadata, ns + "." + ann.value()); } else { final EdmSimpleType simpleType = EdmSimpleType.fromObject(obj); res = new EdmType(metadata, simpleType.toString()); } return res; } public static URI getEditMediaLink(final String name, final ODataEntity entity) { for (ODataLink editMediaLink : entity.getEditMediaLinks()) { if (name.equalsIgnoreCase(editMediaLink.getName())) { return editMediaLink.getLink(); } } throw new IllegalArgumentException("Invalid streamed property " + name); } }