blob: 80a44650ac6d205859d260de0257d0fedfe3f5ce [file] [log] [blame]
/*******************************************************************************
* 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.odata2.core.ep.producer;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.olingo.odata2.api.ODataCallback;
import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.edm.EdmEntityType;
import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.edm.EdmMapping;
import org.apache.olingo.odata2.api.edm.EdmMultiplicity;
import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
import org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent;
import org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent;
import org.apache.olingo.odata2.api.ep.callback.WriteCallbackContext;
import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackContext;
import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackResult;
import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackContext;
import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackResult;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.core.commons.ContentType;
import org.apache.olingo.odata2.core.commons.Encoder;
import org.apache.olingo.odata2.core.ep.EntityProviderProducerException;
import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
import org.apache.olingo.odata2.core.ep.util.FormatJson;
import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
/**
* Producer for writing an entity in JSON, also usable for function imports
* returning a single instance of an entity type.
*
*/
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 && !properties.isOmitJsonWrapper()) {
jsonStreamWriter.beginObject().name(FormatJson.D);
}
jsonStreamWriter.beginObject();
boolean containsMetadata = false;
if (!properties.isContentOnly() || (properties.isContentOnly() && properties.isIncludeMetadataInContentOnly())) {
writeMetadata(entityInfo, data, type);
containsMetadata = true;
}
writeProperties(entityInfo, data, type, containsMetadata);
if (!properties.isContentOnly()) {
writeNavigationProperties(writer, entityInfo, data, type);
} else {
writeAdditonalLinksInContentOnlyCase(entityInfo);
}
jsonStreamWriter.endObject();
if (isRootElement && !properties.isOmitJsonWrapper()) {
jsonStreamWriter.endObject();
}
writer.flush();
} catch (final IOException e) {
throw new EntityProviderProducerException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
.getSimpleName()), e);
} catch (final EdmException e) {
throw new EntityProviderProducerException(e.getMessageReference(), e);
}
}
private void writeNavigationProperties(final Writer writer, final EntityInfoAggregator entityInfo,
final Map<String, Object> data,
final EdmEntityType type) throws EdmException, EntityProviderException, IOException {
for (final String navigationPropertyName : type.getNavigationPropertyNames()) {
if (entityInfo.getSelectedNavigationPropertyNames().contains(navigationPropertyName)) {
jsonStreamWriter.separator();
jsonStreamWriter.name(navigationPropertyName);
if (entityInfo.getExpandedNavigationPropertyNames().contains(navigationPropertyName)) {
String navigationPropertyNameFqn = concat(entityInfo.getEntityType().getName(), navigationPropertyName);
Map<String, ODataCallback> callbacks = properties.getCallbacks();
if (callbacks != null && (callbacks.containsKey(navigationPropertyName) ||
callbacks.containsKey(navigationPropertyNameFqn))) {
writeExpandedNavigationProperty(writer, entityInfo, data, type, navigationPropertyName);
} else {
writeDeferredUri(entityInfo, navigationPropertyName);
}
} else {
writeDeferredUri(entityInfo, navigationPropertyName);
}
}
}
}
private String concat(String s1, String s2) {
return s1.concat(".").concat(s2);
}
private void writeExpandedNavigationProperty(final Writer writer, final EntityInfoAggregator entityInfo,
final Map<String, Object> data,
final EdmEntityType type, final String navigationPropertyName) throws EdmException, EntityProviderException,
IOException {
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.setCurrentWriteProperties(properties);
context.setCurrentExpandSelectTreeNode(properties.getExpandSelectTree().getLinks().get(
navigationPropertyName));
Map<String, ODataCallback> callbacks = properties.getCallbacks();
String navigationPropertyNameFqn = concat(entityInfo.getEntityType().getName(), navigationPropertyName);
ODataCallback callback = callbacks.get(navigationPropertyName);
callback = callback == null ? callbacks.get(navigationPropertyNameFqn) : callback;
if (callback == null) {
throw new EntityProviderProducerException(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>>();
}
//This statement is used for the client use case. Flag should never be set on server side
if(properties.isOmitInlineForNullData() && inlineData.isEmpty()){
writeDeferredUri(entityInfo, navigationPropertyName);
return;
}
final EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
final EntityInfoAggregator inlineEntityInfo =
EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
JsonFeedEntityProducer jsonFeedEntityProducer = new JsonFeedEntityProducer(inlineProperties);
if (properties.isResponsePayload()) {
jsonFeedEntityProducer.appendAsObject(writer, inlineEntityInfo, inlineData, false);
} else {
jsonFeedEntityProducer.appendAsArray(writer, inlineEntityInfo, inlineData);
}
} else {
final WriteEntryCallbackResult result =
((OnWriteEntryContent) callback).retrieveEntryResult((WriteEntryCallbackContext) context);
Map<String, Object> inlineData = result.getEntryData();
//This statement is used for the client use case. Flag should never be set on server side
if(properties.isOmitInlineForNullData() && (inlineData == null || inlineData.isEmpty())){
writeDeferredUri(entityInfo, navigationPropertyName);
return;
}
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 EntityProviderProducerException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
.getSimpleName()), e);
}
}
private void writeProperties(final EntityInfoAggregator entityInfo, final Map<String, Object> data,
final EdmEntityType type, boolean containsMetadata) throws EdmException, EntityProviderException, IOException {
// if the payload contains metadata we must not omit the first comm as it separates the _metadata object form the
// properties
boolean omitComma = !containsMetadata;
List<String> propertyNames = type.getPropertyNames();
for (final String propertyName : propertyNames) {
if (properties.isDataBasedPropertySerialization() && ((Map<?,?>)data).containsKey(propertyName)) {
omitComma = appendPropertyNameValue(entityInfo, data, omitComma, propertyName);
} else if (!properties.isDataBasedPropertySerialization() && entityInfo.getSelectedPropertyNames()
.contains(propertyName)) {
omitComma = appendPropertyNameValue(entityInfo, data, omitComma, propertyName);
}
}
}
/**
* @param entityInfo
* @param data
* @param omitComma
* @param propertyName
* @return
* @throws IOException
* @throws EdmException
* @throws EntityProviderException
*/
private boolean appendPropertyNameValue(final EntityInfoAggregator entityInfo, final Map<String, Object> data,
boolean omitComma, String propertyName) throws IOException, EdmException, EntityProviderException {
if (omitComma) {
omitComma = false;
} else {
jsonStreamWriter.separator();
}
jsonStreamWriter.name(propertyName);
JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter,
entityInfo.getPropertyInfo(propertyName),
data.get(propertyName),
properties.isValidatingFacets(), properties.isDataBasedPropertySerialization());
return omitComma;
}
private void writeMetadata(final EntityInfoAggregator entityInfo, final Map<String, Object> data,
final EdmEntityType type) throws IOException, EntityProviderException, EdmException {
if (properties.getServiceRoot() == null) {
location = "";
} else {
location = properties.getServiceRoot().toASCIIString() +
AtomEntryEntityProducer.createSelfLink(entityInfo, data, null);
}
jsonStreamWriter.name(FormatJson.METADATA);
jsonStreamWriter.beginObject();
jsonStreamWriter.namedStringValue(FormatJson.ID, location);
jsonStreamWriter.separator();
jsonStreamWriter.namedStringValue(FormatJson.URI, location);
jsonStreamWriter.separator();
jsonStreamWriter.namedStringValueRaw(FormatJson.TYPE, type.getNamespace() + Edm.DELIMITER + type.getName());
if (!properties.isOmitETag()) {
eTag = AtomEntryEntityProducer.createETag(entityInfo, data);
if (eTag != null) {
jsonStreamWriter.separator();
jsonStreamWriter.namedStringValue(FormatJson.ETAG, eTag);
}
}
if (type.hasStream()) {
jsonStreamWriter.separator();
EdmMapping entityTypeMapping = entityInfo.getEntityType().getMapping();
String mediaResourceMimeType = null;
String mediaSrc = null;
if (entityTypeMapping != null) {
String mediaResourceSourceKey = entityTypeMapping.getMediaResourceSourceKey();
if (mediaResourceSourceKey != null) {
mediaSrc = (String) data.get(mediaResourceSourceKey);
}
if (mediaSrc == null) {
mediaSrc = location + "/$value";
}
String mediaResourceMimeTypeKey = entityTypeMapping.getMediaResourceMimeTypeKey();
if (mediaResourceMimeTypeKey != null) {
mediaResourceMimeType = (String) data.get(mediaResourceMimeTypeKey);
}
if (mediaResourceMimeType == null) {
mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
}
} else {
mediaSrc = location + "/$value";
mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
}
jsonStreamWriter.namedStringValueRaw(FormatJson.CONTENT_TYPE, mediaResourceMimeType);
jsonStreamWriter.separator();
jsonStreamWriter.namedStringValue(FormatJson.MEDIA_SRC, mediaSrc);
jsonStreamWriter.separator();
jsonStreamWriter.namedStringValue(FormatJson.EDIT_MEDIA, location + "/$value");
}
jsonStreamWriter.endObject();
}
private void writeDeferredUri(final EntityInfoAggregator entityInfo, final String navigationPropertyName)
throws IOException, EntityProviderException, EdmException {
jsonStreamWriter.beginObject()
.name(FormatJson.DEFERRED);
String target = null;
final Map<String, Map<String, Object>> links = properties.getAdditionalLinks();
final Map<String, Object> key = links == null ? null : links.get(navigationPropertyName);
if (key == null || key.isEmpty()) {
target = location + "/" + Encoder.encode(navigationPropertyName);
} else {
target = createCustomTargetLink(entityInfo, navigationPropertyName, key);
}
JsonLinkEntityProducer.appendUri(jsonStreamWriter, target);
jsonStreamWriter.endObject();
}
private String createCustomTargetLink(final EntityInfoAggregator entityInfo, final String navigationPropertyName,
final Map<String, Object> key) throws EntityProviderException, EdmException {
String target;
final EntityInfoAggregator targetEntityInfo = EntityInfoAggregator.create(
entityInfo.getEntitySet().getRelatedEntitySet(
(EdmNavigationProperty) entityInfo.getEntityType().getProperty(navigationPropertyName)));
target = (properties.getServiceRoot() == null ? "" : properties.getServiceRoot().toASCIIString())
+ AtomEntryEntityProducer.createSelfLink(targetEntityInfo, key, null);
return target;
}
private void writeAdditonalLinksInContentOnlyCase(final EntityInfoAggregator entityInfo)
throws IOException, EntityProviderException, EdmException {
final Map<String, Map<String, Object>> links = properties.getAdditionalLinks();
if (links != null && !links.isEmpty()) {
for (Entry<String, Map<String, Object>> entry : links.entrySet()) {
Map<String, Object> navigationKeyMap = entry.getValue();
if (navigationKeyMap != null && !navigationKeyMap.isEmpty()) {
String target = createCustomTargetLink(entityInfo, entry.getKey(), navigationKeyMap);
jsonStreamWriter.separator();
jsonStreamWriter.name(entry.getKey());
jsonStreamWriter.beginObject()
.name(FormatJson.DEFERRED);
JsonLinkEntityProducer.appendUri(jsonStreamWriter, target);
jsonStreamWriter.endObject();
}
}
}
}
public String getETag() {
return eTag;
}
public String getLocation() {
return location;
}
}