/*******************************************************************************
* Copyright 2013 SAP AG
*
* Licensed 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 com.sap.core.odata.core.ep.producer;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.sap.core.odata.api.ODataCallback;
import com.sap.core.odata.api.commons.HttpContentType;
import com.sap.core.odata.api.edm.Edm;
import com.sap.core.odata.api.edm.EdmEntitySet;
import com.sap.core.odata.api.edm.EdmEntityType;
import com.sap.core.odata.api.edm.EdmException;
import com.sap.core.odata.api.edm.EdmMultiplicity;
import com.sap.core.odata.api.edm.EdmNavigationProperty;
import com.sap.core.odata.api.ep.EntityProviderException;
import com.sap.core.odata.api.ep.EntityProviderWriteProperties;
import com.sap.core.odata.api.ep.callback.OnWriteEntryContent;
import com.sap.core.odata.api.ep.callback.OnWriteFeedContent;
import com.sap.core.odata.api.ep.callback.WriteCallbackContext;
import com.sap.core.odata.api.ep.callback.WriteEntryCallbackContext;
import com.sap.core.odata.api.ep.callback.WriteEntryCallbackResult;
import com.sap.core.odata.api.ep.callback.WriteFeedCallbackContext;
import com.sap.core.odata.api.ep.callback.WriteFeedCallbackResult;
import com.sap.core.odata.api.exception.ODataApplicationException;
import com.sap.core.odata.core.commons.Encoder;
import com.sap.core.odata.core.ep.aggregator.EntityInfoAggregator;
import com.sap.core.odata.core.ep.util.FormatJson;
import com.sap.core.odata.core.ep.util.JsonStreamWriter;
/**
* Producer for writing an entity in JSON, also usable for function imports
* returning a single instance of an entity type.
* @author SAP AG
*/
public class JsonEntryEntityProducer {
private final EntityProviderWriteProperties properties;
private String eTag;
private String location;
private JsonStreamWriter jsonStreamWriter;
public JsonEntryEntityProducer(final EntityProviderWriteProperties properties) throws EntityProviderException {
this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
}
public void append(final Writer writer, final EntityInfoAggregator entityInfo, final Map<String, Object> data, final boolean isRootElement) throws EntityProviderException {
final EdmEntityType type = entityInfo.getEntityType();
try {
jsonStreamWriter = new JsonStreamWriter(writer);
if (isRootElement) {
jsonStreamWriter.beginObject().name(FormatJson.D);
}
jsonStreamWriter.beginObject();
jsonStreamWriter.name(FormatJson.METADATA)
.beginObject();
final String self = AtomEntryEntityProducer.createSelfLink(entityInfo, data, null);
location = (properties.getServiceRoot() == null ? "" : properties.getServiceRoot().toASCIIString()) + self;
jsonStreamWriter.namedStringValue(FormatJson.ID, location).separator()
.namedStringValue(FormatJson.URI, location).separator()
.namedStringValueRaw(FormatJson.TYPE,
type.getNamespace() + Edm.DELIMITER + type.getName());
eTag = AtomEntryEntityProducer.createETag(entityInfo, data);
if (eTag != null) {
jsonStreamWriter.separator()
.namedStringValue(FormatJson.ETAG, eTag);
}
if (type.hasStream()) {
jsonStreamWriter.separator()
.namedStringValueRaw(FormatJson.CONTENT_TYPE,
properties.getMediaResourceMimeType() == null ?
type.getMapping() == null || type.getMapping().getMimeType() == null || data.get(type.getMapping().getMimeType()) == null ?
HttpContentType.APPLICATION_OCTET_STREAM : data.get(type.getMapping().getMimeType()).toString() :
properties.getMediaResourceMimeType())
.separator()
.namedStringValue(FormatJson.MEDIA_SRC, self + "/$value").separator()
.namedStringValue(FormatJson.EDIT_MEDIA, location + "/$value");
}
jsonStreamWriter.endObject();
for (final String propertyName : type.getPropertyNames()) {
if (entityInfo.getSelectedPropertyNames().contains(propertyName)) {
jsonStreamWriter.separator()
.name(propertyName);
JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, entityInfo.getPropertyInfo(propertyName), data.get(propertyName));
}
}
for (final String navigationPropertyName : type.getNavigationPropertyNames()) {
if (entityInfo.getSelectedNavigationPropertyNames().contains(navigationPropertyName)) {
jsonStreamWriter.separator()
.name(navigationPropertyName);
if (entityInfo.getExpandedNavigationPropertyNames().contains(navigationPropertyName)) {
if (properties.getCallbacks() != null && properties.getCallbacks().containsKey(navigationPropertyName)) {
final EdmNavigationProperty navigationProperty = (EdmNavigationProperty) type.getProperty(navigationPropertyName);
final boolean isFeed = navigationProperty.getMultiplicity() == EdmMultiplicity.MANY;
final EdmEntitySet entitySet = entityInfo.getEntitySet();
final EdmEntitySet inlineEntitySet = entitySet.getRelatedEntitySet(navigationProperty);
WriteCallbackContext context = isFeed ? new WriteFeedCallbackContext() : new WriteEntryCallbackContext();
context.setSourceEntitySet(entitySet);
context.setNavigationProperty(navigationProperty);
context.setEntryData(data);
context.setCurrentExpandSelectTreeNode(properties.getExpandSelectTree().getLinks().get(navigationPropertyName));
ODataCallback callback = properties.getCallbacks().get(navigationPropertyName);
if (callback == null) {
throw new EntityProviderException(EntityProviderException.EXPANDNOTSUPPORTED);
}
try {
if (isFeed) {
final WriteFeedCallbackResult result = ((OnWriteFeedContent) callback).retrieveFeedResult((WriteFeedCallbackContext) context);
List<Map<String, Object>> inlineData = result.getFeedData();
if (inlineData == null) {
inlineData = new ArrayList<Map<String, Object>>();
}
final EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
final EntityInfoAggregator inlineEntityInfo = EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
new JsonFeedEntityProducer(inlineProperties).append(writer, inlineEntityInfo, inlineData, false);
} else {
final WriteEntryCallbackResult result = ((OnWriteEntryContent) callback).retrieveEntryResult((WriteEntryCallbackContext) context);
Map<String, Object> inlineData = result.getEntryData();
if (inlineData != null && !inlineData.isEmpty()) {
final EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
final EntityInfoAggregator inlineEntityInfo = EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
new JsonEntryEntityProducer(inlineProperties).append(writer, inlineEntityInfo, inlineData, false);
} else {
jsonStreamWriter.unquotedValue("null");
}
}
} catch (final ODataApplicationException e) {
throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
}
} else {
writeDeferredUri(navigationPropertyName);
}
} else {
writeDeferredUri(navigationPropertyName);
}
}
}
jsonStreamWriter.endObject();
if (isRootElement) {
jsonStreamWriter.endObject();
}
writer.flush();
} catch (final IOException e) {
throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
} catch (final EdmException e) {
throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
}
}
private void writeDeferredUri(final String navigationPropertyName) throws IOException {
jsonStreamWriter.beginObject()
.name(FormatJson.DEFERRED);
JsonLinkEntityProducer.appendUri(jsonStreamWriter, location + "/" + Encoder.encode(navigationPropertyName));
jsonStreamWriter.endObject();
}
public String getETag() {
return eTag;
}
public String getLocation() {
return location;
}
}