[OLINGO-1226] Serializer to construct metadata document in json format
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/d55ed59d Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/d55ed59d Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/d55ed59d Branch: refs/heads/master Commit: d55ed59d0d8a6f28328aac951706391070ba269b Parents: 1012bcb Author: ramya vasanth <[email protected]> Authored: Wed Jan 31 10:50:48 2018 +0530 Committer: ramya vasanth <[email protected]> Committed: Wed Jan 31 10:50:48 2018 +0530 ---------------------------------------------------------------------- .../olingo/server/api/serializer/Kind.java | 52 + .../olingo/server/core/ContentNegotiator.java | 22 +- .../json/MetadataDocumentJsonSerializer.java | 1173 +++++++++++++++++ .../serializer/json/ODataJsonSerializer.java | 21 +- .../server/core/ContentNegotiatorTest.java | 43 +- .../MetadataDocumentJsonSerializerTest.java | 1189 ++++++++++++++++++ 6 files changed, 2486 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d55ed59d/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/Kind.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/Kind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/Kind.java new file mode 100644 index 0000000..2450db9 --- /dev/null +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/Kind.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.api.serializer; + +/** + * The different types of edm kinds that form the metadata + * + */ +public enum Kind { + /** EntityType */ + EntityType, + /** ComplexType */ + ComplexType, + /** Function Import */ + FunctionImport, + /** Action Import */ + ActionImport, + /** Term */ + Term, + /** Navigation Property */ + NavigationProperty, + /** Enum Type **/ + EnumType, + /** Singleton **/ + Singleton, + /** Extending **/ + Extending, + /** Entity Container **/ + EntityContainer, + /** Entity Set **/ + EntitySet, + /** Function **/ + Function, + /** Action **/ + Action +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d55ed59d/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java index 63452f4..4aaeee7 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java @@ -34,7 +34,9 @@ public final class ContentNegotiator { private static final String ATOM = "atom"; private static final String JSON = "json"; + private static final String APPLICATION_JSON = "application/json"; private static final String XML = "xml"; + private static final String METADATA = "METADATA"; private static final List<ContentType> DEFAULT_SUPPORTED_CONTENT_TYPES = Collections.unmodifiableList(Arrays.asList( @@ -50,7 +52,8 @@ public final class ContentNegotiator { private static List<ContentType> getDefaultSupportedContentTypes(final RepresentationType type) { switch (type) { case METADATA: - return Collections.singletonList(ContentType.APPLICATION_XML); + return Collections.unmodifiableList(Arrays.asList(ContentType.APPLICATION_XML, + ContentType.APPLICATION_JSON)); case MEDIA: case BINARY: return Collections.singletonList(ContentType.APPLICATION_OCTET_STREAM); @@ -88,7 +91,7 @@ public final class ContentNegotiator { if (formatOption != null && formatOption.getFormat() != null) { final String formatString = formatOption.getFormat().trim(); - final ContentType contentType = mapContentType(formatString); + final ContentType contentType = mapContentType(formatString, representationType); try { result = getAcceptedType( @@ -126,10 +129,19 @@ public final class ContentNegotiator { return result; } - private static ContentType mapContentType(final String formatString) { - return JSON.equalsIgnoreCase(formatString) ? ContentType.JSON : + private static ContentType mapContentType(final String formatString, + RepresentationType representationType) { + if (representationType.name().equals(METADATA)) { + return JSON.equalsIgnoreCase(formatString) || + APPLICATION_JSON.equalsIgnoreCase(formatString) ? ContentType.APPLICATION_JSON : XML.equalsIgnoreCase(formatString) ? ContentType.APPLICATION_XML : - ATOM.equalsIgnoreCase(formatString) ? ContentType.APPLICATION_ATOM_XML : null; + ATOM.equalsIgnoreCase(formatString) ? ContentType.APPLICATION_ATOM_XML : null; + } else { + return JSON.equalsIgnoreCase(formatString) ? ContentType.JSON : + XML.equalsIgnoreCase(formatString) ? ContentType.APPLICATION_XML : + ATOM.equalsIgnoreCase(formatString) ? ContentType.APPLICATION_ATOM_XML : + APPLICATION_JSON.equalsIgnoreCase(formatString)? ContentType.APPLICATION_JSON: null; + } } private static ContentType getAcceptedType(final List<AcceptType> acceptedContentTypes, http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d55ed59d/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/MetadataDocumentJsonSerializer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/MetadataDocumentJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/MetadataDocumentJsonSerializer.java new file mode 100644 index 0000000..7c9e957 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/MetadataDocumentJsonSerializer.java @@ -0,0 +1,1173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.core.serializer.json; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.olingo.commons.api.edm.EdmAction; +import org.apache.olingo.commons.api.edm.EdmActionImport; +import org.apache.olingo.commons.api.edm.EdmAnnotatable; +import org.apache.olingo.commons.api.edm.EdmAnnotation; +import org.apache.olingo.commons.api.edm.EdmAnnotations; +import org.apache.olingo.commons.api.edm.EdmBindingTarget; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntityContainer; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmEnumType; +import org.apache.olingo.commons.api.edm.EdmFunction; +import org.apache.olingo.commons.api.edm.EdmFunctionImport; +import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; +import org.apache.olingo.commons.api.edm.EdmMember; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmNavigationPropertyBinding; +import org.apache.olingo.commons.api.edm.EdmOperation; +import org.apache.olingo.commons.api.edm.EdmParameter; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmReferentialConstraint; +import org.apache.olingo.commons.api.edm.EdmReturnType; +import org.apache.olingo.commons.api.edm.EdmSchema; +import org.apache.olingo.commons.api.edm.EdmSingleton; +import org.apache.olingo.commons.api.edm.EdmStructuredType; +import org.apache.olingo.commons.api.edm.EdmTerm; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.EdmTypeDefinition; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.TargetType; +import org.apache.olingo.commons.api.edm.annotation.EdmApply; +import org.apache.olingo.commons.api.edm.annotation.EdmCast; +import org.apache.olingo.commons.api.edm.annotation.EdmConstantExpression; +import org.apache.olingo.commons.api.edm.annotation.EdmDynamicExpression; +import org.apache.olingo.commons.api.edm.annotation.EdmExpression; +import org.apache.olingo.commons.api.edm.annotation.EdmIf; +import org.apache.olingo.commons.api.edm.annotation.EdmIsOf; +import org.apache.olingo.commons.api.edm.annotation.EdmLabeledElement; +import org.apache.olingo.commons.api.edm.annotation.EdmLabeledElementReference; +import org.apache.olingo.commons.api.edm.annotation.EdmLogicalOrComparisonExpression; +import org.apache.olingo.commons.api.edm.annotation.EdmNavigationPropertyPath; +import org.apache.olingo.commons.api.edm.annotation.EdmNot; +import org.apache.olingo.commons.api.edm.annotation.EdmNull; +import org.apache.olingo.commons.api.edm.annotation.EdmPath; +import org.apache.olingo.commons.api.edm.annotation.EdmPropertyPath; +import org.apache.olingo.commons.api.edm.annotation.EdmPropertyValue; +import org.apache.olingo.commons.api.edm.annotation.EdmRecord; +import org.apache.olingo.commons.api.edm.annotation.EdmUrlRef; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.api.edmx.EdmxReference; +import org.apache.olingo.commons.api.edmx.EdmxReferenceInclude; +import org.apache.olingo.commons.api.edmx.EdmxReferenceIncludeAnnotation; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.Kind; +import org.apache.olingo.server.api.serializer.SerializerException; + +import com.fasterxml.jackson.core.JsonGenerator; + +public class MetadataDocumentJsonSerializer { + + private final ServiceMetadata serviceMetadata; + private final Map<String, String> namespaceToAlias = new HashMap<String, String>(); + private static final String DOLLAR = "$"; + private static final String VERSION = DOLLAR + "Version"; + private static final String REFERENCES = DOLLAR + "Reference"; + private static final String INCLUDE = DOLLAR + "Include"; + private static final String NAMESPACE = DOLLAR + "Namespace"; + private static final String ALIAS = DOLLAR + "Alias"; + private static final String INCLUDE_ANNOTATIONS = DOLLAR + "IncludeAnnotations"; + private static final String TERM_NAMESPACE = DOLLAR + "TermNamespace"; + private static final String TARGET_NAMESPACE = DOLLAR + "TargetNamespace"; + private static final String QUALIFIER = DOLLAR + "Qualifier"; + private static final String IS_FLAGS = DOLLAR + "IsFlags"; + private static final String UNDERLYING_TYPE = DOLLAR + "UnderlyingType"; + private static final String KIND = DOLLAR + "Kind"; + private static final String MAX_LENGTH = DOLLAR + "MaxLength"; + private static final String PRECISION = DOLLAR + "Precision"; + private static final String SCALE = DOLLAR + "Scale"; + private static final String SRID = DOLLAR + "SRID"; + private static final String COLLECTION = DOLLAR + "Collection"; + private static final String BASE_TYPE = DOLLAR + "BaseType"; + private static final String HAS_STREAM = DOLLAR + "HasStream"; + private static final String KEY = DOLLAR + "Key"; + private static final String ABSTRACT = DOLLAR + "Abstract"; + private static final String TYPE = DOLLAR + "Type"; + private static final String NULLABLE = DOLLAR + "Nullable"; + private static final String UNICODE = DOLLAR + "Unicode"; + private static final String DEFAULT_VALUE = DOLLAR + "DefaultValue"; + private static final String PARTNER = DOLLAR + "Partner"; + private static final String CONTAINS_TARGET = DOLLAR + "ContainsTarget"; + private static final String REFERENTIAL_CONSTRAINT = DOLLAR + "ReferentialConstraint"; + private static final String ISBOUND = DOLLAR + "IsBound"; + private static final String ENTITY_SET_PATH = DOLLAR + "EntitySetPath"; + private static final String PARAMETER = DOLLAR + "Parameter"; + private static final String RETURN_TYPE = DOLLAR + "ReturnType"; + private static final String ISCOMPOSABLE = DOLLAR + "IsComposable"; + private static final String PARAMETER_NAME = DOLLAR + "Name"; + private static final String BASE_TERM = DOLLAR + "BaseTerm"; + private static final String APPLIES_TO = DOLLAR + "AppliesTo"; + private static final String NAVIGATION_PROPERTY_BINDING = DOLLAR + "NavigationPropertyBinding"; + private static final String EXTENDS = DOLLAR + "Extends"; + private static final String INCLUDE_IN_SERV_DOC = DOLLAR + "IncludeInServiceDocument"; + private static final String ANNOTATION = DOLLAR + "Annotations"; + private static final String ANNOTATION_PATH = DOLLAR + "Path"; + private static final String NAME = DOLLAR + "Name"; + + public MetadataDocumentJsonSerializer(final ServiceMetadata serviceMetadata) throws SerializerException { + if (serviceMetadata == null || serviceMetadata.getEdm() == null) { + throw new SerializerException("Service Metadata and EDM must not be null for a service.", + SerializerException.MessageKeys.NULL_METADATA_OR_EDM); + } + this.serviceMetadata = serviceMetadata; + } + + public void writeMetadataDocument(final JsonGenerator json) throws SerializerException, IOException { + json.writeStartObject(); + json.writeStringField(VERSION, "4.01"); + if (!serviceMetadata.getReferences().isEmpty()) { + appendReference(json); + } + appendDataServices(json); + json.writeEndObject(); + } + + private void appendDataServices(JsonGenerator json) throws SerializerException, IOException { + for (EdmSchema schema : serviceMetadata.getEdm().getSchemas()) { + appendSchema(json, schema); + } + } + + private void appendSchema(JsonGenerator json, EdmSchema schema) + throws SerializerException, IOException { + json.writeFieldName(schema.getNamespace()); + json.writeStartObject(); + if (schema.getAlias() != null) { + json.writeStringField(ALIAS, schema.getAlias()); + namespaceToAlias.put(schema.getNamespace(), schema.getAlias()); + } + // EnumTypes + appendEnumTypes(json, schema.getEnumTypes()); + + // TypeDefinitions + appendTypeDefinitions(json, schema.getTypeDefinitions()); + + // EntityTypes + appendEntityTypes(json, schema.getEntityTypes()); + + // ComplexTypes + appendComplexTypes(json, schema.getComplexTypes()); + + // Actions + appendActions(json, schema.getActions()); + + // Functions + appendFunctions(json, schema.getFunctions()); + + //Terms + appendTerms(json, schema.getTerms()); + + // EntityContainer + appendEntityContainer(json, schema.getEntityContainer()); + + // AnnotationGroups + appendAnnotationGroups(json, schema.getAnnotationGroups()); + + appendAnnotations(json, schema, null); + + json.writeEndObject(); + } + + private void appendAnnotationGroups(final JsonGenerator json, + final List<EdmAnnotations> annotationGroups) throws SerializerException, IOException { + if (annotationGroups.size() > 0) { + json.writeObjectFieldStart(ANNOTATION); + } + for (EdmAnnotations annotationGroup : annotationGroups) { + appendAnnotationGroup(json, annotationGroup); + } + if (annotationGroups.size() > 0) { + json.writeEndObject(); + } + } + + private void appendAnnotationGroup(final JsonGenerator json, + final EdmAnnotations annotationGroup) throws SerializerException, IOException { + String targetPath = annotationGroup.getTargetPath(); + if (annotationGroup.getQualifier() != null) { + json.writeObjectFieldStart(targetPath + "#" + annotationGroup.getQualifier()); + } else { + json.writeObjectFieldStart(targetPath); + } + appendAnnotations(json, annotationGroup, null); + json.writeEndObject(); + } + + private void appendEntityContainer(final JsonGenerator json, + final EdmEntityContainer container) throws SerializerException, IOException { + if (container != null) { + json.writeObjectFieldStart(container.getName()); + json.writeStringField(KIND, Kind.EntityContainer.name()); + FullQualifiedName parentContainerName = container.getParentContainerName(); + if (parentContainerName != null) { + String parentContainerNameString; + if (namespaceToAlias.get(parentContainerName.getNamespace()) != null) { + parentContainerNameString = + namespaceToAlias.get(parentContainerName.getNamespace()) + "." + parentContainerName.getName(); + } else { + parentContainerNameString = parentContainerName.getFullQualifiedNameAsString(); + } + json.writeObjectFieldStart(Kind.Extending.name()); + json.writeStringField(KIND, Kind.EntityContainer.name()); + json.writeStringField(EXTENDS, parentContainerNameString); + json.writeEndObject(); + } + + // EntitySets + appendEntitySets(json, container.getEntitySets()); + + String containerNamespace; + if (namespaceToAlias.get(container.getNamespace()) != null) { + containerNamespace = namespaceToAlias.get(container.getNamespace()); + } else { + containerNamespace = container.getNamespace(); + } + // ActionImports + appendActionImports(json, container.getActionImports(), containerNamespace); + + // FunctionImports + appendFunctionImports(json, container.getFunctionImports(), containerNamespace); + + + // Singletons + appendSingletons(json, container.getSingletons()); + + // Annotations + appendAnnotations(json, container, null); + + json.writeEndObject(); + } + + } + + private void appendSingletons(final JsonGenerator json, + final List<EdmSingleton> singletons) throws SerializerException, IOException { + for (EdmSingleton singleton : singletons) { + json.writeObjectFieldStart(singleton.getName()); + json.writeStringField(KIND, Kind.Singleton.name()); + json.writeStringField(TYPE, getAliasedFullQualifiedName(singleton.getEntityType())); + + appendNavigationPropertyBindings(json, singleton); + appendAnnotations(json, singleton, null); + json.writeEndObject(); + } + } + + private void appendFunctionImports(final JsonGenerator json, final List<EdmFunctionImport> functionImports, + final String containerNamespace) throws SerializerException, IOException { + for (EdmFunctionImport functionImport : functionImports) { + json.writeObjectFieldStart(functionImport.getName()); + + json.writeStringField(KIND, Kind.FunctionImport.name()); + String functionFQNString; + FullQualifiedName functionFqn = functionImport.getFunctionFqn(); + if (namespaceToAlias.get(functionFqn.getNamespace()) != null) { + functionFQNString = namespaceToAlias.get(functionFqn.getNamespace()) + "." + functionFqn.getName(); + } else { + functionFQNString = functionFqn.getFullQualifiedNameAsString(); + } + json.writeStringField(DOLLAR + Kind.Function.name(), functionFQNString); + + EdmEntitySet returnedEntitySet = functionImport.getReturnedEntitySet(); + if (returnedEntitySet != null) { + json.writeStringField(DOLLAR + Kind.EntitySet.name(), + containerNamespace + "." + returnedEntitySet.getName()); + } + // Default is false and we do not write the default + if (functionImport.isIncludeInServiceDocument()) { + json.writeBooleanField(INCLUDE_IN_SERV_DOC, functionImport.isIncludeInServiceDocument()); + } + appendAnnotations(json, functionImport, null); + json.writeEndObject(); + } + } + + private void appendActionImports(final JsonGenerator json, + final List<EdmActionImport> actionImports, String containerNamespace) + throws SerializerException, IOException { + for (EdmActionImport actionImport : actionImports) { + json.writeObjectFieldStart(actionImport.getName()); + json.writeStringField(KIND, Kind.ActionImport.name()); + json.writeStringField(DOLLAR + Kind.Action.name(), getAliasedFullQualifiedName(actionImport.getUnboundAction())); + if (actionImport.getReturnedEntitySet() != null) { + json.writeStringField(DOLLAR + Kind.EntitySet.name(), + containerNamespace + "." + actionImport.getReturnedEntitySet().getName()); + } + appendAnnotations(json, actionImport, null); + json.writeEndObject(); + } + + } + + private void appendEntitySets(final JsonGenerator json, + final List<EdmEntitySet> entitySets) throws SerializerException, IOException { + for (EdmEntitySet entitySet : entitySets) { + json.writeObjectFieldStart(entitySet.getName()); + json.writeStringField(KIND, Kind.EntitySet.name()); + json.writeStringField(TYPE, getAliasedFullQualifiedName(entitySet.getEntityType())); + if (!entitySet.isIncludeInServiceDocument()) { + json.writeBooleanField(INCLUDE_IN_SERV_DOC, entitySet.isIncludeInServiceDocument()); + } + + appendNavigationPropertyBindings(json, entitySet); + appendAnnotations(json, entitySet, null); + json.writeEndObject(); + } + } + + private void appendNavigationPropertyBindings(final JsonGenerator json, + final EdmBindingTarget bindingTarget) throws SerializerException, IOException { + if (bindingTarget.getNavigationPropertyBindings() != null && + bindingTarget.getNavigationPropertyBindings().size() > 0) { + json.writeObjectFieldStart(NAVIGATION_PROPERTY_BINDING); + for (EdmNavigationPropertyBinding binding : bindingTarget.getNavigationPropertyBindings()) { + json.writeStringField(binding.getPath(), binding.getTarget()); + } + json.writeEndObject(); + } + } + + private void appendTerms(final JsonGenerator json, final List<EdmTerm> terms) + throws SerializerException, IOException { + for (EdmTerm term : terms) { + json.writeObjectFieldStart(term.getName()); + json.writeStringField(KIND, Kind.Term.name()); + + json.writeStringField(TYPE, getAliasedFullQualifiedName(term.getType())); + + if (term.getBaseTerm() != null) { + json.writeStringField(BASE_TERM, getAliasedFullQualifiedName(term.getBaseTerm().getFullQualifiedName())); + } + + if (term.getAppliesTo() != null && !term.getAppliesTo().isEmpty()) { + String appliesToString = ""; + boolean first = true; + for (TargetType target : term.getAppliesTo()) { + if (first) { + first = false; + appliesToString = target.toString(); + } else { + appliesToString = appliesToString + " " + target.toString(); + } + } + json.writeStringField(APPLIES_TO, appliesToString); + } + + // Facets + if (!term.isNullable()) { + json.writeBooleanField(NULLABLE, term.isNullable()); + } + + if (term.getDefaultValue() != null) { + json.writeStringField(DEFAULT_VALUE, term.getDefaultValue()); + } + + if (term.getMaxLength() != null) { + json.writeNumberField(MAX_LENGTH, term.getMaxLength()); + } + + if (term.getPrecision() != null) { + json.writeNumberField(PRECISION, term.getPrecision()); + } + + if (term.getScale() != null) { + json.writeNumberField(SCALE, term.getScale()); + } + + appendAnnotations(json, term, null); + json.writeEndObject(); + } + + } + + private void appendFunctions(final JsonGenerator json, + final List<EdmFunction> functions) throws SerializerException, IOException { + Map<String, List<EdmFunction>> functionsMap = new HashMap<String, List<EdmFunction>>(); + for (EdmFunction function : functions) { + if (functionsMap.containsKey(function.getName())) { + List<EdmFunction> actionsWithSpecificActionName = functionsMap.get(function.getName()); + actionsWithSpecificActionName.add(function); + functionsMap.put(function.getName(), actionsWithSpecificActionName); + } else { + List<EdmFunction> functionList = new ArrayList<EdmFunction>(); + functionList.add(function); + functionsMap.put(function.getName(), functionList); + } + } + + for (Entry<String, List<EdmFunction>> functionsMapEntry : functionsMap.entrySet()) { + json.writeArrayFieldStart(functionsMapEntry.getKey()); + List<EdmFunction> functionEntry = functionsMapEntry.getValue(); + for (EdmFunction function : functionEntry) { + json.writeStartObject(); + json.writeStringField(KIND, Kind.Function.name()); + if (function.getEntitySetPath() != null) { + json.writeStringField(ENTITY_SET_PATH, function.getEntitySetPath()); + } + if (function.isBound()) { + json.writeBooleanField(ISBOUND, function.isBound()); + } + + if (function.isComposable()) { + json.writeBooleanField(ISCOMPOSABLE, function.isComposable()); + } + + appendOperationParameters(json, function); + + appendOperationReturnType(json, function); + + appendAnnotations(json, function, null); + + json.writeEndObject(); + } + json.writeEndArray(); + } + } + + private void appendActions(final JsonGenerator json, + final List<EdmAction> actions) throws SerializerException, IOException { + Map<String, List<EdmAction>> actionsMap = new HashMap<String, List<EdmAction>>(); + for (EdmAction action : actions) { + if (actionsMap.containsKey(action.getName())) { + List<EdmAction> actionsWithSpecificActionName = actionsMap.get(action.getName()); + actionsWithSpecificActionName.add(action); + actionsMap.put(action.getName(), actionsWithSpecificActionName); + } else { + List<EdmAction> actionList = new ArrayList<EdmAction>(); + actionList.add(action); + actionsMap.put(action.getName(), actionList); + } + } + for (Entry<String, List<EdmAction>> actionsMapEntry : actionsMap.entrySet()) { + json.writeArrayFieldStart(actionsMapEntry.getKey()); + List<EdmAction> actionEntry = actionsMapEntry.getValue(); + for (EdmAction action : actionEntry) { + json.writeStartObject(); + json.writeStringField(KIND, Kind.Action.name()); + if (action.getEntitySetPath() != null) { + json.writeStringField(ENTITY_SET_PATH, action.getEntitySetPath()); + } + json.writeBooleanField(ISBOUND, action.isBound()); + + appendOperationParameters(json, action); + + appendOperationReturnType(json, action); + + appendAnnotations(json, action, null); + + json.writeEndObject(); + } + json.writeEndArray(); + } + } + + private void appendOperationReturnType(final JsonGenerator json, + final EdmOperation operation) throws SerializerException, IOException { + EdmReturnType returnType = operation.getReturnType(); + if (returnType != null) { + json.writeObjectFieldStart(RETURN_TYPE); + String returnTypeFqnString; + if (EdmTypeKind.PRIMITIVE.equals(returnType.getType().getKind())) { + returnTypeFqnString = getFullQualifiedName(returnType.getType()); + } else { + returnTypeFqnString = getAliasedFullQualifiedName(returnType.getType()); + } + json.writeStringField(TYPE, returnTypeFqnString); + if (returnType.isCollection()) { + json.writeBooleanField(COLLECTION, returnType.isCollection()); + } + + appendReturnTypeFacets(json, returnType); + json.writeEndObject(); + } + } + + private void appendReturnTypeFacets(final JsonGenerator json, + final EdmReturnType returnType) throws SerializerException, IOException { + if (!returnType.isNullable()) { + json.writeBooleanField(NULLABLE, returnType.isNullable()); + } + if (returnType.getMaxLength() != null) { + json.writeNumberField(MAX_LENGTH, returnType.getMaxLength()); + } + if (returnType.getPrecision() != null) { + json.writeNumberField(PRECISION, returnType.getPrecision()); + } + if (returnType.getScale() != null) { + json.writeNumberField(SCALE, returnType.getScale()); + } + } + + private void appendOperationParameters(final JsonGenerator json, + final EdmOperation operation) throws SerializerException, IOException { + if (!operation.getParameterNames().isEmpty()) { + json.writeArrayFieldStart(PARAMETER); + } + for (String parameterName : operation.getParameterNames()) { + EdmParameter parameter = operation.getParameter(parameterName); + json.writeStartObject(); + json.writeStringField(PARAMETER_NAME, parameterName); + String typeFqnString; + if (EdmTypeKind.PRIMITIVE.equals(parameter.getType().getKind())) { + typeFqnString = getFullQualifiedName(parameter.getType()); + } else { + typeFqnString = getAliasedFullQualifiedName(parameter.getType()); + } + json.writeStringField(TYPE, typeFqnString); + if (parameter.isCollection()) { + json.writeBooleanField(COLLECTION, parameter.isCollection()); + } + + appendParameterFacets(json, parameter); + + appendAnnotations(json, parameter, null); + json.writeEndObject(); + } + if (!operation.getParameterNames().isEmpty()) { + json.writeEndArray(); + } + } + + private void appendParameterFacets(final JsonGenerator json, + final EdmParameter parameter) throws SerializerException, IOException { + if (!parameter.isNullable()) { + json.writeBooleanField(NULLABLE, parameter.isNullable()); + } + if (parameter.getMaxLength() != null) { + json.writeNumberField(MAX_LENGTH, parameter.getMaxLength()); + } + if (parameter.getPrecision() != null) { + json.writeNumberField(PRECISION, parameter.getPrecision()); + } + if (parameter.getScale() != null) { + json.writeNumberField(SCALE, parameter.getScale()); + } + } + + private void appendComplexTypes(final JsonGenerator json, + final List<EdmComplexType> complexTypes) throws SerializerException, IOException { + for (EdmComplexType complexType : complexTypes) { + json.writeObjectFieldStart(complexType.getName()); + + json.writeStringField(KIND, Kind.ComplexType.name()); + if (complexType.getBaseType() != null) { + json.writeStringField(BASE_TYPE, getAliasedFullQualifiedName(complexType.getBaseType())); + } + + if (complexType.isAbstract()) { + json.writeBooleanField(ABSTRACT, complexType.isAbstract()); + } + + appendProperties(json, complexType); + + appendNavigationProperties(json, complexType); + + appendAnnotations(json, complexType, null); + + json.writeEndObject(); + } + } + + private void appendEntityTypes(JsonGenerator json, + List<EdmEntityType> entityTypes) throws SerializerException, IOException { + for (EdmEntityType entityType : entityTypes) { + json.writeObjectFieldStart(entityType.getName()); + json.writeStringField(KIND, Kind.EntityType.name()); + if (entityType.hasStream()) { + json.writeBooleanField(HAS_STREAM, entityType.hasStream()); + } + + if (entityType.getBaseType() != null) { + json.writeStringField(BASE_TYPE, getAliasedFullQualifiedName(entityType.getBaseType())); + } + + if (entityType.isAbstract()) { + json.writeBooleanField(ABSTRACT, entityType.isAbstract()); + } + + appendKey(json, entityType); + + appendProperties(json, entityType); + + appendNavigationProperties(json, entityType); + + appendAnnotations(json, entityType, null); + + json.writeEndObject(); + } + } + + private void appendNavigationProperties(final JsonGenerator json, + final EdmStructuredType type) throws SerializerException, IOException { + List<String> navigationPropertyNames = new ArrayList<String>(type.getNavigationPropertyNames()); + if (type.getBaseType() != null) { + navigationPropertyNames.removeAll(type.getBaseType().getNavigationPropertyNames()); + } + for (String navigationPropertyName : navigationPropertyNames) { + EdmNavigationProperty navigationProperty = type.getNavigationProperty(navigationPropertyName); + json.writeObjectFieldStart(navigationPropertyName); + json.writeStringField(KIND, Kind.NavigationProperty.name()); + + json.writeStringField(TYPE, getAliasedFullQualifiedName(navigationProperty.getType())); + if (navigationProperty.isCollection()) { + json.writeBooleanField(COLLECTION, navigationProperty.isCollection()); + } + + if (!navigationProperty.isNullable()) { + json.writeBooleanField(NULLABLE, navigationProperty.isNullable()); + } + + if (navigationProperty.getPartner() != null) { + EdmNavigationProperty partner = navigationProperty.getPartner(); + json.writeStringField(PARTNER, partner.getName()); + } + + if (navigationProperty.containsTarget()) { + json.writeBooleanField(CONTAINS_TARGET, navigationProperty.containsTarget()); + } + + if (navigationProperty.getReferentialConstraints() != null) { + for (EdmReferentialConstraint constraint : navigationProperty.getReferentialConstraints()) { + json.writeObjectFieldStart(REFERENTIAL_CONSTRAINT); + json.writeStringField(constraint.getPropertyName(), constraint.getReferencedPropertyName()); + for (EdmAnnotation annotation : constraint.getAnnotations()) { + appendAnnotations(json, annotation, null); + } + json.writeEndObject(); + } + } + + appendAnnotations(json, navigationProperty, null); + + json.writeEndObject(); + } + } + + private void appendProperties(final JsonGenerator json, + final EdmStructuredType type) throws SerializerException, IOException { + List<String> propertyNames = new ArrayList<String>(type.getPropertyNames()); + if (type.getBaseType() != null) { + propertyNames.removeAll(type.getBaseType().getPropertyNames()); + } + for (String propertyName : propertyNames) { + EdmProperty property = type.getStructuralProperty(propertyName); + json.writeObjectFieldStart(propertyName); + String fqnString; + if (property.isPrimitive()) { + fqnString = getFullQualifiedName(property.getType()); + } else { + fqnString = getAliasedFullQualifiedName(property.getType()); + } + json.writeStringField(TYPE, fqnString); + if (property.isCollection()) { + json.writeBooleanField(COLLECTION, property.isCollection()); + } + + // Facets + if (!property.isNullable()) { + json.writeBooleanField(NULLABLE, property.isNullable()); + } + + if (!property.isUnicode()) { + json.writeBooleanField(UNICODE, property.isUnicode()); + } + + if (property.getDefaultValue() != null) { + json.writeStringField(DEFAULT_VALUE, property.getDefaultValue()); + } + + if (property.getMaxLength() != null) { + json.writeNumberField(MAX_LENGTH, property.getMaxLength()); + } + + if (property.getPrecision() != null) { + json.writeNumberField(PRECISION, property.getPrecision()); + } + + if (property.getScale() != null) { + json.writeNumberField(SCALE, property.getScale()); + } + + appendAnnotations(json, property, null); + json.writeEndObject(); + } + } + + private void appendKey(final JsonGenerator json, + final EdmEntityType entityType) throws SerializerException, IOException { + List<EdmKeyPropertyRef> keyPropertyRefs = entityType.getKeyPropertyRefs(); + if (keyPropertyRefs != null && !keyPropertyRefs.isEmpty()) { + // Resolve Base Type key as it is shown in derived type + EdmEntityType baseType = entityType.getBaseType(); + if (baseType != null && baseType.getKeyPropertyRefs() != null && !(baseType.getKeyPropertyRefs().isEmpty())) { + return; + } + json.writeArrayFieldStart(KEY); + for (EdmKeyPropertyRef keyRef : keyPropertyRefs) { + + if (keyRef.getAlias() != null) { + json.writeStartObject(); + json.writeStringField(keyRef.getAlias(), keyRef.getName()); + json.writeEndObject(); + } else { + json.writeString(keyRef.getName()); + } + } + json.writeEndArray(); + } + } + + private String getAliasedFullQualifiedName(final EdmType type) { + FullQualifiedName fqn = type.getFullQualifiedName(); + return getAliasedFullQualifiedName(fqn); + } + + private void appendTypeDefinitions(final JsonGenerator json, + final List<EdmTypeDefinition> typeDefinitions) throws SerializerException, IOException { + for (EdmTypeDefinition definition : typeDefinitions) { + json.writeObjectFieldStart(definition.getName()); + json.writeStringField(KIND, definition.getKind().name()); + json.writeStringField(UNDERLYING_TYPE, getFullQualifiedName(definition.getUnderlyingType())); + + // Facets + if (definition.getMaxLength() != null) { + json.writeStringField(MAX_LENGTH, "" + definition.getMaxLength()); + } + + if (definition.getPrecision() != null) { + json.writeStringField(PRECISION, "" + definition.getPrecision()); + } + + if (definition.getScale() != null) { + json.writeStringField(SCALE, "" + definition.getScale()); + } + + if (definition.getSrid() != null) { + json.writeStringField(SRID, "" + definition.getSrid()); + } + + appendAnnotations(json, definition, null); + json.writeEndObject(); + } + } + + private void appendEnumTypes(JsonGenerator json, List<EdmEnumType> enumTypes) + throws SerializerException, IOException { + for (EdmEnumType enumType : enumTypes) { + json.writeObjectFieldStart(enumType.getName()); + json.writeStringField(KIND, Kind.EnumType.name()); + json.writeBooleanField(IS_FLAGS, enumType.isFlags()); + json.writeStringField(UNDERLYING_TYPE, getFullQualifiedName(enumType.getUnderlyingType())); + + for (String memberName : enumType.getMemberNames()) { + + EdmMember member = enumType.getMember(memberName); + if (member.getValue() != null) { + json.writeStringField(memberName, member.getValue()); + } + + appendAnnotations(json, member, memberName); + } + json.writeEndObject(); + } + } + + private void appendAnnotations(JsonGenerator json, + final EdmAnnotatable annotatable, String memberName) throws SerializerException, IOException { + List<EdmAnnotation> annotations = annotatable.getAnnotations(); + if (annotations != null && !annotations.isEmpty()) { + for (EdmAnnotation annotation : annotations) { + String termName = memberName != null ? memberName : ""; + if (annotation.getTerm() != null) { + termName += "@" + getAliasedFullQualifiedName(annotation.getTerm().getFullQualifiedName()); + } + if (annotation.getQualifier() != null) { + termName += "#" + annotation.getQualifier(); + } + if (annotation.getExpression() == null && termName.length() > 0) { + json.writeBooleanField(termName, true); + } else { + appendExpression(json, annotation.getExpression(), termName); + } + appendAnnotations(json, annotation, termName); + } + } + } + + private void appendExpression(final JsonGenerator json, + final EdmExpression expression, String termName) throws SerializerException, IOException { + if (expression == null) { + return; + } + if (expression.isConstant()) { + appendConstantExpression(json, expression.asConstant(), termName); + } else if (expression.isDynamic()) { + appendDynamicExpression(json, expression.asDynamic(), termName); + } else { + throw new IllegalArgumentException("Unkown expressiontype in metadata"); + } + } + + private void appendDynamicExpression(JsonGenerator json, + EdmDynamicExpression dynExp, String termName) throws SerializerException, IOException { + json.writeFieldName(termName); + switch (dynExp.getExpressionType()) { + // Logical + case And: + appendLogicalOrComparisonExpression(json, dynExp.asAnd()); + break; + case Or: + appendLogicalOrComparisonExpression(json, dynExp.asOr()); + break; + case Not: + appendNotExpression(json, dynExp.asNot()); + break; + // Comparison + case Eq: + appendLogicalOrComparisonExpression(json, dynExp.asEq()); + break; + case Ne: + appendLogicalOrComparisonExpression(json, dynExp.asNe()); + break; + case Gt: + appendLogicalOrComparisonExpression(json, dynExp.asGt()); + break; + case Ge: + appendLogicalOrComparisonExpression(json, dynExp.asGe()); + break; + case Lt: + appendLogicalOrComparisonExpression(json, dynExp.asLt()); + break; + case Le: + appendLogicalOrComparisonExpression(json, dynExp.asLe()); + break; + case AnnotationPath: + json.writeStartObject(); + json.writeStringField(ANNOTATION_PATH, dynExp.asAnnotationPath().getValue()); + json.writeEndObject(); + break; + case Apply: + EdmApply asApply = dynExp.asApply(); + json.writeStartObject(); + json.writeArrayFieldStart(DOLLAR + asApply.getExpressionName()); + for (EdmExpression parameter : asApply.getParameters()) { + appendExpression(json, parameter, null); + } + json.writeEndArray(); + json.writeStringField(DOLLAR + Kind.Function.name(), asApply.getFunction()); + + appendAnnotations(json, asApply, null); + json.writeEndObject(); + break; + case Cast: + EdmCast asCast = dynExp.asCast(); + json.writeStartObject(); + appendExpression(json, asCast.getValue(), DOLLAR + asCast.getExpressionName()); + json.writeStringField(TYPE, getAliasedFullQualifiedName(asCast.getType())); + + if (asCast.getMaxLength() != null) { + json.writeNumberField(MAX_LENGTH, asCast.getMaxLength()); + } + + if (asCast.getPrecision() != null) { + json.writeNumberField(PRECISION, asCast.getPrecision()); + } + + if (asCast.getScale() != null) { + json.writeNumberField(SCALE, asCast.getScale()); + } + appendAnnotations(json, asCast, null); + json.writeEndObject(); + break; + case Collection: + json.writeStartArray(); + for (EdmExpression item : dynExp.asCollection().getItems()) { + appendExpression(json, item, null); + } + json.writeEndArray(); + break; + case If: + EdmIf asIf = dynExp.asIf(); + json.writeStartObject(); + json.writeArrayFieldStart(DOLLAR + asIf.getExpressionName()); + appendExpression(json, asIf.getGuard(), null); + appendExpression(json, asIf.getThen(), null); + appendExpression(json, asIf.getElse(), null); + json.writeEndArray(); + appendAnnotations(json, asIf, null); + json.writeEndObject(); + break; + case IsOf: + EdmIsOf asIsOf = dynExp.asIsOf(); + json.writeStartObject(); + appendExpression(json, asIsOf.getValue(), DOLLAR + asIsOf.getExpressionName()); + + json.writeStringField(TYPE, getAliasedFullQualifiedName(asIsOf.getType())); + + if (asIsOf.getMaxLength() != null) { + json.writeNumberField(MAX_LENGTH, asIsOf.getMaxLength()); + } + + if (asIsOf.getPrecision() != null) { + json.writeNumberField(PRECISION, asIsOf.getPrecision()); + } + + if (asIsOf.getScale() != null) { + json.writeNumberField(SCALE, asIsOf.getScale()); + } + appendAnnotations(json, asIsOf, null); + json.writeEndObject(); + break; + case LabeledElement: + EdmLabeledElement asLabeledElement = dynExp.asLabeledElement(); + json.writeStartObject(); + appendExpression(json, asLabeledElement.getValue(), DOLLAR + asLabeledElement.getExpressionName()); + json.writeStringField(NAME, asLabeledElement.getName()); + appendAnnotations(json, asLabeledElement, null); + json.writeEndObject(); + break; + case LabeledElementReference: + EdmLabeledElementReference asLabeledElementReference = dynExp.asLabeledElementReference(); + json.writeStartObject(); + json.writeStringField(DOLLAR + asLabeledElementReference.getExpressionName(), + asLabeledElementReference.getValue()); + json.writeEndObject(); + break; + case Null: + EdmNull asNull = dynExp.asNull(); + json.writeStartObject(); + json.writeStringField(DOLLAR + asNull.getExpressionName(), null); + appendAnnotations(json, dynExp.asNull(), null); + json.writeEndObject(); + break; + case NavigationPropertyPath: + EdmNavigationPropertyPath asNavigationPropertyPath = dynExp.asNavigationPropertyPath(); + json.writeStartObject(); + json.writeStringField(DOLLAR + asNavigationPropertyPath.getExpressionName(), + asNavigationPropertyPath.getValue()); + json.writeEndObject(); + break; + case Path: + EdmPath asPath = dynExp.asPath(); + json.writeStartObject(); + json.writeStringField(DOLLAR + asPath.getExpressionName(), asPath.getValue()); + json.writeEndObject(); + break; + case PropertyPath: + EdmPropertyPath asPropertyPath = dynExp.asPropertyPath(); + json.writeStartObject(); + json.writeStringField(DOLLAR + asPropertyPath.getExpressionName(), asPropertyPath.getValue()); + json.writeEndObject(); + break; + case Record: + EdmRecord asRecord = dynExp.asRecord(); + json.writeStartObject(); + EdmStructuredType type = asRecord.getType(); + if (type != null) { + json.writeStringField(TYPE, getAliasedFullQualifiedName(type)); + } + for (EdmPropertyValue propValue : asRecord.getPropertyValues()) { + appendExpression(json, propValue.getValue(), propValue.getProperty()); + appendAnnotations(json, propValue, propValue.getProperty()); + } + appendAnnotations(json, asRecord, null); + json.writeEndObject(); + break; + case UrlRef: + EdmUrlRef asUrlRef = dynExp.asUrlRef(); + json.writeStartObject(); + appendExpression(json, asUrlRef.getValue(), DOLLAR + asUrlRef.getExpressionName()); + appendAnnotations(json, asUrlRef, null); + json.writeEndObject(); + break; + default: + throw new IllegalArgumentException("Unkown ExpressionType for dynamic expression: " + dynExp.getExpressionType()); + } + } + + private void appendNotExpression(final JsonGenerator json, final EdmNot exp) + throws SerializerException, IOException { + json.writeStartObject(); + appendExpression(json, exp.getLeftExpression(), DOLLAR + exp.getExpressionName()); + appendAnnotations(json, exp, null); + json.writeEndObject(); + } + + private void appendLogicalOrComparisonExpression(final JsonGenerator json, + final EdmLogicalOrComparisonExpression exp) throws SerializerException, IOException { + json.writeStartObject(); + json.writeArrayFieldStart(DOLLAR + exp.getExpressionName()); + appendExpression(json, exp.getLeftExpression(), null); + appendExpression(json, exp.getRightExpression(), null); + json.writeEndArray(); + appendAnnotations(json, exp, null); + json.writeEndObject(); + } + + private void appendConstantExpression(final JsonGenerator json, + final EdmConstantExpression constExp, String termName) throws SerializerException, IOException { + switch (constExp.getExpressionType()) { + case Binary: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Date: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case DateTimeOffset: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Decimal: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Float: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Int: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Duration: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case EnumMember: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Guid: + json.writeObjectFieldStart(termName); + json.writeStringField("$" + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case TimeOfDay: + json.writeObjectFieldStart(termName); + json.writeStringField(DOLLAR + constExp.getExpressionName(), constExp.getValueAsString()); + json.writeEndObject(); + break; + case Bool: + if (termName != null && termName.length() > 0) { + json.writeBooleanField(termName, Boolean.valueOf(constExp.getValueAsString())); + } else { + json.writeBoolean(Boolean.valueOf(constExp.getValueAsString())); + } + break; + case String: + if (termName != null && termName.length() > 0) { + json.writeStringField(termName, constExp.getValueAsString()); + } else { + json.writeString(constExp.getValueAsString()); + } + break; + default: + throw new IllegalArgumentException("Unkown ExpressionType " + + "for constant expression: " + constExp.getExpressionType()); + } + } + + private String getAliasedFullQualifiedName(final FullQualifiedName fqn) { + final String name; + if (namespaceToAlias.get(fqn.getNamespace()) != null) { + name = namespaceToAlias.get(fqn.getNamespace()) + "." + fqn.getName(); + } else { + name = fqn.getFullQualifiedNameAsString(); + } + + return name; + } + + private String getFullQualifiedName(final EdmType type) { + return type.getFullQualifiedName().getFullQualifiedNameAsString(); + } + + private void appendReference(JsonGenerator json) throws SerializerException, IOException { + json.writeObjectFieldStart(REFERENCES); + for (final EdmxReference reference : serviceMetadata.getReferences()) { + json.writeObjectFieldStart(reference.getUri().toASCIIString()); + + List<EdmxReferenceInclude> includes = reference.getIncludes(); + if (!includes.isEmpty()) { + appendIncludes(json, includes); + } + + List<EdmxReferenceIncludeAnnotation> includeAnnotations = reference.getIncludeAnnotations(); + if (!includeAnnotations.isEmpty()) { + appendIncludeAnnotations(json, includeAnnotations); + } + json.writeEndObject(); + } + json.writeEndObject(); + } + + private void appendIncludeAnnotations(JsonGenerator json, + List<EdmxReferenceIncludeAnnotation> includeAnnotations) throws SerializerException, IOException { + json.writeArrayFieldStart(INCLUDE_ANNOTATIONS); + for (EdmxReferenceIncludeAnnotation includeAnnotation : includeAnnotations) { + json.writeStartObject(); + json.writeStringField(TERM_NAMESPACE, includeAnnotation.getTermNamespace()); + if (includeAnnotation.getQualifier() != null) { + json.writeStringField(QUALIFIER, includeAnnotation.getQualifier()); + } + if (includeAnnotation.getTargetNamespace() != null) { + json.writeStringField(TARGET_NAMESPACE, includeAnnotation.getTargetNamespace()); + } + json.writeEndObject(); + } + json.writeEndArray(); + } + + private void appendIncludes(JsonGenerator json, + List<EdmxReferenceInclude> includes) throws SerializerException, IOException { + json.writeArrayFieldStart(INCLUDE); + for (EdmxReferenceInclude include : includes) { + json.writeStartObject(); + json.writeStringField(NAMESPACE, include.getNamespace()); + if (include.getAlias() != null) { + namespaceToAlias.put(include.getNamespace(), include.getAlias()); + // Reference Aliases are ignored for now since they are not V2 compatible + json.writeStringField(ALIAS, include.getAlias()); + } + json.writeEndObject(); + } + json.writeEndArray(); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d55ed59d/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java index 9e4d74a..b227319 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java @@ -155,8 +155,25 @@ public class ODataJsonSerializer extends AbstractODataSerializer { @Override public SerializerResult metadataDocument(final ServiceMetadata serviceMetadata) throws SerializerException { - throw new SerializerException("Metadata in JSON format not supported!", - SerializerException.MessageKeys.JSON_METADATA); + OutputStream outputStream = null; + SerializerException cachedException = null; + + try { + CircleStreamBuffer buffer = new CircleStreamBuffer(); + outputStream = buffer.getOutputStream(); + JsonGenerator json = new JsonFactory().createGenerator(outputStream); + new MetadataDocumentJsonSerializer(serviceMetadata).writeMetadataDocument(json); + + json.close(); + outputStream.close(); + return SerializerResultImpl.with().content(buffer.getInputStream()).build(); + } catch (final IOException e) { + cachedException = + new SerializerException(IO_EXCEPTION_TEXT, e, SerializerException.MessageKeys.IO_EXCEPTION); + throw cachedException; + } finally { + closeCircleStreamBufferOutput(outputStream, cachedException); + } } @Override http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d55ed59d/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java index ae6373c..f863c70 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/ContentNegotiatorTest.java @@ -49,8 +49,10 @@ public class ContentNegotiatorTest { static final private String ACCEPT_CASE_MIN_IEEE754 = ACCEPT_CASE_MIN + ";IEEE754Compatible=true"; static final private String ACCEPT_CASE_JSONQ = "application/json;q=0.2"; static final private String ACCEPT_CASE_XML = ContentType.APPLICATION_XML.toContentTypeString(); + static final private String ACCEPT_CASE_JSON = ContentType.APPLICATION_JSON.toContentTypeString(); static final private String ACCEPT_CASE_WILDCARD1 = "*/*"; static final private String ACCEPT_CASE_WILDCARD2 = "application/*"; + static final private String ACCEPT_CASE_JSON_IEEE754 = ACCEPT_CASE_JSON + ";IEEE754Compatible=true"; //@formatter:off (Eclipse formatter) //CHECKSTYLE:OFF (Maven checkstyle) @@ -90,9 +92,25 @@ public class ContentNegotiatorTest { { ACCEPT_CASE_XML, null, ACCEPT_CASE_WILDCARD1, null }, { ACCEPT_CASE_XML, null, ACCEPT_CASE_WILDCARD2, null }, { "a/a", "a/a", null, "a/a,b/b" }, - { "a/a;x=y", "a/a", ACCEPT_CASE_WILDCARD1, "a/a;x=y" } + { "a/a;x=y", "a/a", ACCEPT_CASE_WILDCARD1, "a/a;x=y" }, + { ACCEPT_CASE_JSON, "json", ACCEPT_CASE_JSON_IEEE754, null }, + { ACCEPT_CASE_JSON, "json", ACCEPT_CASE_WILDCARD1, null }, + { ACCEPT_CASE_JSON, "application/json",ACCEPT_CASE_JSON_IEEE754, null }, + { ACCEPT_CASE_JSON_IEEE754,null, ACCEPT_CASE_JSON_IEEE754, null }, + { ACCEPT_CASE_JSON, null, ACCEPT_CASE_JSON, null } }; + String[][] casesMetadataFail = { + /* expected $format accept modified content types */ + { "Unsupported $format = json;IEEE754Compatible=true","json;IEEE754Compatible=true", null, null}, + { "Unsupported $format = json;charset=ISO-8859-1","json;charset=ISO-8859-1", null, null}, + { "Unsupported or illegal Accept header value: json;" + + "charset=ISO-8859-1 != [application/xml, application/json]",null, + "json;charset=ISO-8859-1", null}, + { "Unsupported $format = application/json;charset=ISO-8859-1", + "application/json;charset=ISO-8859-1",null, null}, + }; + String[][] casesFail = { /* expected $format accept modified content types */ { null, "xxx/yyy", null, null }, @@ -126,9 +144,10 @@ public class ContentNegotiatorTest { testContentNegotiation(new String[] { ACCEPT_CASE_XML, null, null, null }, RepresentationType.METADATA); } - @Test(expected = ContentNegotiatorException.class) - public void metadataJsonFail() throws Exception { - testContentNegotiation(new String[] { null, "json", null, null }, RepresentationType.METADATA); + @Test + public void metadataJson() throws Exception { + testContentNegotiation(new String[] { ACCEPT_CASE_JSON, + "application/json", null, null }, RepresentationType.METADATA); } @Test @@ -149,6 +168,18 @@ public class ContentNegotiatorTest { } } } + + @Test + public void metadataFail() throws Exception { + for (String[] useCase : casesMetadataFail) { + try { + testContentNegotiation(useCase, RepresentationType.METADATA); + fail("Unsupported $format = " + useCase[1] + '|' + useCase[2] + '|' + useCase[3] + "'!"); + } catch (final ContentNegotiatorException e) { + // Expected Exception + } + } + } @Test public void checkSupport() throws Exception { @@ -191,10 +222,8 @@ public class ContentNegotiatorTest { final CustomContentTypeSupport customContentTypeSupport = useCase[3] == null ? null : createCustomContentTypeSupport(useCase[3]); - final ContentType requestedContentType = ContentNegotiator.doContentNegotiation( - formatOption, request, customContentTypeSupport, representationType); - + formatOption, request, customContentTypeSupport, representationType); assertNotNull(requestedContentType); if (useCase[0] != null) { assertEquals(ContentType.create(useCase[0]), requestedContentType);
