All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.adapter.odata.v4.utils.ODataUtils Maven / Gradle / Ivy

There is a newer version: 3.2.0
Show newest version
/**************************************************************************
 * (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));
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy