package org.odata4j.producer.edm; import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Stack; import org.core4j.Enumerable; import org.odata4j.core.NamespacedAnnotation; import org.odata4j.core.OCollection; import org.odata4j.core.OCollection.Builder; 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.OProperties; import org.odata4j.core.OProperty; import org.odata4j.edm.EdmAnnotationAttribute; 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.EdmItem; import org.odata4j.edm.EdmProperty; import org.odata4j.edm.EdmProperty.CollectionKind; import org.odata4j.edm.EdmSchema; import org.odata4j.edm.EdmStructuralType; import org.odata4j.edm.EdmType; import org.odata4j.exceptions.NotFoundException; import org.odata4j.exceptions.NotImplementedException; import org.odata4j.format.xml.EdmxFormatWriter; 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.ExpressionEvaluator; import org.odata4j.producer.ExpressionEvaluator.VariableResolver; import org.odata4j.producer.ODataContext; import org.odata4j.producer.ODataProducer; import org.odata4j.producer.PropertyPath; import org.odata4j.producer.PropertyPathHelper; import org.odata4j.producer.QueryInfo; import org.odata4j.producer.Responses; /** * A producer for $metadata. * * <p>This is somewhat brute-forceish. There is maybe a world where an enhanced * InMemoryProducer and the org.odata4j.edm pojos together are sufficient to * implement much of this...I'm not sure. */ public class MetadataProducer implements ODataProducer { /** * Return this from your decorators annotation override method and the * annotation will be removed. */ public static final Object REMOVE_ANNOTATION_OVERRIDE = new Object(); public static class CustomOptions { /** * Locale will be parsed as a locale string ala java.util.Locale. */ public static final String Locale = "locale"; /** * If true, a query for a structural type will return a flattened * representation of the type..i.e. it will contain inherited properties * as well. */ public static final String Flatten = "flatten"; } private final ODataProducer dataProducer; private final EdmDataServices edm; private final EdmDecorator decorator; /** * Creates a new MetadataProducer. * * @param dataProducer the data producer who defines the $metadata we will expose * @param edmDecorator an optional decorator. the decorator provides * context for evaluating $filter expressions, custom * runtime overrides for annotation values and overrides * for other metadata properties */ public MetadataProducer(ODataProducer dataProducer, EdmDecorator edmDecorator) { this.dataProducer = dataProducer; this.decorator = edmDecorator; edm = new MetadataEdmGenerator().generateEdm(edmDecorator).build(); } /** Get the EDM model that this producer exposes. */ public EdmDataServices getModel() { return this.dataProducer.getMetadata(); } /** Get the EDM that defines the queryable metadata, the meta-EDM */ @Override public EdmDataServices getMetadata() { return edm; } // request context protected class Context implements VariableResolver { public Context(String entitySetName, QueryInfo queryInfo) { this(entitySetName, queryInfo, null); } public Context(String entitySetName, QueryInfo queryInfo, OEntityKey key) { this.entitySet = edm.findEdmEntitySet(entitySetName); this.queryInfo = queryInfo; this.entityKey = key; setLocale(); pathHelper = new PropertyPathHelper(queryInfo.select, queryInfo.expand, getCustomOption(PropertyPathHelper.OptionSelectR), getCustomOption(PropertyPathHelper.OptionExpandR)); flatten = getCustomBoolean(CustomOptions.Flatten, false); } protected final String getCustomOption(String key) { if (queryInfo != null && queryInfo.customOptions != null) return queryInfo.customOptions.get(key); return null; } protected final boolean getCustomBoolean(String key, boolean fallback) { String s = getCustomOption(key); return s == null ? fallback : Boolean.parseBoolean(s); } protected final void setLocale() { String lc = getCustomOption(CustomOptions.Locale); if (lc != null) { Locale l = parseLocale(lc); if (l != null) { locale = l; } } } public Locale parseLocale(String lstring) { String[] s = lstring.split("_", 3); if (1 == s.length) { return new Locale(s[0]); } else if (2 == s.length) { return new Locale(s[0], s[1]); } else if (3 == s.length) { return new Locale(s[0], s[1], s[2]); } else { return null; } } public void addEntity(OEntity e) { entities.add(e); } EdmEntitySet entitySet; QueryInfo queryInfo; OEntityKey entityKey; Locale locale = Locale.ENGLISH; PropertyPathHelper pathHelper; List<OEntity> entities = new LinkedList<OEntity>(); boolean flatten = false; // flatten properties for structural types @Override public Object resolveVariable(String path) { PropertyPath p = new PropertyPath(path); EdmItem i = resolverContext.isEmpty() ? null : this.peekResolver(); if (i != null) { if (i instanceof EdmStructuralType) { return resolveStructuralTypeVariable((EdmStructuralType) i, p); } else if (i instanceof EdmProperty) { return resolvePropertyVariable((EdmProperty) i, p); } } throw new NotImplementedException("unhandled EdmItem type in resolveVariable: " + (i == null ? "null" : i.getClass().getName())); } private Object resolveStructuralTypeVariable(EdmStructuralType et, PropertyPath path) { if (path.getNComponents() == 1) { String name = path.getLastComponent(); if (Edm.EntityType.Abstract.equals(name)) { return et.getIsAbstract() == null ? false : et.getIsAbstract(); } else if (Edm.EntityType.BaseType.equals(name)) { return et.getBaseType() == null ? null : et.getBaseType().getFullyQualifiedTypeName(); } else if (Edm.EntityType.Name.equals(name)) { return et.getName(); } else if (Edm.EntityType.Namespace.equals(name)) { return et.getNamespace(); } else { // see if our decorator has an annotation that works try { return decorator.resolveStructuralTypeProperty(et, path); } catch (Exception ex) { throw new RuntimeException("EdmEntityType property " + name + " not found"); } } } else { String navProp = path.getFirstComponent(); // --to 1 props only // TODO: superclass maybe throw new RuntimeException("EdmEntityType navigation property " + navProp + " not found or not supported"); } } private Object resolvePropertyVariable(EdmProperty prop, PropertyPath path) { if (path.getNComponents() == 1) { String name = path.getLastComponent(); if (Edm.Property.DefaultValue.equals(name)) { return prop.getDefaultValue(); } else if (Edm.Property.CollectionKind.equals(name)) { return prop.getCollectionKind().toString(); } else if (Edm.Property.EntityTypeName.equals(name)) { return prop.getDeclaringType().getName(); } else if (Edm.Property.FixedLength.equals(name)) { return prop.getFixedLength() != null ? prop.getFixedLength().toString() : null; } else if (Edm.Property.MaxLength.equals(name)) { return prop.getMaxLength() != null ? prop.getMaxLength().toString() : null; } else if (Edm.Property.Name.equals(name)) { return prop.getName(); } else if (Edm.Property.Namespace.equals(name)) { return prop.getDeclaringType().getNamespace(); } else if (Edm.Property.Nullable.equals(name)) { return prop.isNullable() ? "true" : "false"; } else if (Edm.Property.Type.equals(name)) { return prop.getType().getFullyQualifiedTypeName(); } else if (Edm.Property.Precision.equals(name)) { return prop.getPrecision() == null ? null : prop.getPrecision().toString(); } else if (Edm.Property.Scale.equals(name)) { return prop.getScale() == null ? null : prop.getScale().toString(); } else if (decorator != null) { try { return decorator.resolvePropertyProperty(prop, path); } catch (IllegalArgumentException e) { throw new RuntimeException("EdmProperty property path " + path + " not found"); } } else { throw new RuntimeException("EdmProperty property " + name + " not found"); } } else { String navProp = path.getFirstComponent(); // --to 1 props only // TODO: class maybe throw new RuntimeException("EdmProperty navigation property " + navProp + " not found or not supported"); } } private Stack<EdmItem> resolverContext = new Stack<EdmItem>(); private void pushResolver(EdmItem item) { resolverContext.push(item); } private EdmItem peekResolver() { return resolverContext.peek(); } private void popResolver() { resolverContext.pop(); } } @Override public EntitiesResponse getEntities(ODataContext context, String entitySetName, QueryInfo queryInfo) { Context c = new Context(entitySetName, queryInfo); if (entitySetName.equals(Edm.EntitySets.Schemas)) { getSchemas(c); } else if (entitySetName.equals(Edm.EntitySets.EntityTypes)) { getEntityTypes(c, false); } else if (entitySetName.equals(Edm.EntitySets.RootEntityTypes)) { getEntityTypes(c, true); } else if (entitySetName.equals(Edm.EntitySets.ComplexTypes)) { getComplexTypes(c, false); } else if (entitySetName.equals(Edm.EntitySets.RootComplexTypes)) { getComplexTypes(c, true); } else if (entitySetName.equals(Edm.EntitySets.Properties)) { getProperties(c); } else { throw new NotFoundException("EntitySet " + entitySetName + " not found"); } return Responses.entities(c.entities, c.entitySet, null, // inline count null); // skip token. } protected void getSchemas(Context c) { EdmDataServices ds = dataProducer.getMetadata(); ExpressionEvaluator f = null; if (c.queryInfo != null && c.queryInfo.filter != null) { f = new ExpressionEvaluator(c); // , c.queryInfo.filter); // TODO add resolver } for (EdmSchema schema : ds.getSchemas()) { boolean add = true; if (f != null) { c.pushResolver(schema); add = f.evaluate(c.queryInfo.filter); } if (add) { c.addEntity(getSchema(c, schema)); } if (f != null) { c.popResolver(); } } } protected OEntity getSchema(Context c, EdmSchema schema) { List<OProperty<?>> props = new ArrayList<OProperty<?>>(); if (c.pathHelper.isSelected(Edm.Schema.Namespace)) { props.add(OProperties.string(Edm.Schema.Namespace, schema.getNamespace())); } if (schema.getAlias() != null && c.pathHelper.isSelected(Edm.Schema.Alias)) { props.add(OProperties.string(Edm.Schema.Alias, schema.getAlias())); } // links List<OLink> links = new LinkedList<OLink>(); // --------------- ComplexTypes ------------------------------------- if (c.pathHelper.isSelected(Edm.Schema.NavProps.ComplexTypes)) { if (c.pathHelper.isExpanded(Edm.Schema.NavProps.ComplexTypes)) { c.pathHelper.navigate(Edm.Schema.NavProps.ComplexTypes); List<OEntity> complexTypes = new ArrayList<OEntity>(schema.getComplexTypes().size()); for (EdmComplexType ct : schema.getComplexTypes()) { complexTypes.add(this.getStructuralType(c, ct)); } c.pathHelper.popPath(); links.add(OLinks.relatedEntitiesInline(null, Edm.Schema.NavProps.ComplexTypes, null, complexTypes)); } else { // deferred links.add(OLinks.relatedEntities(null, Edm.Schema.NavProps.ComplexTypes, null)); } } // else not selected // --------------- EntityTypes ------------------------------------- if (c.pathHelper.isSelected(Edm.Schema.NavProps.EntityTypes)) { if (c.pathHelper.isExpanded(Edm.Schema.NavProps.EntityTypes)) { c.pathHelper.navigate(Edm.Schema.NavProps.EntityTypes); List<OEntity> etypes = new ArrayList<OEntity>(schema.getEntityTypes().size()); for (EdmEntityType et : schema.getEntityTypes()) { etypes.add(this.getStructuralType(c, et)); } c.pathHelper.popPath(); links.add(OLinks.relatedEntitiesInline(null, Edm.Schema.NavProps.EntityTypes, null, etypes)); } else { // deferred links.add(OLinks.relatedEntities(null, Edm.Schema.NavProps.EntityTypes, null)); } } // else not selected // not sure why CSDL doesn't have documentation on a schema element //addDocumenation(c, schema, props); addAnnotationProperties(c, schema, props); return OEntities.create(c.entitySet, OEntityKey.create(Edm.Schema.Namespace, schema.getNamespace()), // OEntityKey entityKey, props, links); } protected void getEntityTypes(Context c, boolean isRoot) { EdmDataServices ds = dataProducer.getMetadata(); ExpressionEvaluator f = null; if (c.queryInfo != null && c.queryInfo.filter != null) { f = new ExpressionEvaluator(c); // , c.queryInfo.filter); // TODO add resolver } for (EdmEntityType et : ds.getEntityTypes()) { if ((isRoot && et.isRootType()) || (!isRoot)) { boolean add = true; if (f != null) { c.pushResolver(et); add = f.evaluate(c.queryInfo.filter); } if (add) { c.addEntity(getStructuralType(c, et)); } if (f != null) { c.popResolver(); } } } } private OEntity getStructuralType(Context c, EdmStructuralType st) { List<OProperty<?>> props = new ArrayList<OProperty<?>>(); if (c.pathHelper.isSelected(Edm.StructuralType.Name)) { props.add(OProperties.string(Edm.StructuralType.Name, st.getName())); } if (c.pathHelper.isSelected(Edm.StructuralType.Namespace)) { props.add(OProperties.string(Edm.StructuralType.Namespace, st.getNamespace())); } if (st.getIsAbstract() != null && c.pathHelper.isSelected(Edm.StructuralType.Abstract)) { props.add(OProperties.boolean_(Edm.StructuralType.Abstract, st.getIsAbstract())); } if (st.getBaseType() != null) { if (c.pathHelper.isSelected(Edm.StructuralType.BaseType)) { props.add(OProperties.string(Edm.StructuralType.BaseType, st.getBaseType().getFullyQualifiedTypeName())); } } else if (st instanceof EdmEntityType && c.pathHelper.isSelected(Edm.EntityType.Key)) { // all root types must specify a key /* * Entity.Key isA EntityKey * EntityKey.Keys isA Collection(PropertyRef) * PropertyRef.Name isA String */ EdmComplexType propRefType = edm.findEdmComplexType(Edm.PropertyRef.fqName()); EdmComplexType entityKeyType = edm.findEdmComplexType(Edm.EntityKey.fqName()); Builder<OComplexObject> builder = OCollections.newBuilder(propRefType); for (String key : ((EdmEntityType) st).getKeys()) { List<OProperty<?>> refProps = new ArrayList<OProperty<?>>(); refProps.add(OProperties.string(Edm.PropertyRef.Name, key)); builder.add(OComplexObjects.create(propRefType, refProps)); } List<OProperty<?>> keyProps = new ArrayList<OProperty<?>>(); EdmProperty keysProp = entityKeyType.findProperty(Edm.EntityKey.Keys); EdmType collectionItemType = entityKeyType.findProperty(Edm.EntityKey.Keys).getType(); keyProps.add(OProperties.collection(Edm.EntityKey.Keys, new EdmCollectionType(keysProp.getCollectionKind(), collectionItemType), builder.build())); OComplexObject key = OComplexObjects.create(entityKeyType, keyProps); props.add(OProperties.complex(Edm.EntityType.Key, entityKeyType, key.getProperties())); } // links List<OLink> links = new LinkedList<OLink>(); // --------------- Properties ------------------------------------- if (c.pathHelper.isSelected(Edm.StructuralType.NavProps.Properties)) { if (c.pathHelper.isExpanded(Edm.StructuralType.NavProps.Properties)) { c.pathHelper.navigate(Edm.StructuralType.NavProps.Properties); List<OEntity> properties = new ArrayList<OEntity>(st.getDeclaredProperties().count()); addProperties(st, st, properties, c); c.pathHelper.popPath(); links.add(OLinks.relatedEntitiesInline(null, Edm.StructuralType.NavProps.Properties, null, properties)); } else { // deferred links.add(OLinks.relatedEntities(null, Edm.StructuralType.NavProps.Properties, null)); } } // else not selected // --------------- SuperType------------------------------------- if (c.pathHelper.isSelected(Edm.StructuralType.NavProps.SuperType)) { if (c.pathHelper.isExpanded(Edm.StructuralType.NavProps.SuperType)) { OEntity superType = null; if (st.getBaseType() != null) { c.pathHelper.navigate(Edm.StructuralType.NavProps.SuperType); superType = this.getStructuralType(c, st.getBaseType()); c.pathHelper.popPath(); } links.add(OLinks.relatedEntityInline(null, Edm.StructuralType.NavProps.SuperType, null, superType)); } else { // deferred links.add(OLinks.relatedEntities(null, Edm.StructuralType.NavProps.SuperType, null)); } } // else not selected // --------------- SubTypes------------------------------------- if (c.pathHelper.isSelected(Edm.StructuralType.NavProps.SubTypes)) { if (c.pathHelper.isExpanded(Edm.StructuralType.NavProps.SubTypes)) { List<EdmStructuralType> stypes = Enumerable.create(dataProducer.getMetadata().getSubTypes(st)).toList(); List<OEntity> subtypes = new ArrayList<OEntity>(stypes.size()); // these are not root types... EdmEntitySet baseSet = c.entitySet; if (baseSet.getName().equals(Edm.EntitySets.RootEntityTypes)) { c.entitySet = edm.findEdmEntitySet(Edm.EntitySets.EntityTypes); } else if (baseSet.getName().equals(Edm.EntitySets.RootComplexTypes)) { c.entitySet = edm.findEdmEntitySet(Edm.EntitySets.ComplexTypes); } c.pathHelper.navigate(Edm.StructuralType.NavProps.SubTypes); for (EdmStructuralType subtype : stypes) { subtypes.add(this.getStructuralType(c, subtype)); } c.pathHelper.popPath(); links.add(OLinks.relatedEntitiesInline(null, Edm.StructuralType.NavProps.SubTypes, null, subtypes)); c.entitySet = baseSet; } else { // deferred links.add(OLinks.relatedEntities(null, Edm.StructuralType.NavProps.SubTypes, null)); } } // else not selected addDocumenation(c, st, props); addAnnotationProperties(c, st, props); return OEntities.create(c.entitySet, OEntityKey.create(Edm.StructuralType.Namespace, st.getNamespace(), Edm.StructuralType.Name, st.getName()), // OEntityKey entityKey, props, links); } private void addProperties(EdmStructuralType queryType, EdmStructuralType st, List<OEntity> props, Context c) { for (EdmProperty p : st.getDeclaredProperties()) { props.add(getProperty(queryType, st, p, c)); } if (c.flatten && st.getBaseType() != null) { addProperties(queryType, st.getBaseType(), props, c); } } private void addDocumenation(Context c, EdmItem item, List<OProperty<?>> props) { if (item.getDocumentation() != null && (item.getDocumentation().getSummary() != null || item.getDocumentation().getLongDescription() != null) && c.pathHelper.isSelected(Edm.Documentation.name())) { List<OProperty<?>> docProps = new ArrayList<OProperty<?>>(); EdmComplexType docType = edm.findEdmComplexType(Edm.Documentation.fqName()); if (item.getDocumentation().getSummary() != null) { docProps.add(OProperties.string(Edm.Documentation.Summary, item.getDocumentation().getSummary())); } if (item.getDocumentation().getLongDescription() != null) { docProps.add(OProperties.string(Edm.Documentation.LongDescription, item.getDocumentation().getLongDescription())); } // OComplexObject doc = OComplexObjects.create(docType, docProps); props.add(OProperties.complex(Edm.Documentation.class.getSimpleName(), docType, docProps)); } } private void addAnnotationProperties(Context c, EdmItem item, List<OProperty<?>> props) { if (item.getAnnotations() != null) { for (NamespacedAnnotation<?> a : item.getAnnotations()) { if (a.getValue() != null) { /* * property naming: so...annotations live in a namespace. JSON doesn't have the concept of namespaces, * I think <prefix>_<propname> makes the most sense. We *could* use <prefix>:<propname> if we quoted the * json key..that isn't a universally supported thing though. * The issue gets weird with Atom. The OData spec says that each sub-element of <m:properties> must live * in the data service namespace....I guess I'll just use the same JSON name....this of course makes * the queryable metadata property names different than the names one would see from $metadata...not * sure we can do anything about that. */ String propName = a.getNamespace().getPrefix() + "_" + a.getName(); if (c.pathHelper.isSelected(propName)) { Object override = this.decorator != null ? this.decorator.getAnnotationValueOverride(item, a, c.flatten, c.locale, c.queryInfo == null ? null : c.queryInfo.customOptions) : null; if (override != MetadataProducer.REMOVE_ANNOTATION_OVERRIDE) { Object ov = override == null ? a.getValue() : override; if (a instanceof EdmAnnotationAttribute) { props.add(OProperties.string(propName, ov.toString())); } else if (ov instanceof OComplexObject) { OComplexObject co = (OComplexObject) ov; props.add(OProperties.complex(propName, (EdmComplexType) co.getType(), co.getProperties())); } else if (ov instanceof OCollection) { OCollection<?> co = (OCollection<?>) ov; props.add(OProperties.collection(propName, new EdmCollectionType(CollectionKind.Bag, co.getType()), co)); } } } } } } } private OEntity getProperty(EdmStructuralType queryType, EdmStructuralType et, EdmProperty p, Context c) { List<OProperty<?>> props = new ArrayList<OProperty<?>>(); if (c.pathHelper.isSelected(Edm.Property.Namespace)) { props.add(OProperties.string(Edm.Property.Namespace, et.getNamespace())); } if (c.pathHelper.isSelected(Edm.Property.EntityTypeName)) { props.add(OProperties.string(Edm.Property.EntityTypeName, et.getName())); } if (c.pathHelper.isSelected(Edm.Property.Name)) { props.add(OProperties.string(Edm.Property.Name, p.getName())); } if (c.pathHelper.isSelected(Edm.Property.Type)) { props.add(OProperties.string(Edm.Property.Type, p.getType().getFullyQualifiedTypeName())); } if (c.pathHelper.isSelected(Edm.Property.Nullable)) { props.add(OProperties.boolean_(Edm.Property.Nullable, p.isNullable())); } if (p.getDefaultValue() != null && c.pathHelper.isSelected(Edm.Property.DefaultValue)) { props.add(OProperties.string(Edm.Property.DefaultValue, p.getDefaultValue())); } if (p.getMaxLength() != null && c.pathHelper.isSelected(Edm.Property.MaxLength)) { props.add(OProperties.int32(Edm.Property.MaxLength, p.getMaxLength())); } if (p.getFixedLength() != null && c.pathHelper.isSelected(Edm.Property.FixedLength)) { props.add(OProperties.boolean_(Edm.Property.FixedLength, p.getFixedLength())); } if (p.getPrecision() != null && c.pathHelper.isSelected(Edm.Property.Precision)) { props.add(OProperties.int32(Edm.Property.Precision, p.getPrecision())); } if (p.getScale() != null && c.pathHelper.isSelected(Edm.Property.Scale)) { props.add(OProperties.int32(Edm.Property.Scale, p.getScale())); } if (p.getUnicode() != null && c.pathHelper.isSelected(Edm.Property.Unicode)) { props.add(OProperties.boolean_(Edm.Property.Unicode, p.getUnicode())); } // TODO: collation // TODO: ConcurrencyMode if (p.getCollectionKind() != CollectionKind.NONE) { props.add(OProperties.string(Edm.Property.CollectionKind, p.getCollectionKind().toString())); } this.addDocumenation(c, p, props); addAnnotationProperties(c, p, props); EdmEntitySet entitySet = edm.findEdmEntitySet(Edm.EntitySets.Properties); if (this.decorator != null) { this.decorator.decorateEntity(entitySet, p, queryType, props, c.flatten, c.locale, c.queryInfo != null ? c.queryInfo.customOptions : null); } return OEntities.create(entitySet, OEntityKey.create(Edm.Property.Namespace, et.getNamespace(), Edm.Property.EntityTypeName, et.getName(), Edm.Property.Name, p.getName()), props, Collections.<OLink> emptyList()); } protected void getComplexTypes(Context c, boolean isRoot) { EdmDataServices ds = dataProducer.getMetadata(); ExpressionEvaluator f = null; if (c.queryInfo != null && c.queryInfo.filter != null) { f = new ExpressionEvaluator(c); // , c.queryInfo.filter); // TODO add resolver } for (EdmComplexType ct : ds.getComplexTypes()) { if ((isRoot && ct.isRootType()) || (!isRoot)) { boolean add = true; if (f != null) { c.pushResolver(ct); add = f.evaluate(c.queryInfo.filter); } if (add) { c.addEntity(getStructuralType(c, ct)); } if (f != null) { c.popResolver(); } } } } protected void getProperties(Context c) { EdmDataServices ds = dataProducer.getMetadata(); ExpressionEvaluator f = null; if (c.queryInfo != null && c.queryInfo.filter != null) { f = new ExpressionEvaluator(c); } for (EdmComplexType ct : ds.getComplexTypes()) { if (ct.isRootType()) { addStructuralTypeProperties(c, ct, f); } } for (EdmEntityType ct : ds.getEntityTypes()) { if (ct.isRootType()) { addStructuralTypeProperties(c, ct, f); } } } protected void addStructuralTypeProperties(Context c, EdmStructuralType st, ExpressionEvaluator ev) { for (EdmProperty prop : st.getProperties()) { boolean add = true; if (ev != null) { c.pushResolver(prop); add = ev.evaluate(c.queryInfo.filter); } if (add) { c.addEntity(this.getProperty(st, st, prop, c)); } if (ev != null) { c.popResolver(); } } EdmDataServices ds = dataProducer.getMetadata(); Iterator<?> candidates = (st instanceof EdmComplexType) ? ds.getComplexTypes().iterator() : ds.getEntityTypes().iterator(); // down the subtypes hole... while (candidates.hasNext()) { EdmStructuralType item = (EdmStructuralType) candidates.next(); if (item.getBaseType() != null && item.getBaseType().equals(st)) { addStructuralTypeProperties(c, item, ev); } } } @Override public EntityResponse getEntity(ODataContext context, String entitySetName, OEntityKey entityKey, EntityQueryInfo queryInfo) { Context c = new Context(entitySetName, queryInfo, entityKey); if (entitySetName.equals(Edm.EntitySets.Schemas)) { findSchema(c); } else if (entitySetName.equals(Edm.EntitySets.EntityTypes) || entitySetName.equals(Edm.EntitySets.RootEntityTypes)) { findStructuralType(c, true, entitySetName.equals(Edm.EntitySets.RootEntityTypes)); } else if (entitySetName.equals(Edm.EntitySets.ComplexTypes) || entitySetName.equals(Edm.EntitySets.RootComplexTypes)) { findStructuralType(c, false, entitySetName.equals(Edm.EntitySets.RootComplexTypes)); } else { throw new NotFoundException("EntitySet " + entitySetName + " not found"); } if (c.entities.isEmpty()) { throw new NotFoundException(entitySetName + entityKey.toKeyString() + " not found"); } return Responses.entity(c.entities.get(0)); } protected void findSchema(Context c) { EdmDataServices ds = dataProducer.getMetadata(); String nm = (String) c.entityKey.asSingleValue(); for (EdmSchema s : ds.getSchemas()) { if (nm.equals(s.getNamespace())) { c.entities.add(this.getSchema(c, s)); } } } protected void findStructuralType(Context c, boolean isEntity, boolean root) { EdmDataServices ds = dataProducer.getMetadata(); Iterable<?> candidates = isEntity ? ds.getEntityTypes() : ds.getComplexTypes(); for (Object eto : candidates) { EdmStructuralType st = (EdmStructuralType) eto; if (root && st.getBaseType() != null) { continue; } boolean matchedAll = true; for (OProperty<?> keyprop : c.entityKey.asComplexProperties()) { String val = null; if (keyprop.getName().equals(Edm.EntityType.Namespace)) { val = st.getNamespace(); } else if (keyprop.getName().equals(Edm.EntityType.Name)) { val = st.getName(); } else { throw new RuntimeException(keyprop.getName() + " is not a key property of " + c.entitySet.getName()); } if (!keyprop.getValue().toString().equals(val)) { matchedAll = false; break; } } if (matchedAll) { c.entities.add(this.getStructuralType(c, st)); } } // didn't find it... } public void dump() { StringWriter sw = new StringWriter(); EdmxFormatWriter.write(edm, sw); System.out.println(sw.toString()); } @Override public BaseResponse getNavProperty(ODataContext context, String entitySetName, OEntityKey entityKey, String navProp, QueryInfo queryInfo) { throw new UnsupportedOperationException("Not supported yet."); } @Override public CountResponse getEntitiesCount(ODataContext context, String entitySetName, QueryInfo queryInfo) { throw new UnsupportedOperationException("Not supported yet."); } @Override public CountResponse getNavPropertyCount(ODataContext context, String entitySetName, OEntityKey entityKey, String navProp, QueryInfo queryInfo) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void close() { throw new UnsupportedOperationException("Not supported yet."); } @Override public EntityResponse createEntity(ODataContext context, String entitySetName, OEntity entity) { throw new UnsupportedOperationException("Not supported yet."); } @Override public EntityResponse createEntity(ODataContext context, String entitySetName, OEntityKey entityKey, String navProp, OEntity entity) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void deleteEntity(ODataContext context, String entitySetName, OEntityKey entityKey) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void mergeEntity(ODataContext context, String entitySetName, OEntity entity) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void updateEntity(ODataContext context, String entitySetName, OEntity entity) { throw new UnsupportedOperationException("Not supported yet."); } @Override public EntityIdResponse getLinks(ODataContext context, OEntityId sourceEntity, String targetNavProp) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void createLink(ODataContext context, OEntityId sourceEntity, String targetNavProp, OEntityId targetEntity) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void updateLink(ODataContext context, OEntityId sourceEntity, String targetNavProp, OEntityKey oldTargetEntityKey, OEntityId newTargetEntity) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void deleteLink(ODataContext context, OEntityId sourceEntity, String targetNavProp, OEntityKey targetEntityKey) { throw new UnsupportedOperationException("Not supported yet."); } @Override public BaseResponse callFunction(ODataContext context, EdmFunctionImport name, Map<String, OFunctionParameter> params, QueryInfo queryInfo) { throw new UnsupportedOperationException("Not supported yet."); } @Override public MetadataProducer getMetadataProducer() { return null; // stop the brutal recursion :) } @Override public <TExtension extends OExtension<ODataProducer>> TExtension findExtension(Class<TExtension> clazz) { return null; } @Override public void beginChangeSetBoundary() { throw new NotImplementedException("ChangeSets not supported for " + this.getClass().getName()); } @Override public void commitChangeSetBoundary() { throw new NotImplementedException("ChangeSets not supported for " + this.getClass().getName()); } @Override public void rollbackChangeSetBoundary() { throw new NotImplementedException("ChangeSets not supported for " + this.getClass().getName()); } @Override public EntityResponse createResponseForBatchPostOperation(String entitySetName, OEntity entity) { throw new NotImplementedException("create Response For Batch Post Operation not supported for " + this.getClass().getName()); } @Override public InputStream getInputStreamForMediaLink(String entitySetName, OEntityKey entityKey, EntityQueryInfo queryInfo) { throw new NotImplementedException("Streaming is not supported for " + this.getClass().getName()); } @Override public void updateEntityWithStream(String entitySetName, OEntity entity) { throw new NotImplementedException("Updation of Stream is not supported for " + this.getClass().getName()); } @Override public ContextStream getInputStreamForNamedStream(String entitySetName, OEntityKey entityKey, String columnName, QueryInfo queryInfo) { throw new NotImplementedException("Streaming is not supported for " + this.getClass().getName()); } @Override public void updateEntityWithNamedStream(String entitySetName, OEntityKey entityKey, String columnName, ContextStream streamContext) { throw new NotImplementedException("Updation of named stream is not supported for " + this.getClass().getName()); } }