Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cloud.sdk.odatav2.connectivity.ODataQuery Maven / Gradle / Ivy
/*******************************************************************************
* (c) 201X SAP SE or an SAP affiliate company. All rights reserved.
******************************************************************************/
package com.sap.cloud.sdk.odatav2.connectivity;
import static com.sap.cloud.sdk.odatav2.connectivity.internal.ODataConnectivityUtil.SEPARATOR_PATH;
import static com.sap.cloud.sdk.odatav2.connectivity.internal.ODataConnectivityUtil.convertKeyValuesToString;
import static com.sap.cloud.sdk.odatav2.connectivity.internal.ODataConnectivityUtil.withSeparator;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.utils.HttpClientUtils;
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.EdmNavigationProperty;
import org.apache.olingo.odata2.api.edm.EdmType;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.client.api.ODataClient;
import org.slf4j.Logger;
import com.google.common.collect.ImmutableList;
import com.sap.cloud.sdk.cloudplatform.cache.CacheKey;
import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.WithDestinationName;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryBuilder.ODataQueryResolver;
import com.sap.cloud.sdk.odatav2.connectivity.api.IODataQuery;
import com.sap.cloud.sdk.odatav2.connectivity.cache.metadata.GuavaMetadataCache;
import com.sap.cloud.sdk.odatav2.connectivity.cache.metadata.MetadataCache;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class ODataQuery implements IODataQuery{
@SuppressWarnings("deprecation")
private static final Logger logger = CloudLoggerFactory.getLogger(ODataQuery.class);
private MetadataCache metadataCache = new GuavaMetadataCache();
private static final List queryListeners;
static {
queryListeners = ImmutableList.copyOf(ServiceLoader.load(ODataQueryListener.class));
}
private static final String SEPARATOR_QUERY = "&";
private static final String METADATA = "$metadata";
@NonNull
private final String servicePath;
@NonNull
private String entity;
@Nullable
private final Map keys;
@NonNull
private final ODataQueryBuilder.ODataQueryResolver oDataQueryResolver;
@Nullable
private final ErrorResultHandler> errorHandler;
@Nullable
private final Map headers;
@Nullable
private final Map destinationRelevantHeaders;
private final boolean useMetadata;
private final Boolean cacheMetadata;
private final URL metadataFliePath;
private final CacheKey cacheKey;
private boolean isCacheRefresh;
private boolean isMediaRequest = false;
private EdmEntitySet entitymetadata = null;
private EdmEntityType entityType = null;
private String requestQuery;
private String requestLocalPath;
private HttpResponse httpResponse = null;
private static final String REQUESTFAILURE = "Failed to execute OData request.";
private List navigationProperties = null;
private EdmEntitySet lastEntityMetadata;
public ODataQuery(String servicePath, String entity, Map keys,
ODataQueryResolver oDataQueryResolver, ErrorResultHandler> errorHandler, Map headers,
Map destinationRelevantHeaders, boolean useMetadata, boolean cacheMetadata,
URL metadataFilePath, CacheKey cacheKey, boolean isCacheRefresh) {
this.servicePath = servicePath;
this.entity = entity;
this.keys = keys;
this.oDataQueryResolver = oDataQueryResolver;
this.errorHandler = errorHandler;
this.headers = headers;
this.destinationRelevantHeaders = destinationRelevantHeaders;
this.useMetadata = useMetadata;
this.cacheMetadata = cacheMetadata;
this.metadataFliePath = metadataFilePath;
this.cacheKey = cacheKey;
this.isCacheRefresh = isCacheRefresh;
}
public ODataQuery(String servicePath, String entity, Map keys,
ODataQueryResolver oDataQueryResolver, ErrorResultHandler> errorHandler, Map headers,
Map destinationRelevantHeaders, boolean useMetadata, boolean cacheMetadata,
URL metadataFilePath, CacheKey cacheKey, boolean isCacheRefresh,
List navigationProperties) {
this(servicePath, entity, keys, oDataQueryResolver, errorHandler, headers,
destinationRelevantHeaders, useMetadata, cacheMetadata, metadataFilePath, cacheKey,
isCacheRefresh);
this.navigationProperties = navigationProperties;
}
/**
* Executes the OData query represented by this ODataQuery object.
*
* @param providedClient
* custom HttpClient capable of connecting to the data source
* @return ODataQueryResult represents the result of the query operation
* @throws ODataException
*/
public ODataQueryResult execute(HttpClient providedClient) throws ODataException {
return execute(providedClient, false);
}
/**
* Executes the OData query represented by this ODataQuery object.
*
* @param providedClient
* HttpClient capable of connecting to the data source
* @param isMediaRequest
* indicates if the request is for a media resource
* @return ODataQueryResult represents the result of the query operation
* @throws ODataException
*/
public ODataQueryResult execute(HttpClient providedClient, boolean isMediaRequest) throws ODataException {
ODataQueryResult result = null;
try {
result = internalExecute(null, isMediaRequest, true, providedClient);
} catch (ODataException e) {
if (e.getODataExceptionType().equals(ODataExceptionType.OTHER)
|| e.getODataExceptionType().equals(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED)) {
throw e;
} else {
result = retryExecuteWithCompleteUrl(null, providedClient, e);
}
}
return result;
}
private ODataQueryResult retryExecuteWithCompleteUrl(String destinationName, HttpClient providedClient,
ODataException e) throws ODataException {
ODataQueryResult result;
try {
getUri(getServiceUrl(destinationName), getMetadataQuery(destinationName), null).toString();
} catch (URISyntaxException e1) {
throw new ODataException(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED, REQUESTFAILURE, e);
}
this.isCacheRefresh = true;
result = internalExecute(destinationName, isMediaRequest, false, providedClient);
return result;
}
ODataQueryResult execute(final String destinationName, boolean isMediaRequest) throws ODataException {
ODataQueryResult result = null;
this.isMediaRequest = isMediaRequest;
try {
result = internalExecute(destinationName, isMediaRequest, false, null);
} catch (ODataException e) {
if (e.getODataExceptionType().equals(ODataExceptionType.OTHER)
|| e.getODataExceptionType().equals(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED)) {
throw e;
} else {
result = retryExecuteWithCompleteUrl(destinationName, null, e);
}
}
return result;
}
/**
* Executes the OData query represented by this ODataQuery object.
*
* @param withDestinationName
* @return ODataQueryResult represents the result of the query operation.
* @throws ODataException
*/
public ODataQueryResult execute(final WithDestinationName withDestinationName) throws ODataException {
ODataQueryResult result = null;
try {
result = execute(withDestinationName.getDestinationName());
} catch (ODataException e) {
if (e.getODataExceptionType().equals(ODataExceptionType.OTHER)
|| e.getODataExceptionType().equals(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED)) {
throw e;
} else {
this.isCacheRefresh = true;
result = execute(withDestinationName.getDestinationName());
}
}
return result;
}
/**
* Executes the OData query represented by this ODataQuery object.
*
* @param destinationName
* @return ODataQueryResult represents the result of the query operation.
* @throws ODataException
*/
public ODataQueryResult execute(final String destinationName) throws ODataException {
if (destinationName == null) {
throw new ODataException(null,
"Missing destination name configuration, " + "please declare an endpoint for the OData query.",
null);
}
return execute(destinationName, false);
}
private ODataQueryResult internalExecute(final String destinationName, boolean isMediaRequest,
boolean withDefaultClient, HttpClient providedClient) throws ODataException {
// TODO This will fail when there is no Destination and there is a listener??
// Need to check
ODataQueryResult result;
notifyQueryListeners(destinationName);
try {
if (StringUtils.isEmpty(servicePath)) {
throw new ODataException(null, "Missing service in OData query.", null);
}
if (StringUtils.isEmpty(entity)) {
throw new ODataException(null, "Missing entity in OData query.", null);
}
HttpClient httpClient = (destinationName == null ? providedClient
: HttpClientAccessor.getHttpClient(destinationName));
final URI serviceUrl = getServiceUrl(destinationName);
httpResponse = loadEntriesFromDestination(destinationName, serviceUrl, isMediaRequest, httpClient);
if(lastEntityMetadata != null){
result = new ODataQueryResult(lastEntityMetadata, httpResponse, isMediaRequest);
}else{
result = new ODataQueryResult(entitymetadata, httpResponse, isMediaRequest);
}
result.setQuery(this);
return result;
} catch (final DestinationAccessException | URISyntaxException | IllegalStateException | IOException
| DestinationNotFoundException | HttpClientInstantiationException | EdmException e) {
if (e instanceof DestinationAccessException) {
logger.error("Could not connect to destination service [No Access] :" + e.getMessage());
logger.error("Could not connect to destination service [No Access] : " + e.getStackTrace());
} else if (e instanceof DestinationNotFoundException) {
logger.error("Could not connect to destination service [Not Found] :" + e.getMessage());
logger.error("Could not connect to destination service [Not Found] : " + e.getStackTrace());
} else if (e instanceof HttpClientInstantiationException) {
logger.error("Could not connect to destination service [Can't Create HttpClient] :" + e.getMessage());
logger.error(
"Could not connect to destination service [Can't Create HttpClient] : " + e.getStackTrace());
}
throw new ODataException(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED, REQUESTFAILURE, e);
}finally {
if(!isMediaRequest)
HttpClientUtils.closeQuietly(httpResponse);
}
}
private URI getServiceUrl(final String destinationName) throws URISyntaxException {
URI servUri = (destinationName != null)
? getUri(DestinationAccessor.getDestination(destinationName).getUri(),
withSeparator(SEPARATOR_PATH, this.servicePath), null)
: new URI(this.servicePath);
return servUri;
}
private void notifyQueryListeners(final String destinationName) {
for (ODataQueryListener ql : queryListeners) {
// wrap in generic try/catch to make the ODataQuery resilient
// against Exceptions from listeners
try {
if (destinationName != null) {
ql.onQuery(destinationName, this.servicePath, this.entity);
} else {
ql.onQuery(new URI(this.servicePath), this.entity);
}
} catch (Exception e) {
logger.error("Failure while invoking query listener of type " + ql.getClass(), e);
}
}
}
private HttpResponse loadEntriesFromDestination(String destinationName, final URI serviceUrl,
boolean isMediaRequest, HttpClient httpClient) throws URISyntaxException, ODataException,
DestinationNotFoundException, DestinationAccessException, HttpClientInstantiationException, EdmException {
if (useMetadata && entitymetadata == null) {
final URI uri = getUri(serviceUrl, withSeparator(SEPARATOR_PATH, METADATA), null);
logRequestMeta(uri);
try {
loadMetadata(httpClient, uri, errorHandler);
} catch (ODataException e) {
throw new ODataException(ODataExceptionType.METADATA_FETCH_FAILED,
"Failed to execute OData Metadata request.", e.getHttpStatusCode(),e);
}
}
Map typeMap = new HashMap<>();
if (entitymetadata != null) { // Currently this will not be null if
// there is keys or filter set in the
// query.
try {
entityType = entitymetadata.getEntityType();
for (String p : entityType.getPropertyNames()) {
typeMap.put(p, entityType.getProperty(p).getType());
}
if (keys != null)
entity += '(' + convertKeyValuesToString(keys, entityType) + ')';
} catch (EdmException e1) {
throw new ODataException(ODataExceptionType.METADATA_PARSING_FAILED,
"Error while parsing the metadata.", null);
}
}
if (isMediaRequest) {
entity = entity + "/$value";
} else {
requestQuery = oDataQueryResolver.getQuery(entityType);
}
String withSeperator = withSeparator(SEPARATOR_PATH, entity, requestLocalPath);
withSeperator = addNavigations(withSeperator);
URI uri = getUri(serviceUrl, withSeperator, requestQuery);
uri = uri.resolve(getUrlPath(serviceUrl, withSeperator, requestQuery));
logRequest(uri, destinationName); // DestinationName passed for this
// logging
return new ODataRequestExecutor(httpClient, uri).errorHandler(errorHandler).headers(headers).execute();
}
public List getNavigations() {
return navigationProperties;
}
public String addNavigations(String url) throws EdmException, ODataException {
if (navigationProperties != null && !navigationProperties.isEmpty()) {
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(url);
for (ODataNavigationRequest navigationProperty : navigationProperties) {
urlBuilder.append("/");
urlBuilder.append(navigationProperty.getNavigationPropertyName());
if (navigationProperty.getKeys() != null && navigationProperty.getNavigationType() != null) {
urlBuilder.append('(');
urlBuilder.append(convertKeyValuesToString(navigationProperty.getKeys(),
navigationProperty.getNavigationType()));
urlBuilder.append(')');
}
}
return urlBuilder.toString();
}
return url;
}
public EdmEntitySet updateNavigationType(Edm edm) throws EdmException, ODataException{
entitymetadata = edm.getDefaultEntityContainer().getEntitySet(getEntityName(entity));
if(navigationProperties != null && !navigationProperties.isEmpty()){
EdmEntityType navigationType = entitymetadata.getEntityType();
for(ODataNavigationRequest property:navigationProperties){
navigationType = updateNavigationTypeForProperty(property, navigationType, edm);
property.setNavigationType(navigationType);
}
lastEntityMetadata = fetchEntityDetails(edm, navigationType);
}
return lastEntityMetadata;
}
private EdmEntitySet fetchEntityDetails(Edm edm, EdmEntityType navigationType) throws EdmException {
for(EdmEntitySet entitySet : edm.getEntitySets()){
if(navigationType != null && entitySet.getEntityType().getName().equals(navigationType.getName())
&& entitySet.getEntityType().getNamespace().equals(navigationType.getNamespace())){
return entitySet;
}
}
return entitymetadata;
}
private EdmEntityType updateNavigationTypeForProperty(ODataNavigationRequest navigationProperty, EdmEntityType entityType, Edm edm) throws ODataException{
EdmNavigationProperty navigation = null;
try {
navigation = (EdmNavigationProperty) entityType.getProperty(navigationProperty.getNavigationPropertyName());
if (navigation != null) {
EdmEntityType navigationType = navigation.getRelationship().getEnd1().getEntityType();
if (navigationType.getName().equals(entityType.getName())) {
return navigation.getRelationship().getEnd2().getEntityType();
} else {
return navigationType;
}
}
} catch (EdmException e) {
throw new ODataException(ODataExceptionType.METADATA_PARSING_FAILED,
"Failed to load entity type for \"" + entity + "\"." + "navigation: "+ navigation, e);
}
return null;
}
private String getUrlPath(URI dest, String additionalPath, String query) {
String adjustedPath = withSeparator(SEPARATOR_PATH, dest.getPath(), additionalPath);
String adjustedQuery = withSeparatorOmitFirst(SEPARATOR_QUERY, dest.getQuery(), query);
if (adjustedQuery.isEmpty()){
adjustedQuery = null;
}else{
adjustedPath = adjustedPath + "?"+adjustedQuery;
}
return adjustedPath.replace(" ", "%20");
}
private void loadMetadata(HttpClient httpClient, URI uri, ErrorResultHandler> errorResultHandler)
throws ODataException {
Edm edm = null;
// **** needs to be refactored
if (!cacheMetadata && metadataFliePath != null && !metadataFliePath.toString().isEmpty()) {
try (InputStream is = metadataFliePath.openStream()) {
edm = ODataClient.newInstance().readMetadata(is, true).getEdm();
if (logger.isDebugEnabled()) {
logger.debug(String.format("Fetched metadata from the file %s", metadataFliePath.toString()));
}
} catch (final EdmException e) {
throw new ODataException(ODataExceptionType.METADATA_PARSING_FAILED,
"Failed to read metadata for \"" + entity + "\".", e);
} catch (IOException | EntityProviderException e) {
logger.error("Failed to fetch the metadata", e);
throw new ODataException(ODataExceptionType.METADATA_FETCH_FAILED,
"Failed to fetch the metadata \"" + entity + "\".", e);
} //*******
} else {
edm = metadataCache.getEdm(servicePath + "/$metadata", httpClient, destinationRelevantHeaders, errorResultHandler,
cacheMetadata, metadataFliePath,cacheKey,isCacheRefresh);
}
try {
updateNavigationType(edm);
} catch (EdmException e) {
throw new ODataException(ODataExceptionType.METADATA_PARSING_FAILED,
"Failed to read metadata for \"" + entity + "\".", e);
}
if (entitymetadata == null) {
throw new ODataException(ODataExceptionType.METADATA_PARSING_FAILED,
"No entity found in metadata for \"" + entity + "\".");
}
}
private String getEntityName(String entitywkeys) {
String result = entitywkeys;
if (entitywkeys.contains("(")) {
result = entitywkeys.substring(0, entitywkeys.indexOf("("));
}
return result;
}
private URI getUri(final URI dest, final String additionalPath, final String query) throws URISyntaxException {
final String adjustedPath = withSeparator(SEPARATOR_PATH, dest.getPath(), additionalPath);
String adjustedQuery = withSeparatorOmitFirst(SEPARATOR_QUERY, dest.getQuery(), query);
if (adjustedQuery.isEmpty())
adjustedQuery = null;
return new URI(dest.getScheme(), dest.getUserInfo(), dest.getHost(), dest.getPort(), adjustedPath,
adjustedQuery, dest.getFragment());
}
private void logRequest(final URI uri, final String destinationName) {
logger.debug("Executing OData entity query to \"" + servicePath + "\" with entity \"" + entity
+ "\" and parameters \"" + StringUtils.defaultIfEmpty(requestQuery, "") + "\" on destination \""
+ destinationName + "\". URI: " + uri + ".");
}
private void logRequestMeta(final URI uri) {
logger.debug("Executing OData metadata query to " + uri + ".");
}
private static String withSeparatorOmitFirst(final String separator, final String... parts) {
return StringUtils.removeStart(withSeparator(separator, parts), separator);
}
@Override
public String toString() {
String query = "";
if (!isMediaRequest && !StringUtils.isEmpty(requestQuery)) {
query = "?" + requestQuery;
}
if (!isMediaRequest && oDataQueryResolver != null) {
try {
query = "?" + oDataQueryResolver.getQuery(null);
} catch (EdmException e) {
return e.getMessage(); // This is sufficient since this method is used for testing and debugging
// purposes only.
}
}
return withSeparator(SEPARATOR_PATH, servicePath, entity, requestLocalPath) + query;
}
public String getMetadataQuery() {
return forMetadata(servicePath);
}
private String getMetadataQuery(String destinationName) {
if (destinationName == null) {
try {
return forMetadata(new URI(servicePath).getPath().toString());
} catch (URISyntaxException e) {
logger.error("Failure in metadata query population " + e.getMessage());
return null;
}
} else
return forMetadata(servicePath);
}
public static String forMetadata(final String path) {
return withSeparator(SEPARATOR_PATH, path, METADATA);
}
protected String getEntity() {
return entity;
}
protected String getServicePath() {
return servicePath;
}
protected Map getKeys() {
return keys;
}
protected ODataQueryBuilder.ODataQueryResolver getoDataQueryResolver() {
return oDataQueryResolver;
}
protected ErrorResultHandler> getErrorHandler() {
return errorHandler;
}
protected Map getHeaders() {
return headers;
}
}