package org.odata4j.producer.inmemory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.core4j.Enumerable;
import org.core4j.Predicate1;
import org.odata4j.edm.EdmAssociation;
import org.odata4j.edm.EdmAssociationEnd;
import org.odata4j.edm.EdmAssociationSet;
import org.odata4j.edm.EdmAssociationSetEnd;
import org.odata4j.edm.EdmComplexType;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmDecorator;
import org.odata4j.edm.EdmEntityContainer;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmGenerator;
import org.odata4j.edm.EdmMultiplicity;
import org.odata4j.edm.EdmNavigationProperty;
import org.odata4j.edm.EdmProperty;
import org.odata4j.edm.EdmSchema;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmType;
public class InMemoryEdmGenerator implements EdmGenerator {
private static final boolean DUMP = false;
private static void dump(String msg) {
if (DUMP) System.out.println(msg);
}
private final Logger log = Logger.getLogger(getClass().getName());
private final String namespace;
private final String containerName;
protected final InMemoryTypeMapping typeMapping;
protected final Map<String, InMemoryEntityInfo<?>> eis; // key: EntitySet name
protected final Map<String, InMemoryComplexTypeInfo<?>> complexTypeInfo; // key complex type edm type name
protected final List<EdmComplexType.Builder> edmComplexTypes = new ArrayList<EdmComplexType.Builder>();
// Note, assumes each Java type will only have a single Entity Set defined for it.
protected final Map<Class<?>, String> entitySetNameByClass = new HashMap<Class<?>, String>();
// build these as we go now.
protected Map<String, EdmEntityType.Builder> entityTypesByName = new HashMap<String, EdmEntityType.Builder>();
protected Map<String, EdmEntitySet.Builder> entitySetsByName = new HashMap<String, EdmEntitySet.Builder>();
protected final boolean flatten;
public InMemoryEdmGenerator(String namespace, String containerName, InMemoryTypeMapping typeMapping,
String idPropertyName, Map<String, InMemoryEntityInfo<?>> eis,
Map<String, InMemoryComplexTypeInfo<?>> complexTypes) {
this(namespace, containerName, typeMapping, idPropertyName, eis, complexTypes, true);
}
public InMemoryEdmGenerator(String namespace, String containerName, InMemoryTypeMapping typeMapping,
String idPropertyName, Map<String, InMemoryEntityInfo<?>> eis,
Map<String, InMemoryComplexTypeInfo<?>> complexTypes, boolean flatten) {
this.namespace = namespace;
this.containerName = containerName;
this.typeMapping = typeMapping;
this.eis = eis;
this.complexTypeInfo = complexTypes;
for (Entry<String, InMemoryEntityInfo<?>> e : eis.entrySet()) {
entitySetNameByClass.put(e.getValue().entityClass, e.getKey());
}
this.flatten = flatten;
}
@Override
public EdmDataServices.Builder generateEdm(EdmDecorator decorator) {
List<EdmSchema.Builder> schemas = new ArrayList<EdmSchema.Builder>();
List<EdmEntityContainer.Builder> containers = new ArrayList<EdmEntityContainer.Builder>();
List<EdmAssociation.Builder> associations = new ArrayList<EdmAssociation.Builder>();
List<EdmAssociationSet.Builder> associationSets = new ArrayList<EdmAssociationSet.Builder>();
createComplexTypes(decorator, edmComplexTypes);
// creates id other basic SUPPORTED_TYPE properties(structural) entities
createStructuralEntities(decorator);
// TODO handle back references too
// create hashmaps from sets
createNavigationProperties(associations, associationSets,
entityTypesByName, entitySetsByName, entitySetNameByClass);
EdmEntityContainer.Builder container = EdmEntityContainer.newBuilder().setName(containerName).setIsDefault(true)
.addEntitySets(entitySetsByName.values()).addAssociationSets(associationSets);
containers.add(container);
EdmSchema.Builder schema = EdmSchema.newBuilder().setNamespace(namespace)
.addEntityTypes(entityTypesByName.values())
.addAssociations(associations)
.addEntityContainers(containers)
.addComplexTypes(edmComplexTypes);
addFunctions(schema, container);
if (decorator != null) {
schema.setDocumentation(decorator.getDocumentationForSchema(namespace));
schema.setAnnotations(decorator.getAnnotationsForSchema(namespace));
}
schemas.add(schema);
EdmDataServices.Builder rt = EdmDataServices.newBuilder().addSchemas(schemas);
if (decorator != null)
rt.addNamespaces(decorator.getNamespaces());
return rt;
}
private void createComplexTypes(EdmDecorator decorator, List<EdmComplexType.Builder> complexTypes) {
for (String complexTypeName : complexTypeInfo.keySet()) {
dump("edm complexType: " + complexTypeName);
InMemoryComplexTypeInfo<?> typeInfo = complexTypeInfo.get(complexTypeName);
List<EdmProperty.Builder> properties = new ArrayList<EdmProperty.Builder>();
// no keys
properties.addAll(toEdmProperties(decorator, typeInfo.propertyModel, new String[] {}, complexTypeName));
EdmComplexType.Builder typeBuilder = EdmComplexType.newBuilder()
.setNamespace(namespace)
.setName(typeInfo.typeName)
.addProperties(properties);
if (decorator != null) {
typeBuilder.setDocumentation(decorator.getDocumentationForEntityType(namespace, complexTypeName));
typeBuilder.setAnnotations(decorator.getAnnotationsForEntityType(namespace, complexTypeName));
}
complexTypes.add(typeBuilder);
}
}
private void createStructuralEntities(EdmDecorator decorator) {
// eis contains all of the registered entity sets.
for (String entitySetName : eis.keySet()) {
InMemoryEntityInfo<?> entityInfo = eis.get(entitySetName);
// do we have this type yet?
EdmEntityType.Builder eet = entityTypesByName.get(entityInfo.entityTypeName);
if (eet == null) {
eet = createStructuralType(decorator, entityInfo);
}
EdmEntitySet.Builder ees = EdmEntitySet.newBuilder().setName(entitySetName).setEntityType(eet);
entitySetsByName.put(ees.getName(), ees);
}
}
protected InMemoryEntityInfo<?> findEntityInfoForClass(Class<?> clazz) {
for (InMemoryEntityInfo<?> typeInfo : this.eis.values()) {
if (typeInfo.entityClass.equals(clazz)) {
return typeInfo;
}
}
return null;
}
/*
* contains all generated InMemoryEntityInfos that get created as we walk
* up the inheritance hierarchy and find Java types that are not registered.
*/
private Map<Class<?>, InMemoryEntityInfo<?>> unregisteredEntityInfo =
new HashMap<Class<?>, InMemoryEntityInfo<?>>();
protected InMemoryEntityInfo<?> getUnregisteredEntityInfo(Class<?> clazz, InMemoryEntityInfo<?> subclass) {
InMemoryEntityInfo<?> ei = unregisteredEntityInfo.get(clazz);
if (ei == null) {
ei = new InMemoryEntityInfo();
ei.entityTypeName = clazz.getSimpleName();
ei.keys = subclass.keys;
ei.entityClass = (Class) clazz;
ei.properties = new EnumsAsStringsPropertyModelDelegate(
new BeanBasedPropertyModel(ei.entityClass, this.flatten));
}
return ei;
}
protected EdmEntityType.Builder createStructuralType(EdmDecorator decorator, InMemoryEntityInfo<?> entityInfo) {
List<EdmProperty.Builder> properties = new ArrayList<EdmProperty.Builder>();
Class<?> superClass = flatten ? null : entityInfo.getSuperClass();
properties.addAll(toEdmProperties(decorator, entityInfo.properties, entityInfo.keys, entityInfo.entityTypeName));
EdmEntityType.Builder eet = EdmEntityType.newBuilder()
.setNamespace(namespace)
.setName(entityInfo.entityTypeName)
.setHasStream(entityInfo.hasStream)
.addProperties(properties);
if (superClass == null) {
eet.addKeys(entityInfo.keys);
}
if (decorator != null) {
eet.setDocumentation(decorator.getDocumentationForEntityType(namespace, entityInfo.entityTypeName));
eet.setAnnotations(decorator.getAnnotationsForEntityType(namespace, entityInfo.entityTypeName));
}
entityTypesByName.put(eet.getName(), eet);
EdmEntityType.Builder superType = null;
if (!this.flatten && entityInfo.entityClass.getSuperclass() != null && !entityInfo.entityClass.getSuperclass().equals(Object.class)) {
InMemoryEntityInfo<?> entityInfoSuper = findEntityInfoForClass(entityInfo.entityClass.getSuperclass());
// may have created it along another branch in the hierarchy
if (entityInfoSuper == null) {
// synthesize...
entityInfoSuper = getUnregisteredEntityInfo(entityInfo.entityClass.getSuperclass(), entityInfo);
}
superType = entityTypesByName.get(entityInfoSuper.entityTypeName);
if (superType == null) {
superType = createStructuralType(decorator, entityInfoSuper);
}
}
eet.setBaseType(superType);
return eet;
}
protected void createNavigationProperties(List<EdmAssociation.Builder> associations,
List<EdmAssociationSet.Builder> associationSets,
Map<String, EdmEntityType.Builder> entityTypesByName,
Map<String, EdmEntitySet.Builder> entitySetByName,
Map<Class<?>, String> entityNameByClass) {
for (String entitySetName : eis.keySet()) {
InMemoryEntityInfo<?> ei = eis.get(entitySetName);
Class<?> clazz1 = ei.entityClass;
generateToOneNavProperties(associations, associationSets,
entityTypesByName, entitySetByName, entityNameByClass,
ei.entityTypeName, ei);
generateToManyNavProperties(associations, associationSets,
entityTypesByName, entitySetByName, entityNameByClass,
ei.entityTypeName, ei, clazz1);
}
}
protected void generateToOneNavProperties(
List<EdmAssociation.Builder> associations,
List<EdmAssociationSet.Builder> associationSets,
Map<String, EdmEntityType.Builder> entityTypesByName,
Map<String, EdmEntitySet.Builder> entitySetByName,
Map<Class<?>, String> entityNameByClass,
String entityTypeName,
InMemoryEntityInfo<?> ei) {
Iterable<String> propertyNames = this.flatten ? ei.properties.getPropertyNames() : ei.properties.getDeclaredPropertyNames();
for (String assocProp : propertyNames) {
EdmEntityType.Builder eet1 = entityTypesByName.get(entityTypeName);
Class<?> clazz2 = ei.properties.getPropertyType(assocProp);
String entitySetName2 = entityNameByClass.get(clazz2);
InMemoryEntityInfo<?> ei2 = entitySetName2 == null ? null : eis.get(entitySetName2);
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "genToOnNavProp {0} - {1}({2}) eetName2: {3}", new Object[] { entityTypeName, assocProp, clazz2, entitySetName2 });
}
if (eet1.findProperty(assocProp) != null || ei2 == null)
continue;
EdmEntityType.Builder eet2 = entityTypesByName.get(ei2.entityTypeName);
EdmMultiplicity m1 = EdmMultiplicity.MANY;
EdmMultiplicity m2 = EdmMultiplicity.ONE;
String assocName = String.format("FK_%s_%s", eet1.getName(), eet2.getName());
EdmAssociationEnd.Builder assocEnd1 = EdmAssociationEnd.newBuilder().setRole(eet1.getName())
.setType(eet1).setMultiplicity(m1);
String assocEnd2Name = eet2.getName();
if (assocEnd2Name.equals(eet1.getName()))
assocEnd2Name = assocEnd2Name + "1";
EdmAssociationEnd.Builder assocEnd2 = EdmAssociationEnd.newBuilder().setRole(assocEnd2Name).setType(eet2).setMultiplicity(m2);
EdmAssociation.Builder assoc = EdmAssociation.newBuilder().setNamespace(namespace).setName(assocName).setEnds(assocEnd1, assocEnd2);
associations.add(assoc);
EdmEntitySet.Builder ees1 = entitySetByName.get(eet1.getName());
EdmEntitySet.Builder ees2 = entitySetByName.get(eet2.getName());
if (ees1 == null) {
// entity set name different than entity type name.
ees1 = getEntitySetForEntityTypeName(eet1.getName());
}
if (ees2 == null) {
// entity set name different than entity type name.
ees2 = getEntitySetForEntityTypeName(eet2.getName());
}
EdmAssociationSet.Builder eas = EdmAssociationSet.newBuilder().setName(assocName).setAssociation(assoc).setEnds(
EdmAssociationSetEnd.newBuilder().setRole(assocEnd1).setEntitySet(ees1),
EdmAssociationSetEnd.newBuilder().setRole(assocEnd2).setEntitySet(ees2));
associationSets.add(eas);
EdmNavigationProperty.Builder np = EdmNavigationProperty.newBuilder(assocProp)
.setRelationship(assoc).setFromTo(assoc.getEnd1(), assoc.getEnd2());
eet1.addNavigationProperties(np);
}
}
protected EdmEntitySet.Builder getEntitySetForEntityTypeName(String entityTypeName) {
for (InMemoryEntityInfo<?> ti : eis.values()) {
if (ti.entityTypeName.equals(entityTypeName)) {
return entitySetsByName.get(ti.entitySetName);
}
}
return null;
}
protected void generateToManyNavProperties(List<EdmAssociation.Builder> associations,
List<EdmAssociationSet.Builder> associationSets,
Map<String, EdmEntityType.Builder> entityTypesByName,
Map<String, EdmEntitySet.Builder> entitySetByName,
Map<Class<?>, String> entityNameByClass,
String entityTypeName,
InMemoryEntityInfo<?> ei,
Class<?> clazz1) {
Iterable<String> collectionNames = this.flatten ? ei.properties.getCollectionNames() : ei.properties.getDeclaredCollectionNames();
for (String assocProp : collectionNames) {
final EdmEntityType.Builder eet1 = entityTypesByName.get(entityTypeName);
Class<?> clazz2 = ei.properties.getCollectionElementType(assocProp);
String entitySetName2 = entityNameByClass.get(clazz2);
InMemoryEntityInfo<?> class2eiInfo = entitySetName2 == null ? null : eis.get(entitySetName2);
if (class2eiInfo == null)
continue;
final EdmEntityType.Builder eet2 = entityTypesByName.get(class2eiInfo.entityTypeName);
try {
EdmAssociation.Builder assoc = Enumerable.create(associations).firstOrNull(new Predicate1<EdmAssociation.Builder>() {
public boolean apply(EdmAssociation.Builder input) {
return input.getEnd1().getType().equals(eet2) && input.getEnd2().getType().equals(eet1);
}
});
EdmAssociationEnd.Builder fromRole, toRole;
if (assoc == null) {
//no association already exists
EdmMultiplicity m1 = EdmMultiplicity.ZERO_TO_ONE;
EdmMultiplicity m2 = EdmMultiplicity.MANY;
//find ei info of class2
for (String tmp : class2eiInfo.properties.getCollectionNames()) {
//class2 has a ref to class1
//Class<?> tmpc = class2eiInfo.properties.getCollectionElementType(tmp);
if (clazz1 == class2eiInfo.properties.getCollectionElementType(tmp)) {
m1 = EdmMultiplicity.MANY;
m2 = EdmMultiplicity.MANY;
break;
}
}
String assocName = String.format("FK_%s_%s", eet1.getName(), eet2.getName());
EdmAssociationEnd.Builder assocEnd1 = EdmAssociationEnd.newBuilder().setRole(eet1.getName()).setType(eet1).setMultiplicity(m1);
String assocEnd2Name = eet2.getName();
if (assocEnd2Name.equals(eet1.getName()))
assocEnd2Name = assocEnd2Name + "1";
EdmAssociationEnd.Builder assocEnd2 = EdmAssociationEnd.newBuilder().setRole(assocEnd2Name).setType(eet2).setMultiplicity(m2);
assoc = EdmAssociation.newBuilder().setNamespace(namespace).setName(assocName).setEnds(assocEnd1, assocEnd2);
associations.add(assoc);
EdmEntitySet.Builder ees1 = entitySetByName.get(eet1.getName());
EdmEntitySet.Builder ees2 = entitySetByName.get(eet2.getName());
if (ees1 == null) {
// entity set name different than entity type name.
ees1 = getEntitySetForEntityTypeName(eet1.getName());
}
if (ees2 == null) {
// entity set name different than entity type name.
ees2 = getEntitySetForEntityTypeName(eet2.getName());
}
EdmAssociationSet.Builder eas = EdmAssociationSet.newBuilder().setName(assocName).setAssociation(assoc).setEnds(
EdmAssociationSetEnd.newBuilder().setRole(assocEnd1).setEntitySet(ees1),
EdmAssociationSetEnd.newBuilder().setRole(assocEnd2).setEntitySet(ees2));
associationSets.add(eas);
fromRole = assoc.getEnd1();
toRole = assoc.getEnd2();
} else {
fromRole = assoc.getEnd2();
toRole = assoc.getEnd1();
}
EdmNavigationProperty.Builder np = EdmNavigationProperty.newBuilder(assocProp).setRelationship(assoc).setFromTo(fromRole, toRole);
eet1.addNavigationProperties(np);
} catch (Exception e) {
// hmmh...I guess the build() will fail later
log.log(Level.WARNING, "Exception building Edm associations: " + entityTypeName + "," + clazz1 + " set: " + ei.entitySetName
+ " -> " + assocProp, e);
}
}
}
protected EdmComplexType.Builder findComplexTypeBuilder(String typeName) {
String fqName = this.namespace + "." + typeName;
for (EdmComplexType.Builder builder : this.edmComplexTypes) {
if (builder.getFullyQualifiedTypeName().equals(fqName)) {
return builder;
}
}
return null;
}
protected EdmComplexType.Builder findComplexTypeForClass(Class<?> clazz) {
for (InMemoryComplexTypeInfo<?> typeInfo : this.complexTypeInfo.values()) {
if (typeInfo.entityClass.equals(clazz)) {
// the typeName defines the edm type name
return findComplexTypeBuilder(typeInfo.typeName);
}
}
return null;
}
private Collection<EdmProperty.Builder> toEdmProperties(
EdmDecorator decorator,
PropertyModel model,
String[] keys,
String structuralTypename) {
List<EdmProperty.Builder> rt = new ArrayList<EdmProperty.Builder>();
Set<String> keySet = Enumerable.create(keys).toSet();
Iterable<String> propertyNames = this.flatten ? model.getPropertyNames() : model.getDeclaredPropertyNames();
for (String propName : propertyNames) {
dump("edm property: " + propName);
Class<?> propType = model.getPropertyType(propName);
EdmType type = typeMapping.findEdmType(propType);
EdmComplexType.Builder typeBuilder = null;
if (type == null) {
typeBuilder = findComplexTypeForClass(propType);
}
dump("edm property: " + propName + " type: " + type + " builder: " + typeBuilder);
if (type == null && typeBuilder == null) {
continue;
}
EdmProperty.Builder ep = EdmProperty
.newBuilder(propName)
.setNullable(!keySet.contains(propName));
if (type != null) {
ep.setType(type);
}
else {
ep.setType(typeBuilder);
}
if (decorator != null) {
ep.setDocumentation(decorator.getDocumentationForProperty(namespace, structuralTypename, propName));
ep.setAnnotations(decorator.getAnnotationsForProperty(namespace, structuralTypename, propName));
}
rt.add(ep);
}
// collections of primitives and complex types
propertyNames = this.flatten ? model.getCollectionNames() : model.getDeclaredCollectionNames();
for (String collectionPropName : propertyNames) {
Class<?> collectionElementType = model.getCollectionElementType(collectionPropName);
if (entitySetNameByClass.get(collectionElementType) != null) {
// this will be a nav prop
continue;
}
EdmType type = typeMapping.findEdmType(collectionElementType);
EdmType.Builder typeBuilder = null;
if (type == null) {
typeBuilder = findComplexTypeForClass(collectionElementType);
} else {
typeBuilder = EdmSimpleType.newBuilder(type);
}
if (typeBuilder == null) {
continue;
}
// either a simple or complex type.
EdmProperty.Builder ep = EdmProperty.newBuilder(collectionPropName)
.setNullable(true)
.setCollectionKind(EdmProperty.CollectionKind.Collection)
.setType(typeBuilder);
if (decorator != null) {
ep.setDocumentation(decorator.getDocumentationForProperty(namespace, structuralTypename, collectionPropName));
ep.setAnnotations(decorator.getAnnotationsForProperty(namespace, structuralTypename, collectionPropName));
}
rt.add(ep);
}
return rt;
}
/**
* get the Edm namespace
* @return the Edm namespace
*/
public String getNamespace() {
return namespace;
}
/**
* provides an override point for applications to add application specific
* EdmFunctions to their producer.
* @param schema the EdmSchema.Builder
* @param container the EdmEntityContainer.Builder
*/
protected void addFunctions(EdmSchema.Builder schema, EdmEntityContainer.Builder container) {
// overridable :)
}
}