package org.odata4j.producer.inmemory;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.core4j.Enumerable;
import org.core4j.Func;
import org.core4j.Func1;
import org.core4j.Predicate1;
import org.odata4j.core.OAtomStreamEntity;
import org.odata4j.core.OCollection;
import org.odata4j.core.OCollections;
import org.odata4j.core.OComplexObject;
import org.odata4j.core.OComplexObjects;
import org.odata4j.core.OEntities;
import org.odata4j.core.OEntity;
import org.odata4j.core.OEntityId;
import org.odata4j.core.OEntityKey;
import org.odata4j.core.OExtension;
import org.odata4j.core.OFunctionParameter;
import org.odata4j.core.OLink;
import org.odata4j.core.OLinks;
import org.odata4j.core.OObject;
import org.odata4j.core.OProperties;
import org.odata4j.core.OProperty;
import org.odata4j.core.OSimpleObject;
import org.odata4j.core.OSimpleObjects;
import org.odata4j.core.OStructuralObject;
import org.odata4j.edm.EdmCollectionType;
import org.odata4j.edm.EdmComplexType;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmDecorator;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmFunctionImport;
import org.odata4j.edm.EdmMultiplicity;
import org.odata4j.edm.EdmNavigationProperty;
import org.odata4j.edm.EdmProperty;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmStructuralType;
import org.odata4j.edm.EdmType;
import org.odata4j.exceptions.NotFoundException;
import org.odata4j.exceptions.NotImplementedException;
import org.odata4j.expression.BoolCommonExpression;
import org.odata4j.expression.OrderByExpression;
import org.odata4j.expression.OrderByExpression.Direction;
import org.odata4j.producer.BaseResponse;
import org.odata4j.producer.ContextStream;
import org.odata4j.producer.CountResponse;
import org.odata4j.producer.EntitiesResponse;
import org.odata4j.producer.EntityIdResponse;
import org.odata4j.producer.EntityQueryInfo;
import org.odata4j.producer.EntityResponse;
import org.odata4j.producer.InlineCount;
import org.odata4j.producer.ODataContext;
import org.odata4j.producer.ODataProducer;
import org.odata4j.producer.PropertyPathHelper;
import org.odata4j.producer.QueryInfo;
import org.odata4j.producer.Responses;
import org.odata4j.producer.edm.MetadataProducer;
import org.odata4j.producer.inmemory.InMemoryProducer.RequestContext.RequestType;
/**
* An in-memory implementation of an ODATA Producer. Uses the standard Java bean
* and property model to access information within entities.
*/
public class InMemoryProducer implements ODataProducer {
private static final boolean DUMP = false;
private static void dump(String msg) {
if (DUMP) System.out.println(msg);
}
public static final String ID_PROPNAME = "EntityId";
private final String namespace;
private final String containerName;
private final int maxResults;
// preserve the order of registration
private final Map<String, InMemoryEntityInfo<?>> eis = new LinkedHashMap<String, InMemoryEntityInfo<?>>();
private final Map<String, InMemoryComplexTypeInfo<?>> complexTypes = new LinkedHashMap<String, InMemoryComplexTypeInfo<?>>();
private EdmDataServices metadata;
private final EdmDecorator decorator;
private final MetadataProducer metadataProducer;
private final InMemoryTypeMapping typeMapping;
private boolean includeNullPropertyValues = true;
private final boolean flattenEdm;
private static final int DEFAULT_MAX_RESULTS = 100;
/**
* Creates a new instance of an in-memory POJO producer.
*
* @param namespace the namespace of the schema registrations
*/
public InMemoryProducer(String namespace) {
this(namespace, DEFAULT_MAX_RESULTS);
}
/**
* Creates a new instance of an in-memory POJO producer.
*
* @param namespace the namespace of the schema registrations
* @param maxResults the maximum number of entities to return in a single call
*/
public InMemoryProducer(String namespace, int maxResults) {
this(namespace, null, maxResults, null, null);
}
/**
* Creates a new instance of an in-memory POJO producer.
*
* @param namespace the namespace of the schema registrations
* @param containerName the container name for generated metadata
* @param maxResults the maximum number of entities to return in a single call
* @param decorator a decorator to use for edm customizations
* @param typeMapping optional mapping between java types and edm types, null for default
*/
public InMemoryProducer(String namespace, String containerName, int maxResults, EdmDecorator decorator, InMemoryTypeMapping typeMapping) {
this(namespace, containerName, maxResults, decorator, typeMapping,
true); // legacy: flatten edm
}
public InMemoryProducer(String namespace, String containerName, int maxResults, EdmDecorator decorator, InMemoryTypeMapping typeMapping,
boolean flattenEdm) {
this.namespace = namespace;
this.containerName = containerName != null && !containerName.isEmpty() ? containerName : "Container";
this.maxResults = maxResults;
this.decorator = decorator;
this.metadataProducer = new MetadataProducer(this, decorator);
this.typeMapping = typeMapping == null ? InMemoryTypeMapping.DEFAULT : typeMapping;
this.flattenEdm = flattenEdm;
}
@Override
public EdmDataServices getMetadata() {
if (metadata == null) {
metadata = newEdmGenerator(namespace, typeMapping, ID_PROPNAME, eis, complexTypes).generateEdm(decorator).build();
}
return metadata;
}
public String getContainerName() {
return containerName;
}
protected InMemoryEdmGenerator newEdmGenerator(String namespace, InMemoryTypeMapping typeMapping, String idPropName, Map<String, InMemoryEntityInfo<?>> eis,
Map<String, InMemoryComplexTypeInfo<?>> complexTypesInfo) {
return new InMemoryEdmGenerator(namespace, containerName, typeMapping, ID_PROPNAME, eis, complexTypesInfo, this.flattenEdm);
}
@Override
public MetadataProducer getMetadataProducer() {
return metadataProducer;
}
@Override
public void close() {
}
public void setIncludeNullPropertyValues(boolean value) {
this.includeNullPropertyValues = value;
}
/**
* Registers a POJO class as an EdmComplexType.
*
* @param complexTypeClass The POJO Class
* @param typeName The name of the EdmComplexType
*/
public <TEntity> void registerComplexType(Class<TEntity> complexTypeClass, String typeName) {
registerComplexType(complexTypeClass, typeName,
new EnumsAsStringsPropertyModelDelegate(new BeanBasedPropertyModel(complexTypeClass, this.flattenEdm)));
}
public <TEntity> void registerComplexType(Class<TEntity> complexTypeClass, String typeName, PropertyModel propertyModel) {
InMemoryComplexTypeInfo<TEntity> i = new InMemoryComplexTypeInfo<TEntity>();
i.typeName = (typeName == null) ? complexTypeClass.getSimpleName() : typeName;
i.entityClass = complexTypeClass;
i.propertyModel = propertyModel;
complexTypes.put(i.typeName, i);
metadata = null;
}
/**
* Registers a new entity based on a POJO, with support for composite keys.
*
* @param entityClass the class of the entities that are to be stored in the set
* @param entitySetName the alias the set will be known by; this is what is used in the OData url
* @param get a function to iterate over the elements in the set
* @param keys one or more keys for the entity
*/
public <TEntity> void register(Class<TEntity> entityClass, String entitySetName, Func<Iterable<TEntity>> get, String... keys) {
register(entityClass, entitySetName, entitySetName, get, keys);
}
/**
* Registers a new entity based on a POJO, with support for composite keys.
*
* @param entityClass the class of the entities that are to be stored in the set
* @param entitySetName the alias the set will be known by; this is what is used in the OData url
* @param entityTypeName type name of the entity
* @param get a function to iterate over the elements in the set
* @param keys one or more keys for the entity
*/
public <TEntity> void register(Class<TEntity> entityClass, String entitySetName, String entityTypeName, Func<Iterable<TEntity>> get, String... keys) {
PropertyModel model = new BeanBasedPropertyModel(entityClass, this.flattenEdm);
model = new EnumsAsStringsPropertyModelDelegate(model);
register(entityClass, model, entitySetName, entityTypeName, get, keys);
}
/**
* Registers a new entity set based on a POJO type using the default property model.
*/
public <TEntity, TKey> void register(Class<TEntity> entityClass, Class<TKey> keyClass, String entitySetName, Func<Iterable<TEntity>> get, Func1<TEntity, TKey> id) {
PropertyModel model = new BeanBasedPropertyModel(entityClass, this.flattenEdm);
model = new EnumsAsStringsPropertyModelDelegate(model);
model = new EntityIdFunctionPropertyModelDelegate<TEntity, TKey>(model, ID_PROPNAME, keyClass, id);
register(entityClass, model, entitySetName, get, ID_PROPNAME);
}
/**
* Registers a new entity set based on a POJO type and a property model.
*
* @param entityClass the class of the entities that are to be stored in the set
* @param propertyModel a way to get/set properties on the POJO
* @param entitySetName the alias the set will be known by; this is what is used in the ODATA URL
* @param get a function to iterate over the elements in the set
* @param keys one or more keys for the entity
*/
public <TEntity, TKey> void register(
Class<TEntity> entityClass,
PropertyModel propertyModel,
String entitySetName,
Func<Iterable<TEntity>> get,
String... keys) {
register(entityClass, propertyModel, entitySetName, entitySetName, get, keys);
}
public <TEntity> void register(
final Class<TEntity> entityClass,
final PropertyModel propertyModel,
final String entitySetName,
final String entityTypeName,
final Func<Iterable<TEntity>> get,
final String... keys) {
register(entityClass, propertyModel, entitySetName, entityTypeName,
get, null, keys);
}
public <TEntity> void register(
final Class<TEntity> entityClass,
final PropertyModel propertyModel,
final String entitySetName,
final String entityTypeName,
final Func<Iterable<TEntity>> get,
final Func1<RequestContext, Iterable<TEntity>> getWithContext,
final String... keys) {
InMemoryEntityInfo<TEntity> ei = new InMemoryEntityInfo<TEntity>();
ei.entitySetName = entitySetName;
ei.entityTypeName = entityTypeName;
ei.properties = propertyModel;
ei.get = get;
ei.getWithContext = getWithContext;
ei.keys = keys;
ei.entityClass = entityClass;
ei.hasStream = OAtomStreamEntity.class.isAssignableFrom(entityClass);
ei.id = new Func1<Object, HashMap<String, Object>>() {
@Override
public HashMap<String, Object> apply(Object input) {
HashMap<String, Object> values = new HashMap<String, Object>();
for (String key : keys) {
values.put(key, eis.get(entitySetName).properties.getPropertyValue(input, key));
}
return values;
}
};
eis.put(entitySetName, ei);
metadata = null;
}
protected InMemoryComplexTypeInfo<?> findComplexTypeInfoForClass(Class<?> clazz) {
// drill down the hierarchy as far as we can go.
InMemoryComplexTypeInfo<?> found = null;
for (InMemoryComplexTypeInfo<?> typeInfo : this.complexTypes.values()) {
if (typeInfo.entityClass.equals(clazz)) {
return typeInfo; // as far down as we can go
} else if (typeInfo.entityClass.isAssignableFrom(clazz)) {
// somewhere in the ancestors of clazz
if (null == found || found.entityClass.isAssignableFrom(typeInfo.entityClass)) {
// we found a lower ancestor
found = typeInfo;
}
}
}
return found;
}
protected InMemoryEntityInfo<?> findEntityInfoForClass(Class<?> clazz) {
// drill down the hierarchy as far as we can go.
InMemoryEntityInfo<?> found = null;
for (InMemoryEntityInfo<?> typeInfo : this.eis.values()) {
if (typeInfo.entityClass.equals(clazz)) {
return typeInfo; // as far down as we can go
} else if (typeInfo.entityClass.isAssignableFrom(clazz)) {
// somewhere in the ancestors of clazz
if (null == found || found.entityClass.isAssignableFrom(typeInfo.entityClass)) {
// we found a lower ancestor
found = typeInfo;
}
}
}
return found;
}
/**
* Transforms a POJO into a list of OProperties based on a given
* EdmStructuralType.
*
* @param obj the POJO to transform
* @param propertyModel the PropertyModel to use to access POJO class
* structure and values.
* @param structuralType the EdmStructuralType
* @param properties put properties into this list.
*/
protected void addPropertiesFromObject(Object obj, PropertyModel propertyModel, EdmStructuralType structuralType, List<OProperty<?>> properties, PropertyPathHelper pathHelper) {
dump("addPropertiesFromObject: " + obj.getClass().getName());
for (Iterator<EdmProperty> it = structuralType.getProperties().iterator(); it.hasNext();) {
EdmProperty property = it.next();
// $select projections not allowed for complex types....hmmh...why?
if (structuralType instanceof EdmEntityType && !pathHelper.isSelected(property.getName())) {
continue;
}
Object value = propertyModel.getPropertyValue(obj, property.getName());
dump(" prop: " + property.getName() + " val: " + value);
if (value == null && !this.includeNullPropertyValues) {
// this is not permitted by the spec but makes debugging wide entity types
// much easier.
continue;
}
if (property.getCollectionKind() == EdmProperty.CollectionKind.NONE) {
if (property.getType().isSimple()) {
properties.add(OProperties.simple(property.getName(), (EdmSimpleType<? extends Object>) property.getType(), value));
} else {
// complex.
if (value == null) {
properties.add(OProperties.complex(property.getName(), (EdmComplexType) property.getType(), null));
} else {
Class<?> propType = propertyModel.getPropertyType(property.getName());
InMemoryComplexTypeInfo<?> typeInfo = findComplexTypeInfoForClass(propType);
if (typeInfo == null) {
continue;
}
List<OProperty<?>> cprops = new ArrayList<OProperty<?>>();
addPropertiesFromObject(value, typeInfo.getPropertyModel(), (EdmComplexType) property.getType(), cprops, pathHelper);
properties.add(OProperties.complex(property.getName(), (EdmComplexType) property.getType(), cprops));
}
}
} else {
// collection.
Iterable<?> values = propertyModel.getCollectionValue(obj, property.getName());
OCollection.Builder<OObject> b = OCollections.newBuilder(property.getType());
if (values != null) {
Class<?> propType = propertyModel.getCollectionElementType(property.getName());
InMemoryComplexTypeInfo<?> typeInfo = property.getType().isSimple() ? null : findComplexTypeInfoForClass(propType);
if ((!property.getType().isSimple()) && typeInfo == null) {
continue;
}
for (Object v : values) {
if (property.getType().isSimple()) {
b.add(OSimpleObjects.create((EdmSimpleType<?>) property.getType(), v));
} else {
List<OProperty<?>> cprops = new ArrayList<OProperty<?>>();
addPropertiesFromObject(v, typeInfo.getPropertyModel(), (EdmComplexType) property.getType(), cprops, pathHelper);
b.add(OComplexObjects.create((EdmComplexType) property.getType(), cprops));
}
}
}
properties.add(OProperties.collection(property.getName(),
// hmmmh...is something is wrong here if I have to create a new EdmCollectionType?
new EdmCollectionType(EdmProperty.CollectionKind.Collection,
property.getType()), b.build()));
}
}
dump("done addPropertiesFromObject: " + obj.getClass().getName());
}
protected OEntity toOEntity(EdmEntitySet ees, Object obj, PropertyPathHelper pathHelper) {
InMemoryEntityInfo<?> ei = eis.get(ees.getName());
final List<OLink> links = new ArrayList<OLink>();
final List<OProperty<?>> properties = new ArrayList<OProperty<?>>();
Map<String, Object> keyKVPair = new HashMap<String, Object>();
for (String key : ei.getKeys()) {
Object keyValue = ei.getPropertyModel().getPropertyValue(obj, key);
keyKVPair.put(key, keyValue);
}
// the entity set being queried may contain objects of subtypes of the entity set's type
EdmEntityType edmEntityType = (EdmEntityType) this.getMetadata().findEdmEntityType(namespace + "." + ei.getEntityTypeName());
// "regular" properties
addPropertiesFromObject(obj, ei.getPropertyModel(), edmEntityType, properties, pathHelper);
// navigation properties
for (final EdmNavigationProperty navProp : edmEntityType.getNavigationProperties()) {
if (!pathHelper.isSelected(navProp.getName())) {
continue;
}
if (!pathHelper.isExpanded(navProp.getName())) {
// defer
if (navProp.getToRole().getMultiplicity() == EdmMultiplicity.MANY) {
links.add(OLinks.relatedEntities(null, navProp.getName(), null));
} else {
links.add(OLinks.relatedEntity(null, navProp.getName(), null));
}
} else {
// inline
pathHelper.navigate(navProp.getName());
if (navProp.getToRole().getMultiplicity() == EdmMultiplicity.MANY) {
List<OEntity> relatedEntities = new ArrayList<OEntity>();
EdmEntitySet relEntitySet = null;
for (final Object entity : getRelatedPojos(navProp, obj, ei)) {
if (relEntitySet == null) {
InMemoryEntityInfo<?> oei = this.findEntityInfoForClass(entity.getClass());
relEntitySet = getMetadata().getEdmEntitySet(oei.getEntitySetName());
}
relatedEntities.add(toOEntity(relEntitySet, entity, pathHelper));
}
// relation and href will be filled in later for atom or json
links.add(OLinks.relatedEntitiesInline(null, navProp.getName(), null, relatedEntities));
} else {
final Object entity = ei.getPropertyModel().getPropertyValue(obj, navProp.getName());
OEntity relatedEntity = null;
if (entity != null) {
InMemoryEntityInfo<?> oei = this.findEntityInfoForClass(entity.getClass());
EdmEntitySet relEntitySet = getMetadata().getEdmEntitySet(oei.getEntitySetName());
relatedEntity = toOEntity(relEntitySet, entity, pathHelper);
}
links.add(OLinks.relatedEntityInline(null, navProp.getName(), null, relatedEntity));
}
pathHelper.popPath();
}
}
return OEntities.create(ees, edmEntityType, OEntityKey.create(keyKVPair), properties, links, obj);
}
protected Iterable<?> getRelatedPojos(EdmNavigationProperty navProp, Object srcObject, InMemoryEntityInfo<?> srcInfo) {
if (navProp.getToRole().getMultiplicity() == EdmMultiplicity.MANY) {
Iterable<?> i = srcInfo.getPropertyModel().getCollectionValue(srcObject, navProp.getName());
return i == null ? Collections.EMPTY_LIST : i;
} else {
// can be null
return Collections.singletonList(srcInfo.getPropertyModel().getPropertyValue(srcObject, navProp.getName()));
}
}
private static Predicate1<Object> filterToPredicate(final BoolCommonExpression filter, final PropertyModel properties) {
return new Predicate1<Object>() {
public boolean apply(Object input) {
return InMemoryEvaluation.evaluate(filter, input, properties);
}
};
}
@Override
public EntitiesResponse getEntities(ODataContext context, String entitySetName, final QueryInfo queryInfo) {
final RequestContext rc = RequestContext.newBuilder(RequestType.GetEntities)
.entitySetName(entitySetName)
.entitySet(getMetadata().getEdmEntitySet(entitySetName))
.queryInfo(queryInfo)
.odataContext(context)
.pathHelper(new PropertyPathHelper(queryInfo)).build();
final InMemoryEntityInfo<?> ei = eis.get(entitySetName);
Enumerable<Object> objects = ei.getWithContext == null
? Enumerable.create(ei.get.apply()).cast(Object.class)
: Enumerable.create(ei.getWithContext.apply(rc)).cast(Object.class);
return getEntitiesResponse(rc, rc.getEntitySet(), objects, ei.getPropertyModel());
}
protected EntitiesResponse getEntitiesResponse(final RequestContext rc, final EdmEntitySet targetEntitySet, Enumerable<Object> objects, PropertyModel propertyModel) {
// apply filter
final QueryInfo queryInfo = rc.getQueryInfo();
if (queryInfo != null && queryInfo.filter != null) {
objects = objects.where(filterToPredicate(queryInfo.filter, propertyModel));
}
// compute inlineCount, must be done after applying filter
Integer inlineCount = null;
if (queryInfo != null && queryInfo.inlineCount == InlineCount.ALLPAGES) {
objects = Enumerable.create(objects.toList()); // materialize up front, since we're about to count
inlineCount = objects.count();
}
// apply ordering
if (queryInfo != null && queryInfo.orderBy != null) {
objects = orderBy(objects, queryInfo.orderBy, propertyModel);
}
// work with oentities
Enumerable<OEntity> entities = objects.select(new Func1<Object, OEntity>() {
@Override
public OEntity apply(Object input) {
return toOEntity(targetEntitySet, input, rc.getPathHelper());
}
});
// skip records by $skipToken
if (queryInfo != null && queryInfo.skipToken != null) {
final Boolean[] skipping = new Boolean[] { true };
entities = entities.skipWhile(new Predicate1<OEntity>() {
@Override
public boolean apply(OEntity input) {
if (skipping[0]) {
String inputKey = input.getEntityKey().toKeyString();
if (queryInfo.skipToken.equals(inputKey)) skipping[0] = false;
return true;
}
return false;
}
});
}
// skip records by $skip amount
if (queryInfo != null && queryInfo.skip != null) {
entities = entities.skip(queryInfo.skip);
}
// apply limit
int limit = this.maxResults;
if (queryInfo != null && queryInfo.top != null && queryInfo.top < limit) {
limit = queryInfo.top;
}
entities = entities.take(limit + 1);
// materialize OEntities
List<OEntity> entitiesList = entities.toList();
// determine skipToken if necessary
String skipToken = null;
if (entitiesList.size() > limit) {
entitiesList = Enumerable.create(entitiesList).take(limit).toList();
skipToken = entitiesList.size() == 0 ? null : Enumerable.create(entitiesList).last().getEntityKey().toKeyString();
}
return Responses.entities(entitiesList, targetEntitySet, inlineCount, skipToken);
}
@Override
public CountResponse getEntitiesCount(ODataContext context, String entitySetName, final QueryInfo queryInfo) {
final RequestContext rc = RequestContext.newBuilder(RequestType.GetEntitiesCount)
.entitySetName(entitySetName)
.entitySet(getMetadata().getEdmEntitySet(entitySetName))
.queryInfo(queryInfo)
.odataContext(context)
.build();
final InMemoryEntityInfo<?> ei = eis.get(entitySetName);
final PropertyPathHelper pathHelper = new PropertyPathHelper(queryInfo);
Enumerable<Object> objects = ei.getWithContext == null
? Enumerable.create(ei.get.apply()).cast(Object.class)
: Enumerable.create(ei.getWithContext.apply(rc)).cast(Object.class);
// apply filter
if (queryInfo != null && queryInfo.filter != null) {
objects = objects.where(filterToPredicate(queryInfo.filter, ei.properties));
}
// inlineCount is not applicable to $count queries
if (queryInfo != null && queryInfo.inlineCount == InlineCount.ALLPAGES) {
throw new UnsupportedOperationException("$inlinecount cannot be applied to the resource segment '$count'");
}
// ignore ordering for count
// work with oentities.
Enumerable<OEntity> entities = objects.select(new Func1<Object, OEntity>() {
@Override
public OEntity apply(Object input) {
return toOEntity(rc.getEntitySet(), input, pathHelper);
}
});
// skipToken is not applicable to $count queries
if (queryInfo != null && queryInfo.skipToken != null) {
throw new UnsupportedOperationException("Skip tokens can only be provided for requests that return collections of entities.");
}
// skip records by $skip amount
// http://services.odata.org/Northwind/Northwind.svc/Customers/$count/?$skip=5
if (queryInfo != null && queryInfo.skip != null) {
entities = entities.skip(queryInfo.skip);
}
// apply $top. maxResults is not applicable to $count but $top is.
// http://services.odata.org/Northwind/Northwind.svc/Customers/$count/?$top=55
int limit = Integer.MAX_VALUE;
if (queryInfo != null && queryInfo.top != null && queryInfo.top < limit) {
limit = queryInfo.top;
}
entities = entities.take(limit);
return Responses.count(entities.count());
}
private Enumerable<Object> orderBy(Enumerable<Object> iter, List<OrderByExpression> orderBys, final PropertyModel properties) {
for (final OrderByExpression orderBy : Enumerable.create(orderBys).reverse())
iter = iter.orderBy(new Comparator<Object>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
public int compare(Object o1, Object o2) {
Comparable lhs = (Comparable) InMemoryEvaluation.evaluate(orderBy.getExpression(), o1, properties);
Comparable rhs = (Comparable) InMemoryEvaluation.evaluate(orderBy.getExpression(), o2, properties);
return (orderBy.getDirection() == Direction.ASCENDING ? 1 : -1) * lhs.compareTo(rhs);
}
});
return iter;
}
@Override
public EntityResponse getEntity(ODataContext context, final String entitySetName, final OEntityKey entityKey, final EntityQueryInfo queryInfo) {
PropertyPathHelper pathHelper = new PropertyPathHelper(queryInfo);
RequestContext rc = RequestContext.newBuilder(RequestType.GetEntity)
.entitySetName(entitySetName)
.entitySet(getMetadata()
.getEdmEntitySet(entitySetName))
.entityKey(entityKey)
.queryInfo(queryInfo)
.pathHelper(pathHelper)
.odataContext(context).build();
final Object rt = getEntityPojo(rc);
if (rt == null)
throw new NotFoundException("No entity found in entityset " + entitySetName
+ " for key " + entityKey.toKeyStringWithoutParentheses()
+ " and query info " + queryInfo);
OEntity oe = toOEntity(rc.getEntitySet(), rt, rc.getPathHelper());
return Responses.entity(oe);
}
@Override
public void mergeEntity(ODataContext context, String entitySetName, OEntity entity) {
throw new NotImplementedException();
}
@Override
public void updateEntity(ODataContext context, String entitySetName, OEntity entity) {
throw new NotImplementedException();
}
@Override
public void deleteEntity(ODataContext context, String entitySetName, OEntityKey entityKey) {
throw new NotImplementedException();
}
@Override
public EntityResponse createEntity(ODataContext context, String entitySetName, OEntity entity) {
throw new NotImplementedException();
}
@Override
public EntityResponse createEntity(ODataContext context, String entitySetName, OEntityKey entityKey, String navProp, OEntity entity) {
throw new NotImplementedException();
}
@Override
public BaseResponse getNavProperty(ODataContext context, String entitySetName, OEntityKey entityKey, String navProp, QueryInfo queryInfo) {
RequestContext rc = RequestContext.newBuilder(RequestType.GetNavProperty)
.entitySetName(entitySetName)
.entitySet(getMetadata().getEdmEntitySet(entitySetName))
.entityKey(entityKey)
.navPropName(navProp)
.queryInfo(queryInfo)
.pathHelper(new PropertyPathHelper(queryInfo))
.odataContext(context)
.build();
EdmNavigationProperty navProperty = rc.getEntitySet().getType().findNavigationProperty(navProp);
if (navProperty != null) {
return getNavProperty(navProperty, rc);
}
// not a NavigationProperty:
EdmProperty edmProperty = rc.getEntitySet().getType().findProperty(navProp);
if (edmProperty == null)
throw new NotFoundException("Property " + navProp + " is not found");
// currently only simple types are supported
EdmType edmType = edmProperty.getType();
if (!edmType.isSimple())
throw new NotImplementedException("Only simple types are supported. Property type is '" + edmType.getFullyQualifiedTypeName() + "'");
// get property value...
InMemoryEntityInfo<?> entityInfo = eis.get(entitySetName);
Object target = getEntityPojo(rc);
Object propertyValue = entityInfo.properties.getPropertyValue(target, navProp);
// ... and create OProperty
OProperty<?> property = OProperties.simple(navProp, (EdmSimpleType<?>) edmType, propertyValue);
return Responses.property(property);
}
protected EdmEntitySet findEntitySetForNavProperty(EdmNavigationProperty navProp) {
EdmEntityType et = navProp.getToRole().getType();
// assumes one set per type...
for (EdmEntitySet set : this.getMetadata().getEntitySets()) {
if (set.getType().equals(et)) {
return set;
}
}
return null;
}
/**
* Gets the entity(s) on the target end of a NavigationProperty.
*
* @param navProp the navigation property
* @param rc the request context
* @return a BaseResponse with either a single Entity (can be null) or a set of entities.
*/
protected BaseResponse getNavProperty(EdmNavigationProperty navProp, RequestContext rc) {
// First, get the source POJO.
Object obj = getEntityPojo(rc);
Iterable relatedPojos = this.getRelatedPojos(navProp, obj, this.findEntityInfoForClass(obj.getClass()));
EdmEntitySet targetEntitySet = findEntitySetForNavProperty(navProp);
if (navProp.getToRole().getMultiplicity() == EdmMultiplicity.MANY) {
// apply filter, orderby, etc.
return getEntitiesResponse(rc, targetEntitySet, Enumerable.create(relatedPojos), eis.get(targetEntitySet.getName()).getPropertyModel());
} else {
return Responses.entity(this.toOEntity(targetEntitySet, relatedPojos.iterator().next(), rc.getPathHelper()));
}
}
@Override
public CountResponse getNavPropertyCount(ODataContext context, String entitySetName, OEntityKey entityKey, String navProp, QueryInfo queryInfo) {
throw new NotImplementedException();
}
@Override
public EntityIdResponse getLinks(ODataContext context, OEntityId sourceEntity, String targetNavProp) {
throw new NotImplementedException();
}
@Override
public void createLink(ODataContext context, OEntityId sourceEntity, String targetNavProp, OEntityId targetEntity) {
throw new NotImplementedException();
}
@Override
public void updateLink(ODataContext context, OEntityId sourceEntity, String targetNavProp, OEntityKey oldTargetEntityKey, OEntityId newTargetEntity) {
throw new NotImplementedException();
}
@Override
public void deleteLink(ODataContext context, OEntityId sourceEntity, String targetNavProp, OEntityKey targetEntityKey) {
throw new NotImplementedException();
}
@Override
public BaseResponse callFunction(ODataContext context, EdmFunctionImport name, java.util.Map<String, OFunctionParameter> params, QueryInfo queryInfo) {
throw new NotImplementedException();
}
@Override
public <TExtension extends OExtension<ODataProducer>> TExtension findExtension(Class<TExtension> clazz) {
return null;
}
public static class RequestContext {
public enum RequestType {
GetEntity, GetEntities, GetEntitiesCount, GetNavProperty
};
public final RequestType requestType;
private final String entitySetName;
private EdmEntitySet entitySet;
private final String navPropName;
private final OEntityKey entityKey;
private final QueryInfo queryInfo;
private final PropertyPathHelper pathHelper;
private ODataContext odataContext;
public RequestType getRequestType() {
return requestType;
}
public String getEntitySetName() {
return entitySetName;
}
public EdmEntitySet getEntitySet() {
return entitySet;
}
public String getNavPropName() {
return navPropName;
}
public OEntityKey getEntityKey() {
return entityKey;
}
public QueryInfo getQueryInfo() {
return queryInfo;
}
public PropertyPathHelper getPathHelper() {
return pathHelper;
}
public ODataContext getODataContext() {
return odataContext;
}
public static Builder newBuilder(RequestType requestType) {
return new Builder().requestType(requestType);
}
public static class Builder {
private RequestType requestType;
private String entitySetName;
private EdmEntitySet entitySet;
private String navPropName;
private OEntityKey entityKey;
private QueryInfo queryInfo;
private PropertyPathHelper pathHelper;
private ODataContext odataContext;
public Builder requestType(RequestType value) {
this.requestType = value;
return this;
}
public Builder entitySetName(String value) {
this.entitySetName = value;
return this;
}
public Builder entitySet(EdmEntitySet value) {
this.entitySet = value;
return this;
}
public Builder navPropName(String value) {
this.navPropName = value;
return this;
}
public Builder entityKey(OEntityKey value) {
this.entityKey = value;
return this;
}
public Builder queryInfo(QueryInfo value) {
this.queryInfo = value;
return this;
}
public Builder pathHelper(PropertyPathHelper value) {
this.pathHelper = value;
return this;
}
public Builder odataContext(ODataContext value) {
this.odataContext = value;
return this;
}
public RequestContext build() {
return new RequestContext(requestType, entitySetName, entitySet, navPropName, entityKey, queryInfo, pathHelper, odataContext);
}
}
private RequestContext(RequestType requestType, String entitySetName, EdmEntitySet entitySet,
String navPropName, OEntityKey entityKey, QueryInfo queryInfo, PropertyPathHelper pathHelper,
ODataContext odataContext) {
this.requestType = requestType;
this.entitySetName = entitySetName;
this.entitySet = entitySet;
this.navPropName = navPropName;
this.entityKey = entityKey;
this.queryInfo = queryInfo;
this.pathHelper = pathHelper;
this.odataContext = odataContext;
}
}
/**
* Given an entity set and an entity key, returns the pojo that is that entity instance.
* The default implementation iterates over the entire set of pojos to find the
* desired instance.
*
* @param rc the current ReqeustContext, may be valuable to the ei.getWithContext impl
* @return the pojo
*/
@SuppressWarnings("unchecked")
protected Object getEntityPojo(final RequestContext rc) {
final InMemoryEntityInfo<?> ei = eis.get(rc.getEntitySetName());
final String[] keyList = ei.keys;
Iterable<Object> iter = ei.getWithContext == null ? ((Iterable<Object>) ei.get.apply())
: ((Iterable<Object>) ei.getWithContext.apply(rc));
final Object rt = Enumerable.create(iter).firstOrNull(new Predicate1<Object>() {
public boolean apply(Object input) {
HashMap<String, Object> idObjectMap = ei.id.apply(input);
if (keyList.length == 1) {
Object idValue = rc.getEntityKey().asSingleValue();
return idObjectMap.get(keyList[0]).equals(idValue);
} else if (keyList.length > 1) {
for (String key : keyList) {
Object curValue = null;
Iterator<OProperty<?>> keyProps = rc.getEntityKey().asComplexProperties().iterator();
while (keyProps.hasNext()) {
OProperty<?> keyProp = keyProps.next();
if (keyProp.getName().equalsIgnoreCase(key)) {
curValue = keyProp.getValue();
}
}
if (curValue == null) {
return false;
} else if (!idObjectMap.get(key).equals(curValue)) {
return false;
}
}
return true;
} else {
return false;
}
}
});
return rt;
}
private enum TriggerType {
Before, After
};
protected void fireUnmarshalEvent(Object pojo, OStructuralObject sobj, TriggerType ttype)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
try {
Method m = pojo.getClass().getMethod(ttype == TriggerType.Before ? "beforeOEntityUnmarshal" : "afterOEntityUnmarshal", OStructuralObject.class);
if (m != null) {
m.invoke(pojo, sobj);
}
} catch (NoSuchMethodException ex) {}
}
/**
* Transforms an OComplexObject into a POJO of the given class
*/
public <T> T toPojo(OComplexObject entity, Class<T> pojoClass) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
InMemoryComplexTypeInfo<?> e = this.findComplexTypeInfoForClass(pojoClass);
T pojo = fillInPojo(entity, this.getMetadata().findEdmComplexType(
this.namespace + "." + e.getTypeName()), e.getPropertyModel(), pojoClass);
fireUnmarshalEvent(pojo, entity, TriggerType.After);
return pojo;
}
/**
* Populates a new POJO instance of type pojoClass using data from the given structural object.
*/
protected <T> T fillInPojo(OStructuralObject sobj, EdmStructuralType stype, PropertyModel propertyModel,
Class<T> pojoClass) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
T pojo = pojoClass.newInstance();
fireUnmarshalEvent(pojo, sobj, TriggerType.Before);
for (Iterator<EdmProperty> it = stype.getProperties().iterator(); it.hasNext();) {
EdmProperty property = it.next();
Object value = null;
try {
value = sobj.getProperty(property.getName()).getValue();
} catch (Exception ex) {
// property not define on object
if (property.isNullable()) {
continue;
} else {
throw new RuntimeException("missing required property " + property.getName());
}
}
if (property.getCollectionKind() == EdmProperty.CollectionKind.NONE) {
if (property.getType().isSimple()) {
// call the setter.
propertyModel.setPropertyValue(pojo, property.getName(), value);
} else {
// complex.
// hmmh, value is a Collection<OProperty<?>>...why is it not an OComplexObject.
propertyModel.setPropertyValue(
pojo,
property.getName(),
value == null
? null
: toPojo(
OComplexObjects.create((EdmComplexType) property.getType(), (List<OProperty<?>>) value),
propertyModel.getPropertyType(property.getName())));
}
} else {
// collection.
OCollection<? extends OObject> collection = (OCollection<? extends OObject>) value;
List<Object> pojos = new ArrayList<Object>();
for (OObject item : collection) {
if (collection.getType().isSimple()) {
pojos.add(((OSimpleObject<?>) item).getValue());
} else {
// turn OComplexObject into a pojo
pojos.add(toPojo((OComplexObject) item, propertyModel.getCollectionElementType(property.getName())));
}
}
propertyModel.setCollectionValue(pojo, property.getName(), pojos);
}
}
return pojo;
}
/*
* Design note:
* toPojo is functionality that is useful on both the producer and consumer side.
* I'm putting it in the producer class for now although I suspect there is a
* more elegant design that factors out POJO Classes and PropertyModels into
* some kind of "PojoModelDefinition" class. The producer side would then
* layer and extended definition that defined how the PojoModelDefinition maps
* to entity sets and such.
*
* with all that said, hopefully this start is useful. I'm going to use it on
* our producer side for now to handle createEntity payloads.
*/
/**
* Transforms the given entity into a POJO of type pojoClass.
*/
public <T> T toPojo(OEntity entity, Class<T> pojoClass) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
InMemoryEntityInfo<?> e = this.findEntityInfoForClass(pojoClass);
// so, how is this going to work?
// we have the PropertyModel available. We can lookup the EdmStructuredType if necessary.
EdmEntitySet entitySet = this.getMetadata().findEdmEntitySet(e.getEntitySetName());
T pojo = fillInPojo(entity, entitySet.getType(), e.getPropertyModel(), pojoClass);
// nav props
for (Iterator<EdmNavigationProperty> it = entitySet.getType().getNavigationProperties().iterator(); it.hasNext();) {
EdmNavigationProperty np = it.next();
OLink link = null;
try {
link = entity.getLink(np.getName(), OLink.class);
} catch (IllegalArgumentException nolinkex) {
continue;
}
if (link.isInline()) {
if (link.isCollection()) {
List<Object> pojos = new ArrayList<Object>();
for (OEntity relatedEntity : link.getRelatedEntities()) {
pojos.add(toPojo(relatedEntity, e.getPropertyModel().getCollectionElementType(np.getName())));
}
e.getPropertyModel().setCollectionValue(pojo, np.getName(), pojos);
} else {
e.getPropertyModel().setPropertyValue(pojo, np.getName(),
toPojo(link.getRelatedEntity(), e.getPropertyModel().getPropertyType(np.getName())));
}
} // else ignore deferred links.
}
fireUnmarshalEvent(pojo, entity, TriggerType.After);
return pojo;
}
@Override
public void beginChangeSetBoundary() {
// TODO Auto-generated method stub
}
@Override
public void commitChangeSetBoundary() {
// TODO Auto-generated method stub
}
@Override
public void rollbackChangeSetBoundary() {
// TODO Auto-generated method stub
}
@Override
public EntityResponse createResponseForBatchPostOperation(String entitySetName,
OEntity entity) {
// TODO Auto-generated method stub
return null;
}
@Override
public InputStream getInputStreamForMediaLink(String entitySetName,
OEntityKey entityKey, EntityQueryInfo queryInfo) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateEntityWithStream(String entitySetName, OEntity entity) {
// TODO Auto-generated method stub
}
@Override
public ContextStream getInputStreamForNamedStream(String entitySetName,
OEntityKey entityKey, String columnName, QueryInfo queryInfo) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateEntityWithNamedStream(String entitySetName,
OEntityKey entityKey, String columnName, ContextStream streamContext) {
// TODO Auto-generated method stub
}
}