com.sap.cds.adapter.odata.v4.utils.ODataUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-adapter-odata-v4 Show documentation
Show all versions of cds-adapter-odata-v4 Show documentation
OData V4 adapter for CDS Services Java
/**************************************************************************
* (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.adapter.odata.v4.utils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.olingo.commons.api.edm.EdmAction;
import org.apache.olingo.commons.api.edm.EdmFunction;
import org.apache.olingo.commons.api.edm.EdmOperation;
import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
import org.apache.olingo.commons.api.ex.ODataErrorDetail;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataRequest;
import org.apache.olingo.server.api.ODataResponse;
import org.apache.olingo.server.api.ODataServerError;
import org.apache.olingo.server.api.serializer.EdmAssistedSerializer;
import org.apache.olingo.server.api.serializer.ODataSerializer;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceAction;
import org.apache.olingo.server.api.uri.UriResourceFunction;
import com.google.common.annotations.VisibleForTesting;
import com.sap.cds.adapter.odata.v4.CdsRequestGlobals;
import com.sap.cds.adapter.odata.v4.processors.response.CdsODataResponse;
import com.sap.cds.reflect.CdsBoundAction;
import com.sap.cds.reflect.CdsBoundFunction;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsOperation;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.application.ApplicationLifecycleService;
import com.sap.cds.services.application.ErrorResponseEventContext.ErrorResponse;
import com.sap.cds.services.messages.Message;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cds.services.utils.StringUtils;
import com.sap.cds.services.utils.model.CdsAnnotations;
public class ODataUtils {
private static final String DEFAULT_BINDING_PARAMETER = "in";
private static final String BINDING_PARAMETER_ANNOTATION = "cds.odata.bindingparameter.name";
public static ODataSerializer createSerializer(OData odata, ODataRequest odataRequest, ContentType responseFormat) {
try {
return odata.createSerializer(responseFormat, Arrays.asList(ODataUtils.getODataVersion(odataRequest)));
} catch (SerializerException e) {
throw new ErrorStatusException(CdsErrorStatuses.SERIALIZER_FAILED, e);
}
}
public static EdmAssistedSerializer createSerializerForApply(OData odata, ODataRequest odataRequest, ContentType responseFormat) {
try {
return odata.createEdmAssistedSerializer(responseFormat, Arrays.asList(ODataUtils.getODataVersion(odataRequest)));
} catch (SerializerException e) {
throw new ErrorStatusException(CdsErrorStatuses.SERIALIZER_FAILED, e);
}
}
public static String getODataVersion(ODataRequest odataRequest) {
String odataVersion = odataRequest.getHeader(HttpHeader.ODATA_VERSION);
String odataMaxVersion = odataRequest.getHeader(HttpHeader.ODATA_MAX_VERSION);
return getODataVersion(odataVersion, odataMaxVersion);
}
public static String getODataVersion(String odataVersion, String odataMaxVersion) {
String version = odataVersion != null && ODataServiceVersion.isValidODataVersion(odataVersion) ? odataVersion
: "4.01";
if (odataMaxVersion != null && ODataServiceVersion.isValidMaxODataVersion(odataMaxVersion)) {
if (Double.parseDouble(odataMaxVersion) < Double.parseDouble(version)) {
version = odataMaxVersion;
}
}
return version;
}
public static String getBindingParameter(CdsModel model, UriInfo uriInfo) {
final int lastPathSegmentIndex = uriInfo.getUriResourceParts().size() - 1;
final UriResource lastPathSegment = uriInfo.getUriResourceParts().get(lastPathSegmentIndex);
switch (lastPathSegment.getKind()) {
case action:
UriResourceAction uriResourceAction = (UriResourceAction) lastPathSegment;
EdmAction action = uriResourceAction.getAction();
return getBindingParameter(model, action);
case function:
UriResourceFunction uriResourceFunction = (UriResourceFunction) lastPathSegment;
EdmFunction function = uriResourceFunction.getFunction();
return getBindingParameter(model, function);
default:
return null;
}
}
@VisibleForTesting
static String getBindingParameter(CdsModel model, EdmOperation operation) {
if (!operation.isBound()) {
return null;
}
String fullQualifiedBoundEntityName = operation.getBindingParameterTypeFqn().getFullQualifiedNameAsString();
Optional foundEntity = model.findEntity(fullQualifiedBoundEntityName);
if (foundEntity.isEmpty()) {
return null;
}
CdsEntity entity = foundEntity.get();
switch (operation.getKind()) {
case ACTION:
return entity.findAction(operation.getName())
.map(a -> resolveBindingParameterName(a, a.as(CdsBoundAction.class).getBindingParameter()))
.orElse(null);
case FUNCTION:
return entity.findFunction(operation.getName())
.map(a -> resolveBindingParameterName(a, a.as(CdsBoundFunction.class).getBindingParameter()))
.orElse(null);
default:
return null;
}
}
private static String resolveBindingParameterName(CdsOperation operation, CdsParameter bindingParameter) {
if (bindingParameter != null) {
return operation.getAnnotationValue(BINDING_PARAMETER_ANNOTATION, bindingParameter.getName());
} else {
return operation.getAnnotationValue(BINDING_PARAMETER_ANNOTATION, DEFAULT_BINDING_PARAMETER);
}
}
public static CdsODataResponse toErrorResponse(ServiceException e, CdsRequestGlobals globals) {
ApplicationLifecycleService applicationLifecycleService = globals.getRuntime().getServiceCatalog().getService(ApplicationLifecycleService.class, ApplicationLifecycleService.DEFAULT_NAME);
ErrorResponse errorResponse = applicationLifecycleService.errorResponse(e);
return new CdsODataResponse(errorResponse);
}
public static void setODataErrorResponse(OData odata, ODataRequest odataRequest, ODataResponse odataResponse,
CdsODataResponse cdsResponse, String bindingParameter, ContentType responseFormat) {
String contentId = odataRequest.getHeader("Content-ID");
ODataServerError serverError = new ODataServerError();
List errorMessages = cdsResponse.getErrorMessages();
Message first = errorMessages.get(0);
serverError.setStatusCode(cdsResponse.getStatusCode());
serverError.setCode(first.getCode() != null ? first.getCode() : String.valueOf(cdsResponse.getStatusCode()));
serverError.setMessage(first.getMessage());
serverError.setTarget(MessagesUtils.getTarget(bindingParameter, first.getTarget()));
serverError.setAdditionalProperties(buildAdditionalProperties(null, null, contentId));
// The messages from cdsResponse may not present if the exception hook was not called.
List details = errorMessages.stream().skip(1).map(m -> {
ODataErrorDetail errorDetail = new ODataErrorDetail();
errorDetail.setCode(MessagesUtils.getMessageCode(m));
errorDetail.setMessage(m.getMessage());
errorDetail.setTarget(MessagesUtils.getTarget(bindingParameter, m.getTarget()));
errorDetail.setAdditionalProperties(buildAdditionalProperties(m.getSeverity().getNumericSeverity(), m.getLongTextUrl(), contentId));
return errorDetail;
}).collect(Collectors.toList());
// Details are nullable!
if (!details.isEmpty()) {
serverError.setDetails(details);
}
odataResponse.setStatusCode(cdsResponse.getStatusCode());
odataResponse.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
odataResponse.setHeader(HttpHeader.ODATA_VERSION, ODataUtils.getODataVersion(odataRequest));
try {
odataResponse.setContent(createSerializer(odata, odataRequest, responseFormat).error(serverError).getContent());
} catch (SerializerException e) {
throw new ErrorStatusException(CdsErrorStatuses.RESPONSE_SERIALIZATION_FAILED, e);
}
}
private static Map buildAdditionalProperties(Integer severity, String longtextUrl, String contentId) {
Map additionalProperties = new HashMap<>();
if (severity != null) {
additionalProperties.put("@Common.numericSeverity", severity);
}
if (longtextUrl != null) {
additionalProperties.put("@Common.longtextUrl", longtextUrl);
}
if (contentId != null) {
additionalProperties.put("@Core.ContentID", contentId);
}
return additionalProperties.isEmpty() ? null : additionalProperties;
}
public static Charset getCharset(ContentType contentType) {
Charset charset = StandardCharsets.UTF_8; // default
// try to obtain charset from content type
String charsetFromContentType = contentType.getParameter(ContentType.PARAMETER_CHARSET);
if (!StringUtils.isEmpty(charsetFromContentType)) {
try {
charset = Charset.forName(charsetFromContentType);
} catch (IllegalArgumentException e) {
throw new ErrorStatusException(CdsErrorStatuses.INVALID_CHARSET);
}
} else {
// fallback to default charset mapping for mime type
String mimeType = contentType.getType() + "/" + contentType.getSubtype();
org.apache.http.entity.ContentType apacheContentType = org.apache.http.entity.ContentType.getByMimeType(mimeType);
if (apacheContentType != null && apacheContentType.getCharset() != null) {
charset = apacheContentType.getCharset();
}
}
return charset;
}
public static Object getMaxAgeValue(CdsEntity entity, String elementName) {
return CdsAnnotations.HTTP_CACHE_CONTROL_MAX_AGE.getOrDefault(entity.getElement(elementName));
}
}