blob: df23b311792d42384abad7495f8658dea052d3fb [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.annotation.processor.core;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.olingo.odata2.annotation.processor.core.datasource.DataSource;
import org.apache.olingo.odata2.annotation.processor.core.datasource.DataSource.BinaryData;
import org.apache.olingo.odata2.annotation.processor.core.datasource.ValueAccess;
import org.apache.olingo.odata2.api.ODataCallback;
import org.apache.olingo.odata2.api.batch.BatchHandler;
import org.apache.olingo.odata2.api.batch.BatchRequestPart;
import org.apache.olingo.odata2.api.batch.BatchResponsePart;
import org.apache.olingo.odata2.api.commons.HttpContentType;
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.commons.InlineCount;
import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmConcurrencyMode;
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.EdmFunctionImport;
import org.apache.olingo.odata2.api.edm.EdmLiteral;
import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
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.edm.EdmProperty;
import org.apache.olingo.odata2.api.edm.EdmSimpleType;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeKind;
import org.apache.olingo.odata2.api.edm.EdmStructuralType;
import org.apache.olingo.odata2.api.edm.EdmType;
import org.apache.olingo.odata2.api.edm.EdmTypeKind;
import org.apache.olingo.odata2.api.edm.EdmTyped;
import org.apache.olingo.odata2.api.ep.EntityProvider;
import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties;
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.ep.entry.ODataEntry;
import org.apache.olingo.odata2.api.ep.feed.ODataFeed;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.exception.ODataBadRequestException;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.exception.ODataHttpException;
import org.apache.olingo.odata2.api.exception.ODataNotFoundException;
import org.apache.olingo.odata2.api.exception.ODataNotImplementedException;
import org.apache.olingo.odata2.api.processor.ODataContext;
import org.apache.olingo.odata2.api.processor.ODataRequest;
import org.apache.olingo.odata2.api.processor.ODataResponse;
import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
import org.apache.olingo.odata2.api.uri.KeyPredicate;
import org.apache.olingo.odata2.api.uri.NavigationSegment;
import org.apache.olingo.odata2.api.uri.PathInfo;
import org.apache.olingo.odata2.api.uri.UriParser;
import org.apache.olingo.odata2.api.uri.expression.BinaryExpression;
import org.apache.olingo.odata2.api.uri.expression.CommonExpression;
import org.apache.olingo.odata2.api.uri.expression.ExpressionKind;
import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
import org.apache.olingo.odata2.api.uri.expression.LiteralExpression;
import org.apache.olingo.odata2.api.uri.expression.MemberExpression;
import org.apache.olingo.odata2.api.uri.expression.MethodExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderExpression;
import org.apache.olingo.odata2.api.uri.expression.PropertyExpression;
import org.apache.olingo.odata2.api.uri.expression.SortOrder;
import org.apache.olingo.odata2.api.uri.expression.UnaryExpression;
import org.apache.olingo.odata2.api.uri.info.DeleteUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetComplexPropertyUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntityCountUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntityLinkCountUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntityLinkUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetCountUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetLinksCountUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetLinksUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntityUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetFunctionImportUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetMediaResourceUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetSimplePropertyUriInfo;
import org.apache.olingo.odata2.api.uri.info.PostUriInfo;
import org.apache.olingo.odata2.api.uri.info.PutMergePatchUriInfo;
/**
* Implementation of the centralized parts of OData processing,
* allowing to use the simplified {@link DataSource} for the
* actual data handling.
*
*/
public class ListsProcessor extends DataSourceProcessor {
// TODO: Paging size should be configurable.
private static final int SERVER_PAGING_SIZE = 100;
public ListsProcessor(final DataSource dataSource, final ValueAccess valueAccess) {
super(dataSource, valueAccess);
}
@Override
public ODataResponse readEntitySet(final GetEntitySetUriInfo uriInfo, final String contentType)
throws ODataException {
ArrayList<Object> data = new ArrayList<Object>();
try {
data.addAll((List<?>) retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments()));
} catch (final ODataNotFoundException e) {
data.clear();
}
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
final InlineCount inlineCountType = uriInfo.getInlineCount();
final Integer count = applySystemQueryOptions(
entitySet,
data,
uriInfo.getFilter(),
inlineCountType,
uriInfo.getOrderBy(),
uriInfo.getSkipToken(),
uriInfo.getSkip(),
uriInfo.getTop());
ODataContext context = getContext();
String nextLink = null;
// Limit the number of returned entities and provide a "next" link
// if there are further entities.
// Almost all system query options in the current request must be carried
// over to the URI for the "next" link, with the exception of $skiptoken
// and $skip.
if (data.size() > SERVER_PAGING_SIZE) {
if (uriInfo.getOrderBy() == null
&& uriInfo.getSkipToken() == null
&& uriInfo.getSkip() == null
&& uriInfo.getTop() == null) {
sortInDefaultOrder(entitySet, data);
}
nextLink = context.getPathInfo().getServiceRoot().relativize(context.getPathInfo().getRequestUri()).toString();
nextLink = percentEncodeNextLink(nextLink);
nextLink += (nextLink.contains("?") ? "&" : "?")
+ "$skiptoken=" + getSkipToken(entitySet, data.get(SERVER_PAGING_SIZE));
while (data.size() > SERVER_PAGING_SIZE) {
data.remove(SERVER_PAGING_SIZE);
}
}
final EdmEntityType entityType = entitySet.getEntityType();
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
for (final Object entryData : data) {
values.add(getStructuralTypeValueMap(entryData, entityType));
}
final EntityProviderWriteProperties feedProperties = EntityProviderWriteProperties
.serviceRoot(context.getPathInfo().getServiceRoot())
.inlineCountType(inlineCountType)
.inlineCount(count)
.expandSelectTree(UriParser.createExpandSelectTree(uriInfo.getSelect(), uriInfo.getExpand()))
.callbacks(getCallbacks(data, entityType))
.nextLink(nextLink)
.build();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "writeFeed");
final ODataResponse response = EntityProvider.writeFeed(contentType, entitySet, values, feedProperties);
context.stopRuntimeMeasurement(timingHandle);
return ODataResponse.fromResponse(response).build();
}
String percentEncodeNextLink(final String link) {
if (link == null) {
return null;
}
return link.replaceAll("\\$skiptoken=.+?(?:&|$)", "")
.replaceAll("\\$skip=.+?(?:&|$)", "")
.replaceFirst("(?:\\?|&)$", ""); // Remove potentially trailing "?" or "&" left over from remove actions
}
@Override
public ODataResponse countEntitySet(final GetEntitySetCountUriInfo uriInfo, final String contentType)
throws ODataException {
ArrayList<Object> data = new ArrayList<Object>();
try {
data.addAll((List<?>) retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments()));
} catch (final ODataNotFoundException e) {
data.clear();
}
applySystemQueryOptions(
uriInfo.getTargetEntitySet(),
data,
uriInfo.getFilter(),
null,
null,
null,
uriInfo.getSkip(),
uriInfo.getTop());
return ODataResponse.fromResponse(EntityProvider.writeText(String.valueOf(data.size()))).build();
}
@Override
public ODataResponse readEntityLinks(final GetEntitySetLinksUriInfo uriInfo, final String contentType)
throws ODataException {
ArrayList<Object> data = new ArrayList<Object>();
try {
data.addAll((List<?>) retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments()));
} catch (final ODataNotFoundException e) {
data.clear();
}
final Integer count = applySystemQueryOptions(
uriInfo.getTargetEntitySet(),
data,
uriInfo.getFilter(),
uriInfo.getInlineCount(),
null, // uriInfo.getOrderBy(),
uriInfo.getSkipToken(),
uriInfo.getSkip(),
uriInfo.getTop());
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
for (final Object entryData : data) {
Map<String, Object> entryValues = new HashMap<String, Object>();
for (final EdmProperty property : entitySet.getEntityType().getKeyProperties()) {
entryValues.put(property.getName(), valueAccess.getPropertyValue(entryData, property));
}
values.add(entryValues);
}
ODataContext context = getContext();
final EntityProviderWriteProperties entryProperties = EntityProviderWriteProperties
.serviceRoot(context.getPathInfo().getServiceRoot())
.inlineCountType(uriInfo.getInlineCount())
.inlineCount(count)
.build();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "writeLinks");
final ODataResponse response = EntityProvider.writeLinks(contentType, entitySet, values, entryProperties);
context.stopRuntimeMeasurement(timingHandle);
return ODataResponse.fromResponse(response).build();
}
@Override
public ODataResponse countEntityLinks(final GetEntitySetLinksCountUriInfo uriInfo, final String contentType)
throws ODataException {
return countEntitySet((GetEntitySetCountUriInfo) uriInfo, contentType);
}
@Override
public ODataResponse readEntity(final GetEntityUriInfo uriInfo, final String contentType) throws ODataException {
final Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (!appliesFilter(data, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final ExpandSelectTreeNode expandSelectTreeNode =
UriParser.createExpandSelectTree(uriInfo.getSelect(), uriInfo.getExpand());
ODataResponse odr =
ODataResponse.fromResponse(writeEntry(uriInfo.getTargetEntitySet(), expandSelectTreeNode, data, contentType))
.build();
return odr;
}
@Override
public ODataResponse existsEntity(final GetEntityCountUriInfo uriInfo, final String contentType)
throws ODataException {
final Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
return ODataResponse.fromResponse(EntityProvider.writeText(appliesFilter(data, uriInfo.getFilter()) ? "1" : "0"))
.build();
}
@Override
public ODataResponse deleteEntity(final DeleteUriInfo uriInfo, final String contentType) throws ODataException {
dataSource.deleteData(
uriInfo.getStartEntitySet(),
mapKey(uriInfo.getKeyPredicates()));
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse createEntity(final PostUriInfo uriInfo, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
final EdmEntityType entityType = entitySet.getEntityType();
Object data = dataSource.newDataObject(entitySet);
ExpandSelectTreeNode expandSelectTree = null;
if (entityType.hasStream()) {
dataSource.createData(entitySet, data);
dataSource.writeBinaryData(entitySet, data,
new BinaryData(EntityProvider.readBinary(content), requestContentType));
} else {
final EntityProviderReadProperties properties = EntityProviderReadProperties.init()
.mergeSemantic(false)
.addTypeMappings(getStructuralTypeTypeMap(data, entityType))
.build();
final ODataEntry entryValues = parseEntry(entitySet, content, requestContentType, properties);
setStructuralTypeValuesFromMap(data, entityType, entryValues.getProperties(), false);
dataSource.createData(entitySet, data);
createInlinedEntities(entitySet, data, entryValues);
expandSelectTree = entryValues.getExpandSelectTree();
}
// Link back to the entity the target entity set is related to, if any.
final List<NavigationSegment> navigationSegments = uriInfo.getNavigationSegments();
if (!navigationSegments.isEmpty()) {
final List<NavigationSegment> previousSegments = navigationSegments.subList(0, navigationSegments.size() - 1);
final Object sourceData = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
previousSegments);
final EdmEntitySet previousEntitySet = previousSegments.isEmpty() ?
uriInfo.getStartEntitySet() : previousSegments.get(previousSegments.size() - 1).getEntitySet();
dataSource.writeRelation(previousEntitySet, sourceData, entitySet, getStructuralTypeValueMap(data, entityType));
}
return ODataResponse.fromResponse(writeEntry(uriInfo.getTargetEntitySet(), expandSelectTree, data, contentType))
.eTag(constructETag(entitySet, data)).build();
}
@Override
public ODataResponse updateEntity(final PutMergePatchUriInfo uriInfo, final InputStream content,
final String requestContentType, final boolean merge, final String contentType) throws ODataException {
Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (!appliesFilter(data, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
final EdmEntityType entityType = entitySet.getEntityType();
final EntityProviderReadProperties properties = EntityProviderReadProperties.init()
.mergeSemantic(merge)
.addTypeMappings(getStructuralTypeTypeMap(data, entityType))
.build();
final ODataEntry entryValues = parseEntry(entitySet, content, requestContentType, properties);
setStructuralTypeValuesFromMap(data, entityType, entryValues.getProperties(), merge);
return ODataResponse.newBuilder().eTag(constructETag(entitySet, data)).build();
}
@Override
public ODataResponse readEntityLink(final GetEntityLinkUriInfo uriInfo, final String contentType)
throws ODataException {
final Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
// if (!appliesFilter(data, uriInfo.getFilter()))
if (data == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
Map<String, Object> values = new HashMap<String, Object>();
for (final EdmProperty property : entitySet.getEntityType().getKeyProperties()) {
values.put(property.getName(), valueAccess.getPropertyValue(data, property));
}
ODataContext context = getContext();
final EntityProviderWriteProperties entryProperties = EntityProviderWriteProperties
.serviceRoot(context.getPathInfo().getServiceRoot())
.build();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "writeLink");
final ODataResponse response = EntityProvider.writeLink(contentType, entitySet, values, entryProperties);
context.stopRuntimeMeasurement(timingHandle);
return ODataResponse.fromResponse(response).build();
}
@Override
public ODataResponse existsEntityLink(final GetEntityLinkCountUriInfo uriInfo, final String contentType)
throws ODataException {
return existsEntity((GetEntityCountUriInfo) uriInfo, contentType);
}
@Override
public ODataResponse deleteEntityLink(final DeleteUriInfo uriInfo, final String contentType) throws ODataException {
final List<NavigationSegment> navigationSegments = uriInfo.getNavigationSegments();
final List<NavigationSegment> previousSegments = navigationSegments.subList(0, navigationSegments.size() - 1);
final Object sourceData = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
previousSegments);
final EdmEntitySet entitySet = previousSegments.isEmpty() ?
uriInfo.getStartEntitySet() : previousSegments.get(previousSegments.size() - 1).getEntitySet();
final EdmEntitySet targetEntitySet = uriInfo.getTargetEntitySet();
final Map<String, Object> keys = mapKey(uriInfo.getTargetKeyPredicates());
final Object targetData = dataSource.readRelatedData(entitySet, sourceData, targetEntitySet, keys);
// if (!appliesFilter(targetData, uriInfo.getFilter()))
if (targetData == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
dataSource.deleteRelation(entitySet, sourceData, targetEntitySet, keys);
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse createEntityLink(final PostUriInfo uriInfo, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
final List<NavigationSegment> navigationSegments = uriInfo.getNavigationSegments();
final List<NavigationSegment> previousSegments = navigationSegments.subList(0, navigationSegments.size() - 1);
final Object sourceData = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
previousSegments);
final EdmEntitySet entitySet = previousSegments.isEmpty() ?
uriInfo.getStartEntitySet() : previousSegments.get(previousSegments.size() - 1).getEntitySet();
final EdmEntitySet targetEntitySet = uriInfo.getTargetEntitySet();
final Map<String, Object> targetKeys = parseLink(targetEntitySet, content, requestContentType);
dataSource.writeRelation(entitySet, sourceData, targetEntitySet, targetKeys);
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse updateEntityLink(final PutMergePatchUriInfo uriInfo, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
final List<NavigationSegment> navigationSegments = uriInfo.getNavigationSegments();
final List<NavigationSegment> previousSegments = navigationSegments.subList(0, navigationSegments.size() - 1);
final Object sourceData = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
previousSegments);
final EdmEntitySet entitySet = previousSegments.isEmpty() ?
uriInfo.getStartEntitySet() : previousSegments.get(previousSegments.size() - 1).getEntitySet();
final EdmEntitySet targetEntitySet = uriInfo.getTargetEntitySet();
final Map<String, Object> keys = mapKey(uriInfo.getTargetKeyPredicates());
final Object targetData = dataSource.readRelatedData(entitySet, sourceData, targetEntitySet, keys);
if (!appliesFilter(targetData, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
dataSource.deleteRelation(entitySet, sourceData, targetEntitySet, keys);
final Map<String, Object> newKeys = parseLink(targetEntitySet, content, requestContentType);
dataSource.writeRelation(entitySet, sourceData, targetEntitySet, newKeys);
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse readEntityComplexProperty(final GetComplexPropertyUriInfo uriInfo, final String contentType)
throws ODataException {
Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
// if (!appliesFilter(data, uriInfo.getFilter()))
if (data == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final List<EdmProperty> propertyPath = uriInfo.getPropertyPath();
final EdmProperty property = propertyPath.get(propertyPath.size() - 1);
final Object value = property.isSimple() ?
property.getMapping() == null || property.getMapping().getMediaResourceMimeTypeKey() == null ?
getPropertyValue(data, propertyPath) : getSimpleTypeValueMap(data, propertyPath) :
getStructuralTypeValueMap(getPropertyValue(data, propertyPath), (EdmStructuralType) property.getType());
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "writeProperty");
final ODataResponse response = EntityProvider.writeProperty(contentType, property, value);
context.stopRuntimeMeasurement(timingHandle);
return ODataResponse.fromResponse(response).eTag(constructETag(uriInfo.getTargetEntitySet(), data)).build();
}
@Override
public ODataResponse readEntitySimpleProperty(final GetSimplePropertyUriInfo uriInfo, final String contentType)
throws ODataException {
return readEntityComplexProperty((GetComplexPropertyUriInfo) uriInfo, contentType);
}
@Override
public ODataResponse readEntitySimplePropertyValue(final GetSimplePropertyUriInfo uriInfo, final String contentType)
throws ODataException {
Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
// if (!appliesFilter(data, uriInfo.getFilter()))
if (data == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final List<EdmProperty> propertyPath = uriInfo.getPropertyPath();
final EdmProperty property = propertyPath.get(propertyPath.size() - 1);
final Object value = property.getMapping() == null || property.getMapping().getMediaResourceMimeTypeKey() == null ?
getPropertyValue(data, propertyPath) : getSimpleTypeValueMap(data, propertyPath);
return ODataResponse.fromResponse(EntityProvider.writePropertyValue(property, value)).eTag(
constructETag(uriInfo.getTargetEntitySet(), data)).build();
}
@Override
public ODataResponse deleteEntitySimplePropertyValue(final DeleteUriInfo uriInfo, final String contentType)
throws ODataException {
Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (data == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final List<EdmProperty> propertyPath = uriInfo.getPropertyPath();
final EdmProperty property = propertyPath.get(propertyPath.size() - 1);
data = getPropertyValue(data, propertyPath.subList(0, propertyPath.size() - 1));
valueAccess.setPropertyValue(data, property, null);
valueAccess.setMappingValue(data, property.getMapping(), null);
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse updateEntityComplexProperty(final PutMergePatchUriInfo uriInfo, final InputStream content,
final String requestContentType, final boolean merge, final String contentType) throws ODataException {
Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (!appliesFilter(data, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final List<EdmProperty> propertyPath = uriInfo.getPropertyPath();
final EdmProperty property = propertyPath.get(propertyPath.size() - 1);
data = getPropertyValue(data, propertyPath.subList(0, propertyPath.size() - 1));
ODataContext context = getContext();
int timingHandle = context.startRuntimeMeasurement("EntityConsumer", "readProperty");
Map<String, Object> values;
try {
values =
EntityProvider.readProperty(requestContentType, property, content, EntityProviderReadProperties.init()
.mergeSemantic(merge).build());
} catch (final EntityProviderException e) {
throw new ODataBadRequestException(ODataBadRequestException.BODY, e);
}
context.stopRuntimeMeasurement(timingHandle);
final Object value = values.get(property.getName());
if (property.isSimple()) {
valueAccess.setPropertyValue(data, property, value);
} else {
@SuppressWarnings("unchecked")
final Map<String, Object> propertyValue = (Map<String, Object>) value;
setStructuralTypeValuesFromMap(valueAccess.getPropertyValue(data, property),
(EdmStructuralType) property.getType(), propertyValue, merge);
}
return ODataResponse.newBuilder().eTag(constructETag(uriInfo.getTargetEntitySet(), data)).build();
}
@Override
public ODataResponse updateEntitySimpleProperty(final PutMergePatchUriInfo uriInfo, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
return updateEntityComplexProperty(uriInfo, content, requestContentType, false, contentType);
}
@Override
public ODataResponse updateEntitySimplePropertyValue(final PutMergePatchUriInfo uriInfo, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (!appliesFilter(data, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final List<EdmProperty> propertyPath = uriInfo.getPropertyPath();
final EdmProperty property = propertyPath.get(propertyPath.size() - 1);
data = getPropertyValue(data, propertyPath.subList(0, propertyPath.size() - 1));
ODataContext context = getContext();
int timingHandle = context.startRuntimeMeasurement("EntityConsumer", "readPropertyValue");
Object value;
try {
value = EntityProvider.readPropertyValue(property, content);
} catch (final EntityProviderException e) {
throw new ODataBadRequestException(ODataBadRequestException.BODY, e);
}
context.stopRuntimeMeasurement(timingHandle);
valueAccess.setPropertyValue(data, property, value);
valueAccess.setMappingValue(data, property.getMapping(), requestContentType);
return ODataResponse.newBuilder().eTag(constructETag(uriInfo.getTargetEntitySet(), data)).build();
}
@Override
public ODataResponse readEntityMedia(final GetMediaResourceUriInfo uriInfo, final String contentType)
throws ODataException {
final Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (!appliesFilter(data, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
final BinaryData binaryData = dataSource.readBinaryData(entitySet, data);
if (binaryData == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
final String mimeType = binaryData.getMimeType() == null ?
HttpContentType.APPLICATION_OCTET_STREAM : binaryData.getMimeType();
return ODataResponse.fromResponse(EntityProvider.writeBinary(mimeType, binaryData.getData())).eTag(
constructETag(entitySet, data)).build();
}
@Override
public ODataResponse deleteEntityMedia(final DeleteUriInfo uriInfo, final String contentType) throws ODataException {
final Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (data == null) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
dataSource.writeBinaryData(uriInfo.getTargetEntitySet(), data, new BinaryData(null, null));
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse updateEntityMedia(final PutMergePatchUriInfo uriInfo, final InputStream content,
final String requestContentType, final String contentType) throws ODataException {
final Object data = retrieveData(
uriInfo.getStartEntitySet(),
uriInfo.getKeyPredicates(),
uriInfo.getFunctionImport(),
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
uriInfo.getNavigationSegments());
if (!appliesFilter(data, uriInfo.getFilter())) {
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "readBinary");
final byte[] value = EntityProvider.readBinary(content);
context.stopRuntimeMeasurement(timingHandle);
final EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
dataSource.writeBinaryData(entitySet, data, new BinaryData(value, requestContentType));
return ODataResponse.newBuilder().eTag(constructETag(entitySet, data)).build();
}
@Override
public ODataResponse executeFunctionImport(final GetFunctionImportUriInfo uriInfo, final String contentType)
throws ODataException {
final EdmFunctionImport functionImport = uriInfo.getFunctionImport();
final EdmType type = functionImport.getReturnType().getType();
final Object data = dataSource.readData(
functionImport,
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
null);
if (data == null) {
throw new ODataNotFoundException(ODataHttpException.COMMON);
}
Object value;
if (type.getKind() == EdmTypeKind.SIMPLE) {
value = type == EdmSimpleTypeKind.Binary.getEdmSimpleTypeInstance() ?
((BinaryData) data).getData() : data;
} else if (functionImport.getReturnType().getMultiplicity() == EdmMultiplicity.MANY) {
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
for (final Object typeData : (List<?>) data) {
values.add(getStructuralTypeValueMap(typeData, (EdmStructuralType) type));
}
value = values;
} else {
value = getStructuralTypeValueMap(data, (EdmStructuralType) type);
}
ODataContext context = getContext();
final EntityProviderWriteProperties entryProperties = EntityProviderWriteProperties
.serviceRoot(context.getPathInfo().getServiceRoot()).build();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "writeFunctionImport");
final ODataResponse response =
EntityProvider.writeFunctionImport(contentType, functionImport, value, entryProperties);
context.stopRuntimeMeasurement(timingHandle);
return ODataResponse.fromResponse(response).build();
}
@Override
public ODataResponse executeFunctionImportValue(final GetFunctionImportUriInfo uriInfo, final String contentType)
throws ODataException {
final EdmFunctionImport functionImport = uriInfo.getFunctionImport();
final EdmSimpleType type = (EdmSimpleType) functionImport.getReturnType().getType();
final Object data = dataSource.readData(
functionImport,
mapFunctionParameters(uriInfo.getFunctionImportParameters()),
null);
if (data == null) {
throw new ODataNotFoundException(ODataHttpException.COMMON);
}
ODataResponse response;
if (type == EdmSimpleTypeKind.Binary.getEdmSimpleTypeInstance()) {
response = EntityProvider.writeBinary(((BinaryData) data).getMimeType(), ((BinaryData) data).getData());
} else {
final String value = type.valueToString(data, EdmLiteralKind.DEFAULT, null);
response = EntityProvider.writeText(value == null ? "" : value);
}
return ODataResponse.fromResponse(response).build();
}
private static Map<String, Object> mapKey(final List<KeyPredicate> keys) throws EdmException {
Map<String, Object> keyMap = new HashMap<String, Object>();
for (final KeyPredicate key : keys) {
final EdmProperty property = key.getProperty();
final EdmSimpleType type = (EdmSimpleType) property.getType();
keyMap.put(property.getName(), type.valueOfString(key.getLiteral(), EdmLiteralKind.DEFAULT, property.getFacets(),
type.getDefaultType()));
}
return keyMap;
}
private static Map<String, Object> mapFunctionParameters(final Map<String, EdmLiteral> functionImportParameters)
throws EdmSimpleTypeException {
if (functionImportParameters == null) {
return Collections.emptyMap();
} else {
Map<String, Object> parameterMap = new HashMap<String, Object>();
for (final String parameterName : functionImportParameters.keySet()) {
final EdmLiteral literal = functionImportParameters.get(parameterName);
final EdmSimpleType type = literal.getType();
parameterMap.put(parameterName, type.valueOfString(literal.getLiteral(), EdmLiteralKind.DEFAULT, null, type
.getDefaultType()));
}
return parameterMap;
}
}
private Object retrieveData(final EdmEntitySet startEntitySet, final List<KeyPredicate> keyPredicates,
final EdmFunctionImport functionImport, final Map<String, Object> functionImportParameters,
final List<NavigationSegment> navigationSegments) throws ODataException {
Object data;
final Map<String, Object> keys = mapKey(keyPredicates);
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement(getClass().getSimpleName(), "retrieveData");
try {
data = functionImport == null ?
keys.isEmpty() ?
dataSource.readData(startEntitySet) : dataSource.readData(startEntitySet, keys) :
dataSource.readData(functionImport, functionImportParameters, keys);
EdmEntitySet currentEntitySet =
functionImport == null ? startEntitySet : functionImport.getEntitySet();
for (NavigationSegment navigationSegment : navigationSegments) {
data = dataSource.readRelatedData(
currentEntitySet,
data,
navigationSegment.getEntitySet(),
mapKey(navigationSegment.getKeyPredicates()));
currentEntitySet = navigationSegment.getEntitySet();
}
} finally {
context.stopRuntimeMeasurement(timingHandle);
}
return data;
}
private <T> String constructETag(final EdmEntitySet entitySet, final T data) throws ODataException {
final EdmEntityType entityType = entitySet.getEntityType();
String eTag = null;
for (final String propertyName : entityType.getPropertyNames()) {
final EdmProperty property = (EdmProperty) entityType.getProperty(propertyName);
if (property.getFacets() != null && property.getFacets().getConcurrencyMode() == EdmConcurrencyMode.Fixed) {
final EdmSimpleType type = (EdmSimpleType) property.getType();
final String component = type.valueToString(valueAccess.getPropertyValue(data, property),
EdmLiteralKind.DEFAULT, property.getFacets());
eTag = eTag == null ? component : eTag + Edm.DELIMITER + component;
}
}
return eTag == null ? null : "W/\"" + eTag + "\"";
}
private <T> Map<String, ODataCallback> getCallbacks(final T data, final EdmEntityType entityType)
throws EdmException {
final List<String> navigationPropertyNames = entityType.getNavigationPropertyNames();
if (navigationPropertyNames.isEmpty()) {
return null;
} else {
final WriteCallback callback = new WriteCallback(data);
Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
for (final String name : navigationPropertyNames) {
callbacks.put(name, callback);
}
return callbacks;
}
}
private class WriteCallback implements OnWriteEntryContent, OnWriteFeedContent {
private final Object data;
private <T> WriteCallback(final T data) {
this.data = data;
}
@Override
public WriteFeedCallbackResult retrieveFeedResult(final WriteFeedCallbackContext context)
throws ODataApplicationException {
try {
final EdmEntityType entityType =
context.getSourceEntitySet().getRelatedEntitySet(context.getNavigationProperty()).getEntityType();
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
Object relatedData = null;
try {
relatedData = readRelatedData(context);
for (final Object entryData : (List<?>) relatedData) {
values.add(getStructuralTypeValueMap(entryData, entityType));
}
} catch (final ODataNotFoundException e) {
values.clear();
}
WriteFeedCallbackResult result = new WriteFeedCallbackResult();
result.setFeedData(values);
EntityProviderWriteProperties inlineProperties =
EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).callbacks(
getCallbacks(relatedData, entityType)).expandSelectTree(context.getCurrentExpandSelectTreeNode())
.selfLink(context.getSelfLink()).build();
result.setInlineProperties(inlineProperties);
return result;
} catch (final ODataException e) {
throw new ODataApplicationException(e.getLocalizedMessage(), Locale.ROOT, e);
}
}
@Override
public WriteEntryCallbackResult retrieveEntryResult(final WriteEntryCallbackContext context)
throws ODataApplicationException {
try {
final EdmEntityType entityType =
context.getSourceEntitySet().getRelatedEntitySet(context.getNavigationProperty()).getEntityType();
WriteEntryCallbackResult result = new WriteEntryCallbackResult();
Object relatedData;
try {
relatedData = readRelatedData(context);
} catch (final ODataNotFoundException e) {
relatedData = null;
}
if (relatedData == null) {
result.setEntryData(Collections.<String, Object> emptyMap());
} else {
result.setEntryData(getStructuralTypeValueMap(relatedData, entityType));
EntityProviderWriteProperties inlineProperties =
EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).callbacks(
getCallbacks(relatedData, entityType)).expandSelectTree(context.getCurrentExpandSelectTreeNode())
.build();
result.setInlineProperties(inlineProperties);
}
return result;
} catch (final ODataException e) {
throw new ODataApplicationException(e.getLocalizedMessage(), Locale.ROOT, e);
}
}
private Object readRelatedData(final WriteCallbackContext context) throws ODataException {
final EdmEntitySet entitySet = context.getSourceEntitySet();
return dataSource.readRelatedData(
entitySet,
data instanceof List ? readEntryData((List<?>) data, entitySet.getEntityType(), context
.extractKeyFromEntryData()) : data,
entitySet.getRelatedEntitySet(context.getNavigationProperty()),
Collections.<String, Object> emptyMap());
}
private <T> T readEntryData(final List<T> data, final EdmEntityType entityType, final Map<String, Object> key)
throws ODataException {
for (final T entryData : data) {
boolean found = true;
for (final EdmProperty keyProperty : entityType.getKeyProperties()) {
if (!valueAccess.getPropertyValue(entryData, keyProperty).equals(key.get(keyProperty.getName()))) {
found = false;
break;
}
}
if (found) {
return entryData;
}
}
return null;
}
}
private <T> ODataResponse writeEntry(final EdmEntitySet entitySet, final ExpandSelectTreeNode expandSelectTree,
final T data, final String contentType) throws ODataException, EntityProviderException {
final EdmEntityType entityType = entitySet.getEntityType();
final Map<String, Object> values = getStructuralTypeValueMap(data, entityType);
ODataContext context = getContext();
EntityProviderWriteProperties writeProperties = EntityProviderWriteProperties
.serviceRoot(context.getPathInfo().getServiceRoot())
.expandSelectTree(expandSelectTree)
.callbacks(getCallbacks(data, entityType))
.build();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "writeEntry");
final ODataResponse response = EntityProvider.writeEntry(contentType, entitySet, values, writeProperties);
context.stopRuntimeMeasurement(timingHandle);
return response;
}
private ODataEntry parseEntry(final EdmEntitySet entitySet, final InputStream content,
final String requestContentType, final EntityProviderReadProperties properties) throws ODataBadRequestException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement("EntityConsumer", "readEntry");
ODataEntry entryValues;
try {
entryValues = EntityProvider.readEntry(requestContentType, entitySet, content, properties);
} catch (final EntityProviderException e) {
throw new ODataBadRequestException(ODataBadRequestException.BODY, e);
}
context.stopRuntimeMeasurement(timingHandle);
return entryValues;
}
private Map<String, Object> parseLink(final EdmEntitySet entitySet, final InputStream content,
final String contentType) throws ODataException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement("EntityProvider", "readLink");
final String uriString = EntityProvider.readLink(contentType, entitySet, content);
context.stopRuntimeMeasurement(timingHandle);
final Map<String, Object> targetKeys = parseLinkUri(entitySet, uriString);
if (targetKeys == null) {
throw new ODataBadRequestException(ODataBadRequestException.BODY);
}
return targetKeys;
}
private Map<String, Object> parseLinkUri(final EdmEntitySet targetEntitySet, final String uriString)
throws EdmException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement("UriParser", "getKeyPredicatesFromEntityLink");
List<KeyPredicate> key = null;
try {
key = UriParser.getKeyPredicatesFromEntityLink(targetEntitySet, uriString,
context.getPathInfo().getServiceRoot());
} catch (ODataException e) {
// We don't understand the link target. This could also be seen as an error.
}
context.stopRuntimeMeasurement(timingHandle);
return key == null ? null : mapKey(key);
}
private <T> void createInlinedEntities(final EdmEntitySet entitySet, final T data, final ODataEntry entryValues)
throws ODataException {
final EdmEntityType entityType = entitySet.getEntityType();
for (final String navigationPropertyName : entityType.getNavigationPropertyNames()) {
final EdmNavigationProperty navigationProperty =
(EdmNavigationProperty) entityType.getProperty(navigationPropertyName);
final EdmEntitySet relatedEntitySet = entitySet.getRelatedEntitySet(navigationProperty);
final EdmEntityType relatedEntityType = relatedEntitySet.getEntityType();
final Object relatedValue = entryValues.getProperties().get(navigationPropertyName);
if (relatedValue == null) {
for (final String uriString : entryValues.getMetadata().getAssociationUris(navigationPropertyName)) {
final Map<String, Object> key = parseLinkUri(relatedEntitySet, uriString);
if (key != null) {
dataSource.writeRelation(entitySet, data, relatedEntitySet, key);
}
}
} else {
if (relatedValue instanceof ODataFeed) {
ODataFeed feed = (ODataFeed) relatedValue;
final List<ODataEntry> relatedValueList = feed.getEntries();
for (final ODataEntry relatedValues : relatedValueList) {
Object relatedData = dataSource.newDataObject(relatedEntitySet);
setStructuralTypeValuesFromMap(relatedData, relatedEntityType, relatedValues.getProperties(), false);
dataSource.createData(relatedEntitySet, relatedData);
dataSource.writeRelation(entitySet, data, relatedEntitySet, getStructuralTypeValueMap(relatedData,
relatedEntityType));
createInlinedEntities(relatedEntitySet, relatedData, relatedValues);
}
} else if (relatedValue instanceof ODataEntry) {
final ODataEntry relatedValueEntry = (ODataEntry) relatedValue;
Object relatedData = dataSource.newDataObject(relatedEntitySet);
setStructuralTypeValuesFromMap(relatedData, relatedEntityType, relatedValueEntry.getProperties(), false);
dataSource.createData(relatedEntitySet, relatedData);
dataSource.writeRelation(entitySet, data, relatedEntitySet, getStructuralTypeValueMap(relatedData,
relatedEntityType));
createInlinedEntities(relatedEntitySet, relatedData, relatedValueEntry);
} else {
throw new ODataException("Unexpected class for a related value: " + relatedValue.getClass().getSimpleName());
}
}
}
}
private <T> Integer applySystemQueryOptions(final EdmEntitySet entitySet, final List<T> data,
final FilterExpression filter, final InlineCount inlineCount, final OrderByExpression orderBy,
final String skipToken, final Integer skip, final Integer top) throws ODataException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement(getClass().getSimpleName(), "applySystemQueryOptions");
if (filter != null) {
// Remove all elements the filter does not apply for.
// A for-each loop would not work with "remove", see Java documentation.
for (Iterator<T> iterator = data.iterator(); iterator.hasNext();) {
if (!appliesFilter(iterator.next(), filter)) {
iterator.remove();
}
}
}
final Integer count = inlineCount == InlineCount.ALLPAGES ? data.size() : null;
if (orderBy != null) {
sort(data, orderBy);
} else if (skipToken != null || skip != null || top != null) {
sortInDefaultOrder(entitySet, data);
}
if (skipToken != null) {
while (!data.isEmpty() && !getSkipToken(entitySet, data.get(0)).equals(skipToken)) {
data.remove(0);
}
}
if (skip != null) {
if (skip >= data.size()) {
data.clear();
} else {
for (int i = 0; i < skip; i++) {
data.remove(0);
}
}
}
if (top != null) {
while (data.size() > top) {
data.remove(top.intValue());
}
}
context.stopRuntimeMeasurement(timingHandle);
return count;
}
private <T> void sort(final List<T> data, final OrderByExpression orderBy) {
Collections.sort(data, new Comparator<T>() {
@Override
public int compare(final T entity1, final T entity2) {
try {
int result = 0;
for (final OrderExpression expression : orderBy.getOrders()) {
String first = evaluateExpression(entity1, expression.getExpression());
String second = evaluateExpression(entity2, expression.getExpression());
if (first != null && second != null) {
result = first.compareTo(second);
} else if (first == null && second != null) {
result = 1;
} else if (first != null && second == null) {
result = -1;
}
if (expression.getSortOrder() == SortOrder.desc) {
result = -result;
}
if (result != 0) {
break;
}
}
return result;
} catch (final ODataException e) {
return 0;
}
}
});
}
private <T> void sortInDefaultOrder(final EdmEntitySet entitySet, final List<T> data) {
Collections.sort(data, new Comparator<T>() {
@Override
public int compare(final T entity1, final T entity2) {
try {
return getSkipToken(entitySet, entity1).compareTo(getSkipToken(entitySet, entity2));
} catch (final ODataException e) {
return 0;
}
}
});
}
private <T> boolean appliesFilter(final T data, final FilterExpression filter) throws ODataException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement(getClass().getSimpleName(), "appliesFilter");
try {
return data != null && (filter == null || evaluateExpression(data, filter.getExpression()).equals("true"));
} catch (final RuntimeException e) {
return false;
} finally {
context.stopRuntimeMeasurement(timingHandle);
}
}
private <T> String evaluateExpression(final T data, final CommonExpression expression) throws ODataException {
switch (expression.getKind()) {
case UNARY:
final UnaryExpression unaryExpression = (UnaryExpression) expression;
final String operand = evaluateExpression(data, unaryExpression.getOperand());
switch (unaryExpression.getOperator()) {
case NOT:
return Boolean.toString(!Boolean.parseBoolean(operand));
case MINUS:
return operand.startsWith("-") ? operand.substring(1) : "-" + operand;
default:
throw new ODataNotImplementedException();
}
case BINARY:
final BinaryExpression binaryExpression = (BinaryExpression) expression;
final EdmSimpleType type = (EdmSimpleType) binaryExpression.getLeftOperand().getEdmType();
final String left = evaluateExpression(data, binaryExpression.getLeftOperand());
final String right = evaluateExpression(data, binaryExpression.getRightOperand());
switch (binaryExpression.getOperator()) {
case ADD:
if (binaryExpression.getEdmType() == EdmSimpleTypeKind.Decimal.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Double.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Single.getEdmSimpleTypeInstance()) {
return Double.toString(Double.valueOf(left) + Double.valueOf(right));
} else {
return Long.toString(Long.valueOf(left) + Long.valueOf(right));
}
case SUB:
if (binaryExpression.getEdmType() == EdmSimpleTypeKind.Decimal.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Double.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Single.getEdmSimpleTypeInstance()) {
return Double.toString(Double.valueOf(left) - Double.valueOf(right));
} else {
return Long.toString(Long.valueOf(left) - Long.valueOf(right));
}
case MUL:
if (binaryExpression.getEdmType() == EdmSimpleTypeKind.Decimal.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Double.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Single.getEdmSimpleTypeInstance()) {
return Double.toString(Double.valueOf(left) * Double.valueOf(right));
} else {
return Long.toString(Long.valueOf(left) * Long.valueOf(right));
}
case DIV:
final String number = Double.toString(Double.valueOf(left) / Double.valueOf(right));
return number.endsWith(".0") ? number.replace(".0", "") : number;
case MODULO:
if (binaryExpression.getEdmType() == EdmSimpleTypeKind.Decimal.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Double.getEdmSimpleTypeInstance()
|| binaryExpression.getEdmType() == EdmSimpleTypeKind.Single.getEdmSimpleTypeInstance()) {
return Double.toString(Double.valueOf(left) % Double.valueOf(right));
} else {
return Long.toString(Long.valueOf(left) % Long.valueOf(right));
}
case AND:
return Boolean.toString(left.equals("true") && right.equals("true"));
case OR:
return Boolean.toString(left.equals("true") || right.equals("true"));
case EQ:
return Boolean.toString(left.equals(right));
case NE:
return Boolean.toString(!left.equals(right));
case LT:
if (type == EdmSimpleTypeKind.String.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Guid.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Time.getEdmSimpleTypeInstance()) {
return Boolean.toString(left.compareTo(right) < 0);
} else {
return Boolean.toString(Double.valueOf(left) < Double.valueOf(right));
}
case LE:
if (type == EdmSimpleTypeKind.String.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Guid.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Time.getEdmSimpleTypeInstance()) {
return Boolean.toString(left.compareTo(right) <= 0);
} else {
return Boolean.toString(Double.valueOf(left) <= Double.valueOf(right));
}
case GT:
if (type == EdmSimpleTypeKind.String.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Guid.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Time.getEdmSimpleTypeInstance()) {
return Boolean.toString(left.compareTo(right) > 0);
} else {
return Boolean.toString(Double.valueOf(left) > Double.valueOf(right));
}
case GE:
if (type == EdmSimpleTypeKind.String.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Guid.getEdmSimpleTypeInstance()
|| type == EdmSimpleTypeKind.Time.getEdmSimpleTypeInstance()) {
return Boolean.toString(left.compareTo(right) >= 0);
} else {
return Boolean.toString(Double.valueOf(left) >= Double.valueOf(right));
}
case PROPERTY_ACCESS:
throw new ODataNotImplementedException();
default:
throw new ODataNotImplementedException();
}
case PROPERTY:
final EdmProperty property = (EdmProperty) ((PropertyExpression) expression).getEdmProperty();
final EdmSimpleType propertyType = (EdmSimpleType) property.getType();
return propertyType.valueToString(valueAccess.getPropertyValue(data, property), EdmLiteralKind.DEFAULT,
property.getFacets());
case MEMBER:
final MemberExpression memberExpression = (MemberExpression) expression;
final PropertyExpression propertyExpression = (PropertyExpression) memberExpression.getProperty();
final EdmProperty memberProperty = (EdmProperty) propertyExpression.getEdmProperty();
final EdmSimpleType memberType = (EdmSimpleType) memberExpression.getEdmType();
List<EdmProperty> propertyPath = new ArrayList<EdmProperty>();
CommonExpression currentExpression = memberExpression;
while (currentExpression != null) {
final PropertyExpression currentPropertyExpression =
(PropertyExpression) (currentExpression.getKind() == ExpressionKind.MEMBER ?
((MemberExpression) currentExpression).getProperty() : currentExpression);
final EdmTyped currentProperty = currentPropertyExpression.getEdmProperty();
final EdmTypeKind kind = currentProperty.getType().getKind();
if (kind == EdmTypeKind.SIMPLE || kind == EdmTypeKind.COMPLEX) {
propertyPath.add(0, (EdmProperty) currentProperty);
} else {
throw new ODataNotImplementedException();
}
currentExpression =
currentExpression.getKind() == ExpressionKind.MEMBER ? ((MemberExpression) currentExpression).getPath()
: null;
}
return memberType.valueToString(getPropertyValue(data, propertyPath), EdmLiteralKind.DEFAULT, memberProperty
.getFacets());
case LITERAL:
final LiteralExpression literal = (LiteralExpression) expression;
final EdmSimpleType literalType = (EdmSimpleType) literal.getEdmType();
return literalType.valueToString(literalType.valueOfString(literal.getUriLiteral(), EdmLiteralKind.URI, null,
literalType.getDefaultType()),
EdmLiteralKind.DEFAULT, null);
case METHOD:
final MethodExpression methodExpression = (MethodExpression) expression;
final String first = evaluateExpression(data, methodExpression.getParameters().get(0));
final String second = methodExpression.getParameterCount() > 1 ?
evaluateExpression(data, methodExpression.getParameters().get(1)) : "";
final String third = methodExpression.getParameterCount() > 2 ?
evaluateExpression(data, methodExpression.getParameters().get(2)) : "";
switch (methodExpression.getMethod()) {
case ENDSWITH:
return Boolean.toString(first.endsWith(second));
case INDEXOF:
return Integer.toString(first.indexOf(second));
case STARTSWITH:
return Boolean.toString(first.startsWith(second));
case TOLOWER:
return first.toLowerCase(Locale.ROOT);
case TOUPPER:
return first.toUpperCase(Locale.ROOT);
case TRIM:
return first.trim();
case SUBSTRING:
final int offset = second.length() == 0 ? 0 : Integer.parseInt(second);
final int length = third.length() == 0 ? 0 : Integer.parseInt(second);
return first.substring(offset, offset + length);
case SUBSTRINGOF:
return Boolean.toString(second.contains(first));
case CONCAT:
return first + second;
case LENGTH:
return Integer.toString(first.length());
case YEAR:
return String.valueOf(Integer.parseInt(first.substring(0, 4)));
case MONTH:
return String.valueOf(Integer.parseInt(first.substring(5, 7)));
case DAY:
return String.valueOf(Integer.parseInt(first.substring(8, 10)));
case HOUR:
return String.valueOf(Integer.parseInt(first.substring(11, 13)));
case MINUTE:
return String.valueOf(Integer.parseInt(first.substring(14, 16)));
case SECOND:
return String.valueOf(Integer.parseInt(first.substring(17, 19)));
case ROUND:
return Long.toString(Math.round(Double.valueOf(first)));
case FLOOR:
return Long.toString(Math.round(Math.floor(Double.valueOf(first))));
case CEILING:
return Long.toString(Math.round(Math.ceil(Double.valueOf(first))));
default:
throw new ODataNotImplementedException();
}
default:
throw new ODataNotImplementedException();
}
}
private <T> String getSkipToken(final EdmEntitySet entitySet, final T data) throws ODataException {
String skipToken = "";
for (final EdmProperty property : entitySet.getEntityType().getKeyProperties()) {
final EdmSimpleType type = (EdmSimpleType) property.getType();
skipToken = skipToken.concat(type.valueToString(valueAccess.getPropertyValue(data, property),
EdmLiteralKind.DEFAULT, property.getFacets()));
}
return skipToken;
}
private <T> Object getPropertyValue(final T data, final List<EdmProperty> propertyPath) throws ODataException {
Object dataObject = data;
for (final EdmProperty property : propertyPath) {
if (dataObject != null) {
dataObject = valueAccess.getPropertyValue(dataObject, property);
}
}
return dataObject;
}
private void handleMimeType(final Object data, final EdmMapping mapping, final Map<String, Object> valueMap)
throws ODataException {
final String mimeTypeName = mapping.getMediaResourceMimeTypeKey();
if (mimeTypeName != null) {
Object value = valueAccess.getMappingValue(data, mapping);
valueMap.put(mimeTypeName, value);
}
}
private <T> Map<String, Object> getSimpleTypeValueMap(final T data, final List<EdmProperty> propertyPath)
throws ODataException {
final EdmProperty property = propertyPath.get(propertyPath.size() - 1);
Map<String, Object> valueWithMimeType = new HashMap<String, Object>();
valueWithMimeType.put(property.getName(), getPropertyValue(data, propertyPath));
handleMimeType(data, property.getMapping(), valueWithMimeType);
return valueWithMimeType;
}
private <T> Map<String, Object> getStructuralTypeValueMap(final T data, final EdmStructuralType type)
throws ODataException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement(getClass().getSimpleName(), "getStructuralTypeValueMap");
Map<String, Object> valueMap = new HashMap<String, Object>();
EdmMapping mapping = type.getMapping();
if (mapping != null) {
handleMimeType(data, mapping, valueMap);
}
for (final String propertyName : type.getPropertyNames()) {
final EdmProperty property = (EdmProperty) type.getProperty(propertyName);
final Object value = valueAccess.getPropertyValue(data, property);
if (property.isSimple()) {
if (property.getMapping() == null || property.getMapping().getMediaResourceMimeTypeKey() == null) {
valueMap.put(propertyName, value);
} else {
// TODO: enable MIME type mapping outside the current subtree
valueMap.put(propertyName, getSimpleTypeValueMap(data, Arrays.asList(property)));
}
} else {
valueMap.put(propertyName, getStructuralTypeValueMap(value, (EdmStructuralType) property.getType()));
}
}
context.stopRuntimeMeasurement(timingHandle);
return valueMap;
}
private <T> Map<String, Object> getStructuralTypeTypeMap(final T data, final EdmStructuralType type)
throws ODataException {
ODataContext context = getContext();
final int timingHandle = context.startRuntimeMeasurement(getClass().getSimpleName(), "getStructuralTypeTypeMap");
Map<String, Object> typeMap = new HashMap<String, Object>();
for (final String propertyName : type.getPropertyNames()) {
final EdmProperty property = (EdmProperty) type.getProperty(propertyName);
if (property.isSimple()) {
Object value = valueAccess.getPropertyType(data, property);
if (value != null) {
typeMap.put(propertyName, value);
}
} else {
Object value = valueAccess.getPropertyValue(data, property);
if (value == null) {
Class<?> complexClass = valueAccess.getPropertyType(data, property);
value = createInstance(complexClass);
}
typeMap.put(propertyName, getStructuralTypeTypeMap(value,
(EdmStructuralType) property.getType()));
}
}
context.stopRuntimeMeasurement(timingHandle);
return typeMap;
}
private <T> void setStructuralTypeValuesFromMap(final T data, final EdmStructuralType type,
final Map<String, Object> valueMap, final boolean merge) throws ODataException {
if (data == null) {
throw new ODataException("Unable to set structural type values to NULL data.");
}
ODataContext context = getContext();
final int timingHandle =
context.startRuntimeMeasurement(getClass().getSimpleName(), "setStructuralTypeValuesFromMap");
for (final String propertyName : type.getPropertyNames()) {
final EdmProperty property = (EdmProperty) type.getProperty(propertyName);
if (type instanceof EdmEntityType && ((EdmEntityType) type).getKeyProperties().contains(property)) {
Object v = valueAccess.getPropertyValue(data, property);
if (v != null) {
continue;
}
}
if (!merge || valueMap != null && valueMap.containsKey(propertyName)) {
final Object value = valueMap == null ? null : valueMap.get(propertyName);
if (property.isSimple()) {
valueAccess.setPropertyValue(data, property, value);
} else {
@SuppressWarnings("unchecked")
final Map<String, Object> values = (Map<String, Object>) value;
Object complexData = valueAccess.getPropertyValue(data, property);
if (complexData == null) {
Class<?> complexClass = valueAccess.getPropertyType(data, property);
complexData = createInstance(complexClass);
valueAccess.setPropertyValue(data, property, complexData);
}
setStructuralTypeValuesFromMap(complexData,
(EdmStructuralType) property.getType(), values, merge);
}
}
}
context.stopRuntimeMeasurement(timingHandle);
}
private Object createInstance(final Class<?> complexClass) throws ODataException {
try {
return complexClass.newInstance();
} catch (InstantiationException e) {
throw new ODataException("Unable to create instance for complex data class '"
+ complexClass + "'.", e);
} catch (IllegalAccessException e) {
throw new ODataException("Unable to create instance for complex data class '"
+ complexClass + "'.", e);
}
}
@Override
public ODataResponse executeBatch(final BatchHandler handler, final String contentType, final InputStream content)
throws ODataException {
ODataResponse batchResponse;
List<BatchResponsePart> batchResponseParts = new ArrayList<BatchResponsePart>();
PathInfo pathInfo = getContext().getPathInfo();
EntityProviderBatchProperties batchProperties = EntityProviderBatchProperties.init().pathInfo(pathInfo).build();
List<BatchRequestPart> batchParts = EntityProvider.parseBatchRequest(contentType, content, batchProperties);
for (BatchRequestPart batchPart : batchParts) {
batchResponseParts.add(handler.handleBatchPart(batchPart));
}
batchResponse = EntityProvider.writeBatchResponse(batchResponseParts);
return batchResponse;
}
@Override
public BatchResponsePart executeChangeSet(final BatchHandler handler, final List<ODataRequest> requests)
throws ODataException {
List<ODataResponse> responses = new ArrayList<ODataResponse>();
for (ODataRequest request : requests) {
ODataResponse response = handler.handleRequest(request);
if (response.getStatus().getStatusCode() >= HttpStatusCodes.BAD_REQUEST.getStatusCode()) {
// Rollback
List<ODataResponse> errorResponses = new ArrayList<ODataResponse>(1);
errorResponses.add(response);
return BatchResponsePart.responses(errorResponses).changeSet(false).build();
}
responses.add(response);
}
return BatchResponsePart.responses(responses).changeSet(true).build();
}
}