[OLINGO-1155]Delta support 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/f7e5a5c7 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/f7e5a5c7 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/f7e5a5c7 Branch: refs/heads/master Commit: f7e5a5c716821bb376ce1143b8b24e5ae1abd6b3 Parents: f6dd0de Author: Archana Rai <[email protected]> Authored: Mon Jul 31 16:13:11 2017 +0530 Committer: Archana Rai <[email protected]> Committed: Mon Jul 31 16:13:11 2017 +0530 ---------------------------------------------------------------------- .../apache/olingo/commons/api/Constants.java | 14 + .../olingo/commons/api/data/DeletedEntity.java | 2 +- .../api/edm/constants/ODataServiceVersion.java | 10 +- .../commons/api/format/PreferenceName.java | 1 + .../org/apache/olingo/server/api/OData.java | 10 + .../api/serializer/EdmDeltaSerializer.java | 40 + .../api/serializer/SerializerException.java | 2 + .../olingo/server/api/uri/UriInfoAll.java | 6 + .../olingo/server/api/uri/UriInfoCrossjoin.java | 6 + .../olingo/server/api/uri/UriInfoResource.java | 8 +- .../api/uri/queryoption/DeltaTokenOption.java | 28 + .../uri/queryoption/SystemQueryOptionKind.java | 7 +- .../server/core/RequestURLHierarchyVisitor.java | 12 +- .../olingo/server/core/RequestURLVisitor.java | 5 +- .../apache/olingo/server/core/ODataImpl.java | 30 + .../olingo/server/core/debug/DebugTabUri.java | 6 +- .../server/core/prefer/PreferencesImpl.java | 3 +- .../serializer/json/JsonDeltaSerializer.java | 555 ++++++++++ .../JsonDeltaSerializerWithNavigations.java | 655 ++++++++++++ .../serializer/json/ODataJsonSerializer.java | 23 +- .../olingo/server/core/uri/UriInfoImpl.java | 7 + .../olingo/server/core/uri/parser/Parser.java | 9 + .../uri/queryoption/DeltaTokenOptionImpl.java | 39 + .../server/core/uri/validator/UriValidator.java | 43 +- .../server-core-exceptions-i18n.properties | 1 + .../olingo/server/core/uri/UriInfoImplTest.java | 10 +- .../olingo/server/tecsvc/data/DataCreator.java | 41 +- .../olingo/server/tecsvc/data/DataProvider.java | 88 +- .../processor/TechnicalEntityProcessor.java | 109 +- .../tecsvc/processor/TechnicalProcessor.java | 19 + .../queryoptions/options/DeltaTokenHandler.java | 54 + .../tecsvc/provider/ContainerProvider.java | 14 +- .../tecsvc/provider/EntityTypeProvider.java | 13 +- .../server/tecsvc/provider/SchemaProvider.java | 1 + .../json/JsonDeltaSerializerTest.java | 731 +++++++++++++ .../JsonDeltaSerializerWithNavigationsTest.java | 1012 ++++++++++++++++++ 36 files changed, 3567 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java ---------------------------------------------------------------------- diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java index fb8a6b3..e41abd4 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java @@ -319,4 +319,18 @@ public interface Constants { String ASSOCIATION_LINK_TYPE = ContentType.APPLICATION_XML.toContentTypeString(); String ENTITY_COLLECTION_BINDING_LINK_TYPE = ContentType.APPLICATION_XML.toContentTypeString(); String ENTITY_BINDING_LINK_TYPE = ContentType.APPLICATION_XML.toContentTypeString(); + + //For v4.01 Delta + + String LINK = "/$link"; + String DELETEDLINK = "/$deletedLink"; + String DELTA = "/$delta"; + String DELTAVALUE = "delta"; + String AT = "@"; + String DELETEDENTITY = "/$deletedEntity"; + String DELTALINK = "@deltaLink"; + String HASH = "#"; + String REMOVED = "removed"; + String ENTITY = "/$entity"; + String REASON = "Reason"; } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/DeletedEntity.java ---------------------------------------------------------------------- diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/DeletedEntity.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/DeletedEntity.java index a5a6a43..87dc3f9 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/DeletedEntity.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/DeletedEntity.java @@ -23,7 +23,7 @@ import java.net.URI; /** * A deleted entity contains the reason for deletion and the id. */ -public class DeletedEntity { +public class DeletedEntity extends Entity{ /** * Reason of the removal from the list http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/constants/ODataServiceVersion.java ---------------------------------------------------------------------- diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/constants/ODataServiceVersion.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/constants/ODataServiceVersion.java index ef7cb65..c781191 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/constants/ODataServiceVersion.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/constants/ODataServiceVersion.java @@ -40,7 +40,12 @@ public enum ODataServiceVersion { /** * OData Version 4.0 */ - V40("4.0"); + V40("4.0"), + /** + * OData Version 4.01 + */ + V401("4.01"); + private static final Pattern DATASERVICEVERSIONPATTERN = Pattern.compile("(\\p{Digit}+\\.\\p{Digit}+)(:?;.*)?"); @@ -57,7 +62,8 @@ public enum ODataServiceVersion { return V10.toString().equals(possibleDataServiceVersion) || V20.toString().equals(possibleDataServiceVersion) || V30.toString().equals(possibleDataServiceVersion) - || V40.toString().equals(possibleDataServiceVersion); + || V40.toString().equals(possibleDataServiceVersion) + || V401.toString().equals(possibleDataServiceVersion); } else { throw new IllegalArgumentException(version); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/PreferenceName.java ---------------------------------------------------------------------- diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/PreferenceName.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/PreferenceName.java index f77e615..5ee884c 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/PreferenceName.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/PreferenceName.java @@ -29,6 +29,7 @@ public enum PreferenceName { INCLUDE_ANNOTATIONS("odata.include-annotations"), MAX_PAGE_SIZE("odata.maxpagesize"), TRACK_CHANGES("odata.track-changes"), + TRACK_CHANGES_PREF("track-changes"), RETURN("return"), RESPOND_ASYNC("respond-async"), WAIT("wait"), http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java index f72803b..9e00413 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java @@ -35,6 +35,7 @@ import org.apache.olingo.server.api.etag.ETagHelper; import org.apache.olingo.server.api.etag.ServiceMetadataETagSupport; import org.apache.olingo.server.api.prefer.Preferences; import org.apache.olingo.server.api.serializer.EdmAssistedSerializer; +import org.apache.olingo.server.api.serializer.EdmDeltaSerializer; import org.apache.olingo.server.api.serializer.FixedFormatSerializer; import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.api.serializer.SerializerException; @@ -184,4 +185,13 @@ public abstract class OData { */ public abstract EdmAssistedSerializer createEdmAssistedSerializer(final ContentType contentType) throws SerializerException; + + /** + * Creates a new serializer object capable of working without EDM information + * for rendering delta content in the specified format. + * @param contentType a content type supported by Olingo + * @param version versions supported by Olingo + */ + public abstract EdmDeltaSerializer createEdmDeltaSerializer(final ContentType contentType, + final List<String> versions) throws SerializerException; } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EdmDeltaSerializer.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EdmDeltaSerializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EdmDeltaSerializer.java new file mode 100644 index 0000000..9195294 --- /dev/null +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EdmDeltaSerializer.java @@ -0,0 +1,40 @@ +/* + * 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; + +import org.apache.olingo.commons.api.data.Delta; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.server.api.ServiceMetadata; + +public interface EdmDeltaSerializer { + + /** + * Writes collection of delta-response into an InputStream. + * Information from the EDM is used in addition to information from the data and preferred, + * but the serializer works without any EDM information as well. + * Linked data is always written as expanded items (so closed reference loops have to be avoided). + * @param metadata metadata for the service + * @param referencedEntityType the {@link EdmEntityType} or <code>null</code> if not available + * @param delta the delta data as entity collection + * @param options options for the serializer + */ + SerializerResult entityCollection(ServiceMetadata metadata, EdmEntityType referencedEntityType, + Delta delta, EntityCollectionSerializerOptions options) throws SerializerException; + +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java index be3416b..62a0dd5 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java @@ -41,6 +41,8 @@ public class SerializerException extends ODataLibraryException { INCONSISTENT_PROPERTY_TYPE, /** parameter: property name */ MISSING_PROPERTY, + /** parameter: Delta property name */ + MISSING_DELTA_PROPERTY, /** parameter: - */ MISSING_ID, /** parameters: property name, property value */ http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoAll.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoAll.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoAll.java index cc63abd..ec58a0d 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoAll.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoAll.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.olingo.server.api.uri.queryoption.CountOption; import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption; +import org.apache.olingo.server.api.uri.queryoption.DeltaTokenOption; import org.apache.olingo.server.api.uri.queryoption.FormatOption; import org.apache.olingo.server.api.uri.queryoption.SearchOption; import org.apache.olingo.server.api.uri.queryoption.SkipOption; @@ -68,4 +69,9 @@ public interface UriInfoAll { * @return Object containing information of the $top option */ TopOption getTopOption(); + + /** + * @return Object containing information of the $deltatoken option + */ + DeltaTokenOption getDeltaTokenOption(); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoCrossjoin.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoCrossjoin.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoCrossjoin.java index 8fdba84..1718812 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoCrossjoin.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoCrossjoin.java @@ -21,6 +21,7 @@ package org.apache.olingo.server.api.uri; import java.util.List; import org.apache.olingo.server.api.uri.queryoption.CountOption; +import org.apache.olingo.server.api.uri.queryoption.DeltaTokenOption; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.FilterOption; import org.apache.olingo.server.api.uri.queryoption.FormatOption; @@ -91,4 +92,9 @@ public interface UriInfoCrossjoin { * @return Object containing information of the $top option */ TopOption getTopOption(); + + /** + * @return Object containing information of the $deltatoken option + */ + DeltaTokenOption getDeltaTokenOption(); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java index 05e010e..cb5f826 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.olingo.server.api.uri.queryoption.ApplyOption; import org.apache.olingo.server.api.uri.queryoption.CountOption; import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption; +import org.apache.olingo.server.api.uri.queryoption.DeltaTokenOption; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.FilterOption; import org.apache.olingo.server.api.uri.queryoption.FormatOption; @@ -69,7 +70,12 @@ public interface UriInfoResource { * @return Object containing information of the $count option */ CountOption getCountOption(); - + + /** + * @return Object containing information of the $deltatoken option + */ + DeltaTokenOption getDeltaTokenOption(); + /** * @return Object containing information of the $orderby option */ http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/DeltaTokenOption.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/DeltaTokenOption.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/DeltaTokenOption.java new file mode 100644 index 0000000..51773b7 --- /dev/null +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/DeltaTokenOption.java @@ -0,0 +1,28 @@ +/* + * 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.uri.queryoption; + +public interface DeltaTokenOption extends SystemQueryOption { + + /** + * @return Value of $deltatoken + */ + String getValue(); + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java index 3cfe47f..76a762d 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java @@ -83,7 +83,12 @@ public enum SystemQueryOptionKind { * @see LevelsExpandOption */ LEVELS("$levels"), - + + /** + * @see deltaTokenOption + */ + DELTATOKEN("$deltatoken"), + /** * @see ApplyOption */ http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java index bc48f71..96be8a4 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java @@ -47,6 +47,7 @@ import org.apache.olingo.server.api.uri.UriResourceRoot; import org.apache.olingo.server.api.uri.UriResourceSingleton; import org.apache.olingo.server.api.uri.UriResourceValue; import org.apache.olingo.server.api.uri.queryoption.CountOption; +import org.apache.olingo.server.api.uri.queryoption.DeltaTokenOption; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.FilterOption; import org.apache.olingo.server.api.uri.queryoption.FormatOption; @@ -259,7 +260,10 @@ public class RequestURLHierarchyVisitor implements RequestURLVisitor { if (info.getSkipTokenOption() != null) { visit(info.getSkipTokenOption()); } - + + if (info.getDeltaTokenOption() != null) { + visit(info.getDeltaTokenOption()); + } } @Override @@ -313,7 +317,11 @@ public class RequestURLHierarchyVisitor implements RequestURLVisitor { @Override public void visit(UriResourceRef info) { } - + + @Override + public void visit(DeltaTokenOption option) { + } + @Override public void visit(UriResourceRoot info) { } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java ---------------------------------------------------------------------- diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java index f3f4027..5fda0e4 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java @@ -43,6 +43,7 @@ import org.apache.olingo.server.api.uri.UriResourceRoot; import org.apache.olingo.server.api.uri.UriResourceSingleton; import org.apache.olingo.server.api.uri.UriResourceValue; import org.apache.olingo.server.api.uri.queryoption.CountOption; +import org.apache.olingo.server.api.uri.queryoption.DeltaTokenOption; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.FilterOption; import org.apache.olingo.server.api.uri.queryoption.FormatOption; @@ -96,7 +97,9 @@ public interface RequestURLVisitor { void visit(TopOption option); void visit(UriResourceCount option); - + + void visit(DeltaTokenOption option); + void visit(UriResourceRef info); void visit(UriResourceRoot info); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java index 08693aa..ef42e62 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java @@ -39,6 +39,7 @@ import org.apache.olingo.server.api.etag.ETagHelper; import org.apache.olingo.server.api.etag.ServiceMetadataETagSupport; import org.apache.olingo.server.api.prefer.Preferences; import org.apache.olingo.server.api.serializer.EdmAssistedSerializer; +import org.apache.olingo.server.api.serializer.EdmDeltaSerializer; import org.apache.olingo.server.api.serializer.FixedFormatSerializer; import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.api.serializer.SerializerException; @@ -53,6 +54,8 @@ import org.apache.olingo.server.core.prefer.PreferencesImpl; import org.apache.olingo.server.core.serializer.FixedFormatSerializerImpl; import org.apache.olingo.server.core.serializer.json.EdmAssistedJsonSerializer; import org.apache.olingo.server.core.serializer.json.ODataJsonSerializer; +import org.apache.olingo.server.core.serializer.json.JsonDeltaSerializer; +import org.apache.olingo.server.core.serializer.json.JsonDeltaSerializerWithNavigations; import org.apache.olingo.server.core.serializer.xml.ODataXmlSerializer; import org.apache.olingo.server.core.uri.UriHelperImpl; @@ -96,6 +99,33 @@ public class ODataImpl extends OData { throw new SerializerException("Unsupported format: " + contentType.toContentTypeString(), SerializerException.MessageKeys.UNSUPPORTED_FORMAT, contentType.toContentTypeString()); } + + + @Override + public EdmDeltaSerializer createEdmDeltaSerializer(final ContentType contentType, final List<String> versions) + throws SerializerException { + if (contentType.isCompatible(ContentType.APPLICATION_JSON)) { + if(versions!=null && versions.size()>0){ + return getMaxVersion(versions)>4 ? new JsonDeltaSerializerWithNavigations(contentType): + new JsonDeltaSerializer(contentType); + } + return new JsonDeltaSerializerWithNavigations(contentType); + } + throw new SerializerException("Unsupported format: " + contentType.toContentTypeString(), + SerializerException.MessageKeys.UNSUPPORTED_FORMAT, contentType.toContentTypeString()); + } + + private float getMaxVersion(List<String> versions) { + Float versionValue [] = new Float [versions.size()]; + int i=0; + Float max=new Float(0); + for(String version:versions){ + Float ver = Float.valueOf(version); + versionValue[i++] = ver; + max = max > ver ? max : ver ; + } + return max; + } @Override public ODataHttpHandler createHandler(final ServiceMetadata serviceMetadata) { http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java index 007b7c6..2acbc48 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java @@ -105,7 +105,11 @@ public class DebugTabUri implements DebugTab { } else if (uriInfo.getKind() == UriInfoKind.entityId) { appendType(gen, "typeCast", uriInfo.asUriInfoEntityId().getEntityTypeCast()); } - + + if (uriInfo.getDeltaTokenOption() != null) { + gen.writeStringField("deltatoken", uriInfo.getDeltaTokenOption().getValue()); + } + if (uriInfo.getFormatOption() != null) { gen.writeStringField("format", uriInfo.getFormatOption().getFormat()); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java index 7997d57..a757674 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java @@ -78,7 +78,8 @@ public class PreferencesImpl implements Preferences { @Override public boolean hasTrackChanges() { - return preferences.containsKey(PreferenceName.TRACK_CHANGES.getName()); + return (preferences.containsKey(PreferenceName.TRACK_CHANGES.getName()) + ||preferences.containsKey(PreferenceName.TRACK_CHANGES_PREF.getName())); } @Override http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializer.java new file mode 100644 index 0000000..0708d5d --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializer.java @@ -0,0 +1,555 @@ +/* + * 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.io.OutputStream; +import java.net.URI; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.AbstractEntityCollection; +import org.apache.olingo.commons.api.data.ComplexValue; +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.DeletedEntity; +import org.apache.olingo.commons.api.data.Delta; +import org.apache.olingo.commons.api.data.DeltaLink; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Link; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmStructuredType; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.EdmDeltaSerializer; +import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriHelper; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; +import org.apache.olingo.server.api.uri.queryoption.SelectOption; +import org.apache.olingo.server.core.serializer.SerializerResultImpl; +import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer; +import org.apache.olingo.server.core.serializer.utils.ContentTypeHelper; +import org.apache.olingo.server.core.serializer.utils.ContextURLBuilder; +import org.apache.olingo.server.core.serializer.utils.ExpandSelectHelper; +import org.apache.olingo.server.core.uri.UriHelperImpl; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +public class JsonDeltaSerializer implements EdmDeltaSerializer { + + private static final String LINK = "/$link"; + private static final String DELETEDLINK = "/$deletedLink"; + private static final String DELTA = "/$delta"; + private static final String HASH = "#"; + private static final String DELETEDENTITY = "/$deletedEntity"; + private static final String ENTITY = "/$entity"; + private static final String REASON = "Reason"; + private static final String IO_EXCEPTION_TEXT = "An I/O exception occurred."; + private final boolean isIEEE754Compatible; + private final boolean isODataMetadataNone; + private final boolean isODataMetadataFull; + + public JsonDeltaSerializer(final ContentType contentType) { + isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType); + isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType); + isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType); + } + + @Override + public SerializerResult entityCollection(ServiceMetadata metadata, EdmEntityType referencedEntityType, Delta delta, + EntityCollectionSerializerOptions options) throws SerializerException { + OutputStream outputStream = null; + SerializerException cachedException = null; + try { + CircleStreamBuffer buffer = new CircleStreamBuffer(); + outputStream = buffer.getOutputStream(); + JsonGenerator json = new JsonFactory().createGenerator(outputStream); + boolean pagination = false; + json.writeStartObject(); + + final ContextURL contextURL = checkContextURL(options == null ? null : options.getContextURL()); + writeContextURL(contextURL, json); + + if (options != null && options.getCount() != null && options.getCount().getValue()) { + writeInlineCount(delta.getCount(), json); + } + json.writeFieldName(Constants.VALUE); + writeEntitySet(metadata, referencedEntityType, delta, options, json); + + pagination = writeNextLink(delta, json); + writeDeltaLink(delta, json, pagination); + + 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); + } + + } + + protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType, + final Delta entitySet, final EntityCollectionSerializerOptions options, + final JsonGenerator json) throws IOException, + SerializerException { + json.writeStartArray(); + for (final Entity entity : entitySet.getEntities()) { + writeAddedUpdatedEntity(metadata, entityType, entity, options.getExpand(), + options.getSelect(), options.getContextURL(), false, options.getContextURL() + .getEntitySetOrSingletonOrType(), json); + } + for (final DeletedEntity deletedEntity : entitySet.getDeletedEntities()) { + writeDeletedEntity(deletedEntity, options, json); + } + for (final DeltaLink addedLink : entitySet.getAddedLinks()) { + writeLink(addedLink, options, json, true); + } + for (final DeltaLink deletedLink : entitySet.getDeletedLinks()) { + writeLink(deletedLink, options, json, false); + } + json.writeEndArray(); + } + + private void writeLink(DeltaLink link, EntityCollectionSerializerOptions options, + JsonGenerator json, boolean isAdded) throws IOException, SerializerException { + try { + json.writeStartObject(); + String entityId = options.getContextURL().getEntitySetOrSingletonOrType(); + String operation = isAdded ? LINK : DELETEDLINK; + json.writeStringField(Constants.JSON_CONTEXT, HASH + entityId + operation); + if (link != null) { + if (link.getSource() != null) { + json.writeStringField(Constants.ATTR_SOURCE, link.getSource().toString()); + } else { + throw new SerializerException("DeltaLink source is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Source"); + } + if (link.getRelationship() != null) { + json.writeStringField(Constants.ATTR_RELATIONSHIP, link.getRelationship().toString()); + } else { + throw new SerializerException("DeltaLink relationship is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Relationship"); + } + if (link.getTarget() != null) { + json.writeStringField(Constants.ERROR_TARGET, link.getTarget().toString()); + } else { + throw new SerializerException("DeltaLink target is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Target"); + } + } else { + throw new SerializerException("DeltaLink is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Delta Link"); + } + json.writeEndObject(); + } catch (IOException e) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } + } + + private void writeDeletedEntity(DeletedEntity deletedEntity, + EntityCollectionSerializerOptions options, JsonGenerator json) throws IOException, SerializerException { + if (deletedEntity.getId() == null) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } + if (deletedEntity.getReason() == null) { + throw new SerializerException("DeletedEntity reason is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, REASON); + } + json.writeStartObject(); + json.writeStringField(Constants.JSON_CONTEXT, HASH + deletedEntity.getId().toASCIIString() + DELETEDENTITY); + json.writeStringField(Constants.JSON_ID, deletedEntity.getId().toASCIIString()); + json.writeStringField(Constants.ELEM_REASON, deletedEntity.getReason().name()); + json.writeEndObject(); + + } + + public void writeAddedUpdatedEntity(final ServiceMetadata metadata, final EdmEntityType entityType, + final Entity entity, final ExpandOption expand, final SelectOption select, final ContextURL url, + final boolean onlyReference, String name, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartObject(); + if (entity.getId() != null && url != null) { + String entityId = entity.getId().toString(); + name = url.getEntitySetOrSingletonOrType(); + if (!entityId.contains(name)) { + String entityName = entityId.substring(0, entityId.indexOf("(")); + if (!entityName.equals(name)) { + json.writeStringField(Constants.JSON_CONTEXT, HASH + entityName + ENTITY); + } + } + } + json.writeStringField(Constants.JSON_ID, getEntityId(entity, entityType, name)); + writeProperties(metadata, entityType, entity.getProperties(), select, json); + json.writeEndObject(); + + } + + private Property findProperty(final String propertyName, final List<Property> properties) { + for (final Property property : properties) { + if (propertyName.equals(property.getName())) { + return property; + } + } + return null; + } + + protected void writeProperty(final ServiceMetadata metadata, + final EdmProperty edmProperty, final Property property, + final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + boolean isStreamProperty = isStreamProperty(edmProperty); + if (property != null) { + if (!isStreamProperty) { + json.writeFieldName(edmProperty.getName()); + } + writePropertyValue(metadata, edmProperty, property, selectedPaths, json); + } + + } + + private boolean isStreamProperty(EdmProperty edmProperty) { + final EdmType type = edmProperty.getType(); + return (edmProperty.isPrimitive() && type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Stream)); + } + + private void writePropertyValue(final ServiceMetadata metadata, final EdmProperty edmProperty, + final Property property, final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + final EdmType type = edmProperty.getType(); + try { + if (edmProperty.isPrimitive() + || type.getKind() == EdmTypeKind.ENUM || type.getKind() == EdmTypeKind.DEFINITION) { + if (edmProperty.isCollection()) { + writePrimitiveCollection((EdmPrimitiveType) type, property, + edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), json); + } else { + writePrimitive((EdmPrimitiveType) type, property, + edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), json); + } + } else if (property.isComplex()) { + if (edmProperty.isCollection()) { + writeComplexCollection(metadata, (EdmComplexType) type, property, selectedPaths, json); + } else { + writeComplex(metadata, (EdmComplexType) type, property, selectedPaths, json); + } + } else { + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, edmProperty.getName()); + } + } catch (final EdmPrimitiveTypeException e) { + throw new SerializerException("Wrong value for property!", e, + SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, + edmProperty.getName(), property.getValue().toString()); + } + } + + protected EdmComplexType resolveComplexType(final ServiceMetadata metadata, final EdmComplexType baseType, + final String derivedTypeName) throws SerializerException { + + String fullQualifiedName = baseType.getFullQualifiedName().getFullQualifiedNameAsString(); + if (derivedTypeName == null || + fullQualifiedName.equals(derivedTypeName)) { + return baseType; + } + EdmComplexType derivedType = metadata.getEdm().getComplexType(new FullQualifiedName(derivedTypeName)); + if (derivedType == null) { + throw new SerializerException("Complex Type not found", + SerializerException.MessageKeys.UNKNOWN_TYPE, derivedTypeName); + } + EdmComplexType type = derivedType.getBaseType(); + while (type != null) { + if (type.getFullQualifiedName().equals(baseType.getFullQualifiedName())) { + return derivedType; + } + type = type.getBaseType(); + } + throw new SerializerException("Wrong base type", + SerializerException.MessageKeys.WRONG_BASE_TYPE, derivedTypeName, + baseType.getFullQualifiedName().getFullQualifiedNameAsString()); + } + + private void writeComplex(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartObject(); + String derivedName = property.getType(); + final EdmComplexType resolvedType = resolveComplexType(metadata, (EdmComplexType) type, derivedName); + if (!isODataMetadataNone && !resolvedType.equals(type) || isODataMetadataFull) { + json.writeStringField(Constants.JSON_TYPE, "#" + property.getType()); + } + writeComplexValue(metadata, resolvedType, property.asComplex().getValue(), selectedPaths, + json); + json.writeEndObject(); + } + + private void writePrimitiveCollection(final EdmPrimitiveType type, final Property property, + final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, + final Boolean isUnicode, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartArray(); + for (Object value : property.asCollection()) { + switch (property.getValueType()) { + case COLLECTION_PRIMITIVE: + case COLLECTION_ENUM: + try { + writePrimitiveValue(property.getName(), type, value, isNullable, + maxLength, precision, scale, isUnicode, json); + } catch (EdmPrimitiveTypeException e) { + throw new SerializerException("Wrong value for property!", e, + SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, + property.getName(), property.getValue().toString()); + } + break; + case COLLECTION_GEOSPATIAL: + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + default: + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + } + } + json.writeEndArray(); + } + + private void writeComplexCollection(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, + final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartArray(); + for (Object value : property.asCollection()) { + switch (property.getValueType()) { + case COLLECTION_COMPLEX: + json.writeStartObject(); + if (isODataMetadataFull) { + json.writeStringField(Constants.JSON_TYPE, "#" + + type.getFullQualifiedName().getFullQualifiedNameAsString()); + } + writeComplexValue(metadata, type, ((ComplexValue) value).getValue(), selectedPaths, json); + json.writeEndObject(); + break; + default: + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + } + } + json.writeEndArray(); + } + + private void writePrimitive(final EdmPrimitiveType type, final Property property, + final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, + final Boolean isUnicode, final JsonGenerator json) + throws EdmPrimitiveTypeException, IOException, SerializerException { + if (property.isPrimitive()) { + writePrimitiveValue(property.getName(), type, property.asPrimitive(), + isNullable, maxLength, precision, scale, isUnicode, json); + } else if (property.isGeospatial()) { + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + } else if (property.isEnum()) { + writePrimitiveValue(property.getName(), type, property.asEnum(), + isNullable, maxLength, precision, scale, isUnicode, json); + } else { + throw new SerializerException("Inconsistent property type!", + SerializerException.MessageKeys.INCONSISTENT_PROPERTY_TYPE, property.getName()); + } + } + + protected void writePrimitiveValue(final String name, final EdmPrimitiveType type, final Object primitiveValue, + final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, + final Boolean isUnicode, final JsonGenerator json) throws EdmPrimitiveTypeException, IOException { + final String value = type.valueToString(primitiveValue, + isNullable, maxLength, precision, scale, isUnicode); + if (value == null) { + json.writeNull(); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) { + json.writeBoolean(Boolean.parseBoolean(value)); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single) + || (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64)) + && !isIEEE754Compatible) { + json.writeNumber(value); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Stream)) { + if (primitiveValue instanceof Link) { + Link stream = (Link) primitiveValue; + if (!isODataMetadataNone) { + if (stream.getMediaETag() != null) { + json.writeStringField(name + Constants.JSON_MEDIA_ETAG, stream.getMediaETag()); + } + if (stream.getType() != null) { + json.writeStringField(name + Constants.JSON_MEDIA_CONTENT_TYPE, stream.getType()); + } + } + if (isODataMetadataFull) { + if (stream.getRel() != null && stream.getRel().equals(Constants.NS_MEDIA_READ_LINK_REL)) { + json.writeStringField(name + Constants.JSON_MEDIA_READ_LINK, stream.getHref()); + } + if (stream.getRel() == null || stream.getRel().equals(Constants.NS_MEDIA_EDIT_LINK_REL)) { + json.writeStringField(name + Constants.JSON_MEDIA_EDIT_LINK, stream.getHref()); + } + } + } + } else { + json.writeString(value); + } + } + + protected void writeComplexValue(final ServiceMetadata metadata, + final EdmComplexType type, final List<Property> properties, + final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + + for (final String propertyName : type.getPropertyNames()) { + final Property property = findProperty(propertyName, properties); + if (selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, propertyName)) { + writeProperty(metadata, (EdmProperty) type.getProperty(propertyName), property, + selectedPaths == null ? null : ExpandSelectHelper.getReducedSelectedPaths(selectedPaths, propertyName), + json); + } + } + } + + protected void writeProperties(final ServiceMetadata metadata, final EdmStructuredType type, + final List<Property> properties, + final SelectOption select, final JsonGenerator json) + throws IOException, SerializerException { + final boolean all = ExpandSelectHelper.isAll(select); + final Set<String> selected = all ? new HashSet<String>() : ExpandSelectHelper.getSelectedPropertyNames(select + .getSelectItems()); + for (final String propertyName : type.getPropertyNames()) { + if (all || selected.contains(propertyName)) { + final EdmProperty edmProperty = type.getStructuralProperty(propertyName); + final Property property = findProperty(propertyName, properties); + final Set<List<String>> selectedPaths = all || edmProperty.isPrimitive() ? null : ExpandSelectHelper + .getSelectedPaths(select.getSelectItems(), propertyName); + writeProperty(metadata, edmProperty, property, selectedPaths, json); + } + } + } + + /** + * Get the ascii representation of the entity id + * or thrown an {@link SerializerException} if id is <code>null</code>. + * + * @param entity the entity + * @return ascii representation of the entity id + */ + private String getEntityId(Entity entity, EdmEntityType entityType, String name) throws SerializerException { + try { + if (entity != null) { + if (entity.getId() == null) { + if (entityType == null || entityType.getKeyPredicateNames() == null + || name == null) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } else { + final UriHelper uriHelper = new UriHelperImpl(); + entity.setId(URI.create(name + '(' + uriHelper.buildKeyPredicate(entityType, entity) + ')')); + return entity.getId().toASCIIString(); + } + } else { + return entity.getId().toASCIIString(); + } + } + return null; + } catch (Exception e) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } + } + + void writeInlineCount(final Integer count, final JsonGenerator json) + throws IOException { + if (count != null) { + String countValue = isIEEE754Compatible ? String.valueOf(count) : String.valueOf(count); + json.writeStringField(Constants.JSON_COUNT, countValue); + } + } + + ContextURL checkContextURL(final ContextURL contextURL) throws SerializerException { + if (isODataMetadataNone) { + return null; + } else if (contextURL == null) { + throw new SerializerException("ContextURL null!", SerializerException.MessageKeys.NO_CONTEXT_URL); + } + return contextURL; + } + + void writeContextURL(final ContextURL contextURL, final JsonGenerator json) throws IOException { + if (!isODataMetadataNone && contextURL != null) { + json.writeStringField(Constants.JSON_CONTEXT, ContextURLBuilder.create(contextURL).toASCIIString() + DELTA); + } + } + + boolean writeNextLink(final AbstractEntityCollection entitySet, final JsonGenerator json) + throws IOException { + if (entitySet.getNext() != null) { + json.writeStringField(Constants.JSON_NEXT_LINK, entitySet.getNext().toASCIIString()); + return true; + } else { + return false; + } + } + + void writeDeltaLink(final AbstractEntityCollection entitySet, final JsonGenerator json, boolean pagination) + throws IOException { + if (entitySet.getDeltaLink() != null && !pagination) { + json.writeStringField(Constants.JSON_DELTA_LINK, entitySet.getDeltaLink().toASCIIString()); + } + } + + protected void closeCircleStreamBufferOutput(final OutputStream outputStream, + final SerializerException cachedException) + throws SerializerException { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + if (cachedException != null) { + throw cachedException; + } else { + throw new SerializerException(IO_EXCEPTION_TEXT, e, + SerializerException.MessageKeys.IO_EXCEPTION); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/f7e5a5c7/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializerWithNavigations.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializerWithNavigations.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializerWithNavigations.java new file mode 100644 index 0000000..d2831b7 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/JsonDeltaSerializerWithNavigations.java @@ -0,0 +1,655 @@ +/* + * 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.io.OutputStream; +import java.net.URI; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.AbstractEntityCollection; +import org.apache.olingo.commons.api.data.Annotation; +import org.apache.olingo.commons.api.data.ComplexValue; +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.DeletedEntity; +import org.apache.olingo.commons.api.data.Delta; +import org.apache.olingo.commons.api.data.DeltaLink; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Link; +import org.apache.olingo.commons.api.data.Linked; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmStructuredType; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.EdmDeltaSerializer; +import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriHelper; +import org.apache.olingo.server.api.uri.queryoption.CountOption; +import org.apache.olingo.server.api.uri.queryoption.ExpandItem; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; +import org.apache.olingo.server.api.uri.queryoption.SelectOption; +import org.apache.olingo.server.core.serializer.SerializerResultImpl; +import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer; +import org.apache.olingo.server.core.serializer.utils.ContentTypeHelper; +import org.apache.olingo.server.core.serializer.utils.ContextURLBuilder; +import org.apache.olingo.server.core.serializer.utils.ExpandSelectHelper; +import org.apache.olingo.server.core.uri.UriHelperImpl; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +public class JsonDeltaSerializerWithNavigations implements EdmDeltaSerializer { + private static final String IO_EXCEPTION_TEXT = "An I/O exception occurred."; + private final boolean isIEEE754Compatible; + private final boolean isODataMetadataNone; + private final boolean isODataMetadataFull; + + public JsonDeltaSerializerWithNavigations(final ContentType contentType) { + isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType); + isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType); + isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType); + } + + @Override + public SerializerResult entityCollection(ServiceMetadata metadata, EdmEntityType referencedEntityType, Delta delta, + EntityCollectionSerializerOptions options) throws SerializerException { + OutputStream outputStream = null; + SerializerException cachedException = null; + boolean pagination = false; + try { + CircleStreamBuffer buffer = new CircleStreamBuffer(); + outputStream = buffer.getOutputStream(); + JsonGenerator json = new JsonFactory().createGenerator(outputStream); + json.writeStartObject(); + + final ContextURL contextURL = checkContextURL(options == null ? null : options.getContextURL()); + writeContextURL(contextURL, json); + + if (options != null && options.getCount() != null && options.getCount().getValue()) { + writeInlineCount(delta.getCount(), json); + } + json.writeFieldName(Constants.VALUE); + writeEntitySet(metadata, referencedEntityType, delta, options, json); + + pagination = writeNextLink(delta, json); + writeDeltaLink(delta, json, pagination); + + 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); + } + + } + + protected void closeCircleStreamBufferOutput(final OutputStream outputStream, + final SerializerException cachedException) + throws SerializerException { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + if (cachedException != null) { + throw cachedException; + } else { + throw new SerializerException(IO_EXCEPTION_TEXT, e, + SerializerException.MessageKeys.IO_EXCEPTION); + } + } + } + } + protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType, + final Delta entitySet, final EntityCollectionSerializerOptions options, + final JsonGenerator json) throws IOException, + SerializerException { + json.writeStartArray(); + for (final Entity entity : entitySet.getEntities()) { + writeAddedUpdatedEntity(metadata, entityType, entity, options.getExpand(), options.getSelect(), + options.getContextURL(), false, options.getContextURL().getEntitySetOrSingletonOrType(), json); + } + for (final DeletedEntity deletedEntity : entitySet.getDeletedEntities()) { + writeDeletedEntity(deletedEntity, json); + } + for (final DeltaLink addedLink : entitySet.getAddedLinks()) { + writeLink(addedLink, options, json, true); + } + for (final DeltaLink deletedLink : entitySet.getDeletedLinks()) { + writeLink(deletedLink, options, json, false); + } + json.writeEndArray(); + } + + private void writeLink(DeltaLink link, EntityCollectionSerializerOptions options, + JsonGenerator json, boolean isAdded) throws IOException, SerializerException { + try { + json.writeStartObject(); + String entityId = options.getContextURL().getEntitySetOrSingletonOrType();// throw error if not set id + String operation = isAdded ? Constants.LINK : Constants.DELETEDLINK; + json.writeStringField(Constants.AT + Constants.CONTEXT, Constants.HASH + entityId + operation); + if (link != null) { + if (link.getSource() != null) { + json.writeStringField(Constants.ATTR_SOURCE, link.getSource().toString()); + } else { + throw new SerializerException("DeltaLink source is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Source"); + } + if (link.getRelationship() != null) { + json.writeStringField(Constants.ATTR_RELATIONSHIP, link.getRelationship().toString()); + } else { + throw new SerializerException("DeltaLink relationship is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Relationship"); + } + if (link.getTarget() != null) { + json.writeStringField(Constants.ERROR_TARGET, link.getTarget().toString()); + } else { + throw new SerializerException("DeltaLink target is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Target"); + } + } else { + throw new SerializerException("DeltaLink is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, "Delta Link"); + } + json.writeEndObject(); + } catch (IOException e) { + throw new SerializerException("Entity id is null.", + SerializerException.MessageKeys.MISSING_ID); + } + } + + private void writeDeletedEntity(Entity deletedEntity, + JsonGenerator json) throws IOException, SerializerException { + if (deletedEntity.getId() == null) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } + json.writeStartObject(); + if (isODataMetadataFull) { + + json.writeStringField(Constants.AT + Constants.CONTEXT, Constants.HASH + deletedEntity.getId().toASCIIString() + + Constants.DELETEDENTITY); + } + if (((DeletedEntity) deletedEntity).getReason() == null) { + throw new SerializerException("DeletedEntity reason is null.", + SerializerException.MessageKeys.MISSING_DELTA_PROPERTY, Constants.REASON); + } + json.writeFieldName(Constants.AT + Constants.REMOVED); + json.writeStartObject(); + json.writeStringField(Constants.ELEM_REASON, + ((DeletedEntity) deletedEntity).getReason().name()); + List<Annotation> annotations = deletedEntity.getAnnotations(); + if (annotations != null && annotations.size() > 0) { + for (Annotation annotation : annotations) { + json.writeStringField(Constants.AT + annotation.getTerm(), annotation.getValue().toString()); + } + } + json.writeEndObject(); + List<Property> properties = deletedEntity.getProperties(); + if (properties != null && properties.size() > 0) { + for (Property property : properties) { + json.writeStringField(property.getName(), property.getValue().toString()); + } + } + json.writeStringField(Constants.AT + Constants.ATOM_ATTR_ID, deletedEntity.getId().toASCIIString()); + json.writeEndObject(); + + } + + public void writeAddedUpdatedEntity(final ServiceMetadata metadata, final EdmEntityType entityType, + final Entity entity, final ExpandOption expand, final SelectOption select, final ContextURL url, + final boolean onlyReference, String name, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartObject(); + if (entity.getId() != null && url != null) { + name = url.getEntitySetOrSingletonOrType(); + String entityId = entity.getId().toString(); + if (!entityId.contains(name)) { + String entityName = entityId.substring(0, entityId.indexOf("(")); + if (!entityName.equals(name)) { + json.writeStringField(Constants.AT + Constants.CONTEXT, Constants.HASH + entityName + + Constants.ENTITY); + } + } + } + json.writeStringField(Constants.AT + Constants.ATOM_ATTR_ID, getEntityId(entity, entityType, name)); + writeProperties(metadata, entityType, entity.getProperties(), select, json); + writeNavigationProperties(metadata, entityType, entity, expand, name, json); + json.writeEndObject(); + + } + + private Property findProperty(final String propertyName, final List<Property> properties) { + for (final Property property : properties) { + if (propertyName.equals(property.getName())) { + return property; + } + } + return null; + } + + protected void writeProperty(final ServiceMetadata metadata, + final EdmProperty edmProperty, final Property property, + final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + boolean isStreamProperty = isStreamProperty(edmProperty); + if (property != null) { + if (!isStreamProperty) { + json.writeFieldName(edmProperty.getName()); + } + writePropertyValue(metadata, edmProperty, property, selectedPaths, json); + } + } + + private boolean isStreamProperty(EdmProperty edmProperty) { + final EdmType type = edmProperty.getType(); + return (edmProperty.isPrimitive() && type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Stream)); + } + + private void writePropertyValue(final ServiceMetadata metadata, final EdmProperty edmProperty, + final Property property, final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + final EdmType type = edmProperty.getType(); + try { + if (edmProperty.isPrimitive() + || type.getKind() == EdmTypeKind.ENUM || type.getKind() == EdmTypeKind.DEFINITION) { + if (edmProperty.isCollection()) { + writePrimitiveCollection((EdmPrimitiveType) type, property, + edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), json); + } else { + writePrimitive((EdmPrimitiveType) type, property, + edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), json); + } + } else if (property.isComplex()) { + if (edmProperty.isCollection()) { + writeComplexCollection(metadata, (EdmComplexType) type, property, selectedPaths, json); + } else { + writeComplex(metadata, (EdmComplexType) type, property, selectedPaths, json); + } + } else { + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, edmProperty.getName()); + } + } catch (final EdmPrimitiveTypeException e) { + throw new SerializerException("Wrong value for property!", e, + SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, + edmProperty.getName(), property.getValue().toString()); + } + } + + protected EdmComplexType resolveComplexType(final ServiceMetadata metadata, final EdmComplexType baseType, + final String derivedTypeName) throws SerializerException { + + String fullQualifiedName = baseType.getFullQualifiedName().getFullQualifiedNameAsString(); + if (derivedTypeName == null || + fullQualifiedName.equals(derivedTypeName)) { + return baseType; + } + EdmComplexType derivedType = metadata.getEdm().getComplexType(new FullQualifiedName(derivedTypeName)); + if (derivedType == null) { + throw new SerializerException("Complex Type not found", + SerializerException.MessageKeys.UNKNOWN_TYPE, derivedTypeName); + } + EdmComplexType type = derivedType.getBaseType(); + while (type != null) { + if (type.getFullQualifiedName().equals(baseType.getFullQualifiedName())) { + return derivedType; + } + type = type.getBaseType(); + } + throw new SerializerException("Wrong base type", + SerializerException.MessageKeys.WRONG_BASE_TYPE, derivedTypeName, + baseType.getFullQualifiedName().getFullQualifiedNameAsString()); + } + + private void writeComplex(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartObject(); + String derivedName = property.getType(); + final EdmComplexType resolvedType = resolveComplexType(metadata, (EdmComplexType) type, derivedName); + if (!isODataMetadataNone && !resolvedType.equals(type) || isODataMetadataFull) { + json.writeStringField(Constants.JSON_TYPE, "#" + property.getType()); + } + writeComplexValue(metadata, resolvedType, property.asComplex().getValue(), selectedPaths, + json); + json.writeEndObject(); + } + + private void writePrimitiveCollection(final EdmPrimitiveType type, final Property property, + final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, + final Boolean isUnicode, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartArray(); + for (Object value : property.asCollection()) { + switch (property.getValueType()) { + case COLLECTION_PRIMITIVE: + case COLLECTION_ENUM: + try { + writePrimitiveValue(property.getName(), type, value, isNullable, + maxLength, precision, scale, isUnicode, json); + } catch (EdmPrimitiveTypeException e) { + throw new SerializerException("Wrong value for property!", e, + SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, + property.getName(), property.getValue().toString()); + } + break; + case COLLECTION_GEOSPATIAL: + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + default: + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + } + } + json.writeEndArray(); + } + + private void writeComplexCollection(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, + final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + json.writeStartArray(); + for (Object value : property.asCollection()) { + switch (property.getValueType()) { + case COLLECTION_COMPLEX: + json.writeStartObject(); + if (isODataMetadataFull) { + json.writeStringField(Constants.JSON_TYPE, "#" + + type.getFullQualifiedName().getFullQualifiedNameAsString()); + } + writeComplexValue(metadata, type, ((ComplexValue) value).getValue(), selectedPaths, json); + json.writeEndObject(); + break; + default: + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + } + } + json.writeEndArray(); + } + + private void writePrimitive(final EdmPrimitiveType type, final Property property, + final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, + final Boolean isUnicode, final JsonGenerator json) + throws EdmPrimitiveTypeException, IOException, SerializerException { + if (property.isPrimitive()) { + writePrimitiveValue(property.getName(), type, property.asPrimitive(), + isNullable, maxLength, precision, scale, isUnicode, json); + } else if (property.isGeospatial()) { + throw new SerializerException("Property type not yet supported!", + SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, property.getName()); + } else if (property.isEnum()) { + writePrimitiveValue(property.getName(), type, property.asEnum(), + isNullable, maxLength, precision, scale, isUnicode, json); + } else { + throw new SerializerException("Inconsistent property type!", + SerializerException.MessageKeys.INCONSISTENT_PROPERTY_TYPE, property.getName()); + } + } + + protected void writePrimitiveValue(final String name, final EdmPrimitiveType type, final Object primitiveValue, + final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, + final Boolean isUnicode, final JsonGenerator json) throws EdmPrimitiveTypeException, IOException { + final String value = type.valueToString(primitiveValue, + isNullable, maxLength, precision, scale, isUnicode); + if (value == null) { + json.writeNull(); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) { + json.writeBoolean(Boolean.parseBoolean(value)); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single) + || (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64)) + && !isIEEE754Compatible) { + json.writeNumber(value); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Stream)) { + if (primitiveValue instanceof Link) { + Link stream = (Link) primitiveValue; + if (!isODataMetadataNone) { + if (stream.getMediaETag() != null) { + json.writeStringField(name + Constants.JSON_MEDIA_ETAG, stream.getMediaETag()); + } + if (stream.getType() != null) { + json.writeStringField(name + Constants.JSON_MEDIA_CONTENT_TYPE, stream.getType()); + } + } + if (isODataMetadataFull) { + if (stream.getRel() != null && stream.getRel().equals(Constants.NS_MEDIA_READ_LINK_REL)) { + json.writeStringField(name + Constants.JSON_MEDIA_READ_LINK, stream.getHref()); + } + if (stream.getRel() == null || stream.getRel().equals(Constants.NS_MEDIA_EDIT_LINK_REL)) { + json.writeStringField(name + Constants.JSON_MEDIA_EDIT_LINK, stream.getHref()); + } + } + } + } else { + json.writeString(value); + } + } + + protected void writeComplexValue(final ServiceMetadata metadata, + final EdmComplexType type, final List<Property> properties, + final Set<List<String>> selectedPaths, final JsonGenerator json) + throws IOException, SerializerException { + + for (final String propertyName : type.getPropertyNames()) { + final Property property = findProperty(propertyName, properties); + if (selectedPaths == null || ExpandSelectHelper.isSelected(selectedPaths, propertyName)) { + writeProperty(metadata, (EdmProperty) type.getProperty(propertyName), property, + selectedPaths == null ? null : ExpandSelectHelper.getReducedSelectedPaths(selectedPaths, propertyName), + json); + } + } + } + + protected void writeProperties(final ServiceMetadata metadata, final EdmStructuredType type, + final List<Property> properties, + final SelectOption select, final JsonGenerator json) + throws IOException, SerializerException { + final boolean all = ExpandSelectHelper.isAll(select); + final Set<String> selected = all ? new HashSet<String>() : ExpandSelectHelper.getSelectedPropertyNames(select + .getSelectItems()); + for (final String propertyName : type.getPropertyNames()) { + if ((all || selected.contains(propertyName)) && properties.size() > 0) { + final EdmProperty edmProperty = type.getStructuralProperty(propertyName); + final Property property = findProperty(propertyName, properties); + final Set<List<String>> selectedPaths = all || edmProperty.isPrimitive() ? null : ExpandSelectHelper + .getSelectedPaths(select.getSelectItems(), propertyName); + writeProperty(metadata, edmProperty, property, selectedPaths, json); + } + } + } + + protected void writeNavigationProperties(final ServiceMetadata metadata, + final EdmStructuredType type, final Linked linked, final ExpandOption expand, + final String name, final JsonGenerator json) throws SerializerException, IOException { + if (ExpandSelectHelper.hasExpand(expand)) { + final boolean expandAll = ExpandSelectHelper.getExpandAll(expand) != null; + final Set<String> expanded = expandAll ? new HashSet<String>() : ExpandSelectHelper.getExpandedPropertyNames( + expand.getExpandItems()); + for (final String propertyName : type.getNavigationPropertyNames()) { + if (expandAll || expanded.contains(propertyName)) { + final EdmNavigationProperty property = type.getNavigationProperty(propertyName); + final Link navigationLink = linked.getNavigationLink(property.getName()); + final ExpandItem innerOptions = expandAll ? null : ExpandSelectHelper.getExpandItem(expand.getExpandItems(), + propertyName); + if (innerOptions != null && innerOptions.getLevelsOption() != null) { + throw new SerializerException("Expand option $levels is not supported.", + SerializerException.MessageKeys.NOT_IMPLEMENTED); + } + if (navigationLink != null) { + writeExpandedNavigationProperty(metadata, property, navigationLink, + innerOptions == null ? null : innerOptions.getExpandOption(), + innerOptions == null ? null : innerOptions.getSelectOption(), + innerOptions == null ? null : innerOptions.getCountOption(), + innerOptions == null ? false : innerOptions.hasCountPath(), + innerOptions == null ? false : innerOptions.isRef(), + name, json); + } + } + } + } + } + + protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType, + final AbstractEntityCollection entitySet, final ExpandOption expand, final SelectOption select, + final boolean onlyReference, String name, final JsonGenerator json) throws IOException, + SerializerException { + json.writeStartArray(); + for (final Entity entity : entitySet) { + if (onlyReference) { + json.writeStartObject(); + json.writeStringField(Constants.JSON_ID, getEntityId(entity, entityType, null)); + json.writeEndObject(); + } else { + if (entity instanceof DeletedEntity) { + writeDeletedEntity(entity, json); + } else { + writeAddedUpdatedEntity(metadata, entityType, entity, expand, select, null, false, name, json); + } + + } + } + json.writeEndArray(); + } + + protected void writeExpandedNavigationProperty( + final ServiceMetadata metadata, final EdmNavigationProperty property, + final Link navigationLink, final ExpandOption innerExpand, + final SelectOption innerSelect, final CountOption innerCount, + final boolean writeOnlyCount, final boolean writeOnlyRef, final String name, + final JsonGenerator json) throws IOException, SerializerException { + + if (property.isCollection()) { + if (navigationLink != null && navigationLink.getInlineEntitySet() != null) { + json.writeFieldName(property.getName() + Constants.AT + Constants.DELTAVALUE); + writeEntitySet(metadata, property.getType(), navigationLink.getInlineEntitySet(), innerExpand, + innerSelect, writeOnlyRef, name, json); + } + + } else { + json.writeFieldName(property.getName()+ Constants.AT + Constants.DELTAVALUE); + if (navigationLink != null && navigationLink.getInlineEntity() != null) { + if (navigationLink.getInlineEntity() instanceof DeletedEntity) { + writeDeletedEntity(navigationLink.getInlineEntity(), json); + } else { + writeAddedUpdatedEntity(metadata, property.getType(), navigationLink.getInlineEntity(), + innerExpand, innerSelect, null, writeOnlyRef, name, json); + } + } + } + } + + /** + * Get the ascii representation of the entity id + * or thrown an {@link SerializerException} if id is <code>null</code>. + * + * @param entity the entity + * @return ascii representation of the entity id + */ + private String getEntityId(Entity entity, EdmEntityType entityType, String name) throws SerializerException { + try { + if (entity != null) { + if (entity.getId() == null) { + if (entityType == null || entityType.getKeyPredicateNames() == null + || name == null) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } else { + final UriHelper uriHelper = new UriHelperImpl(); + entity.setId(URI.create(name + '(' + uriHelper.buildKeyPredicate(entityType, entity) + ')')); + return entity.getId().toASCIIString(); + } + } else { + return entity.getId().toASCIIString(); + } + } + return null; + } catch (Exception e) { + throw new SerializerException("Entity id is null.", SerializerException.MessageKeys.MISSING_ID); + } + } + + void writeInlineCount(final Integer count, final JsonGenerator json) + throws IOException { + if (count != null) { + String countValue = isIEEE754Compatible ? String.valueOf(count) : String.valueOf(count); + json.writeStringField(Constants.AT + Constants.ATOM_ELEM_COUNT, countValue); + } + } + + ContextURL checkContextURL(final ContextURL contextURL) throws SerializerException { + if (isODataMetadataNone) { + return null; + } else if (contextURL == null) { + throw new SerializerException("ContextURL null!", SerializerException.MessageKeys.NO_CONTEXT_URL); + } + return contextURL; + } + + void writeContextURL(final ContextURL contextURL, final JsonGenerator json) throws IOException { + if (!isODataMetadataNone && contextURL != null) { + String context = Constants.AT + Constants.CONTEXT; + json.writeStringField(context, ContextURLBuilder.create(contextURL).toASCIIString() + Constants.DELTA); + } + } + + boolean writeNextLink(final AbstractEntityCollection entitySet, final JsonGenerator json) + throws IOException { + if (entitySet.getNext() != null) { + json.writeStringField(Constants.JSON_NEXT_LINK, entitySet.getNext().toASCIIString()); + return true; + } else { + return false; + } + } + + void writeDeltaLink(final AbstractEntityCollection entitySet, final JsonGenerator json, + final boolean pagination) + throws IOException { + if (entitySet.getDeltaLink() != null && !pagination) { + json.writeStringField(Constants.DELTALINK, entitySet.getDeltaLink().toASCIIString()); + + } + } +}
