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 :) } }