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

com.sap.cds.adapter.odata.v2.CdsODataV2ServiceFactory Maven / Gradle / Ivy

There is a newer version: 3.6.0
Show newest version
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.adapter.odata.v2;

import java.util.Map;
import java.util.Optional;

import jakarta.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.olingo.odata2.api.ODataCallback;
import org.apache.olingo.odata2.api.ODataService;
import org.apache.olingo.odata2.api.ODataServiceFactory;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataContext;
import org.apache.olingo.odata2.api.uri.PathInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cds.adapter.odata.v2.metadata.MetadataInfo;
import com.sap.cds.adapter.odata.v2.metadata.model.AbstractEdmxProviderAccessor;
import com.sap.cds.adapter.odata.v2.processors.ErrorCallback;
import com.sap.cds.adapter.odata.v2.processors.OlingoProcessor;
import com.sap.cds.adapter.odata.v2.query.SystemQueryLoader;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.cds.ApplicationService;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cds.services.utils.ODataUtils;
import com.sap.cds.services.utils.StringUtils;
import com.sap.cds.services.utils.path.CdsServicePath;

public class CdsODataV2ServiceFactory extends ODataServiceFactory {

	private static final Logger logger = LoggerFactory.getLogger(CdsODataV2ServiceFactory.class);
	private static final String PATHINFO = "~pathInfo";
	private static final String UNEXPECTED_ERROR_MESSAGE = "An unexpected error occurred during servlet processing";

	private final CdsRuntime runtime;
	private final Map servicePaths;

	public CdsODataV2ServiceFactory(CdsRuntime runtime) {
		this.runtime = runtime;
		this.servicePaths = CdsServicePath.basePaths(runtime, CdsODataV2ServletFactory.PROTOCOL_KEY);

		// initialize metadata provider and caches
		AbstractEdmxProviderAccessor.initialize(runtime);
		SystemQueryLoader.initialize(runtime);
	}

	@Override
	public ODataService createService(ODataContext context) throws ODataException {
		HttpServletRequest request = (HttpServletRequest) context.getParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT);
		CdsRequestGlobals requestGlobals = (CdsRequestGlobals) request.getAttribute(CdsODataV2Servlet.ATTRIBUTE_REQUEST_GLOBALS);

		try {
			PathInfo path = (PathInfo) context.getParameter(PATHINFO);
			String pathInfo = path.getRequestUri().getPath();
			if (pathInfo == null) {
				throw new ErrorStatusException(CdsErrorStatuses.INVALID_URI_RESOURCE);
			}

			MutablePair serviceInfo = extractServiceInfo(request.getPathInfo(), servicePaths);
			ApplicationService applicationService = serviceInfo.getLeft();
			String serviceName = applicationService.getDefinition().getQualifiedName();
			requestGlobals.setApplicationService(applicationService);

			applicationService.getDefinition().entities().forEach(entity -> {
				String cdsName = entity.getName();
				String edmName = ODataUtils.toODataName(cdsName);
				if (!edmName.equals(cdsName)) {
					String key = entity.getQualifier() + "." + edmName;
					String value = entity.getQualifier() + "." + cdsName;
					requestGlobals.getCdsEntityNames().put(key, value);
				}
			});

			MetadataInfo metadataInfo = AbstractEdmxProviderAccessor.getInstance().getMetadataInfo(serviceName);
			if (metadataInfo.getEdmxMetadataProvider() == null) {
				throw new ErrorStatusException(CdsErrorStatuses.SERVICE_NOT_FOUND, serviceName);
			}

			return createODataSingleProcessorService(metadataInfo.getEdmxMetadataProvider(), new OlingoProcessor(metadataInfo, requestGlobals));
		} catch (ServiceException e) {
			int httpStatus = e.getErrorStatus().getHttpStatus();
			if(httpStatus >= 500 && httpStatus < 600) {
				logger.error(UNEXPECTED_ERROR_MESSAGE, e);
			} else {
				logger.debug(UNEXPECTED_ERROR_MESSAGE, e);
			}
		} catch (Exception e) { // NOSONAR
			logger.error(UNEXPECTED_ERROR_MESSAGE, e);
		}
		return null;
	}

	/**
	 * Extracts the service name from the given url path.
	 *
	 * Common schema of an odata request:
		http://host:port/path/SampleService.svc/Categories(1)/Products?$top=2&$orderby=Name
		\______________________________________/\____________________/ \__________________/
		                  |                               |                       |
		          service root URL                  resource path           query options
		                     \________________________________________/
										|
									pathInfo
	 */
	private MutablePair extractServiceInfo(String pathInfo, Map servicePaths) {
		if(pathInfo.trim().isEmpty()) {
			throw new ErrorStatusException(ErrorStatuses.NOT_FOUND);
		}

		Optional servicePathKey = getServicePath(pathInfo);

		if(servicePathKey.isPresent()) {
			// service should have a path derived from the model
			String servicePath = servicePathKey.get();
			ApplicationService service = servicePaths.get(servicePath);
			if (service != null) {
				return MutablePair.of(service, servicePath);
			}
		}

		throw new ErrorStatusException(CdsErrorStatuses.SERVICE_NOT_FOUND, pathInfo);
	}

	/**
	 * Returns an {@link Optional} describing the service path matching the given path info
	 * most accurately.
	 *
	 * @param pathInfo the path info to find the service path for
	 * @return the service path optional
	 */
	public Optional getServicePath(String pathInfo) {
		String path = StringUtils.trim(pathInfo.trim(), '/');
		return servicePaths.keySet().stream()
				// sort from longest path to shortest
				.sorted((a, b) -> -1 * Integer.compare(a.length(), b.length()))
				// find the most accurate match
				.filter(p -> path.startsWith(p)).findFirst();
	}

	@SuppressWarnings("unchecked")
	@Override
	public  T getCallback(final Class callbackInterface) {
		return (T) (callbackInterface.isAssignableFrom(ErrorCallback.class) ?
				new ErrorCallback(runtime) : super.getCallback(callbackInterface));
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy