com.sap.cloud.sdk.services.scp.blockchain.hyperledgerfabric.FabricService Maven / Gradle / Ivy
/*
* Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.services.scp.blockchain.hyperledgerfabric;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import com.google.common.annotations.Beta;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpEntityUtil;
import com.sap.cloud.sdk.cloudplatform.connectivity.ScpCfService;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException;
import com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import lombok.extern.slf4j.Slf4j;
/**
* SAP Cloud Platform Blockchain using Hyperledger Fabric adapter service. Facilitates access to chaincode interfaces.
*
* Be aware that this class is in Beta (as indicated by the annotation) and therefore subject to breaking changes.
*
*/
@Beta
@Slf4j
public class FabricService
{
private final ScpCfService cfService;
private final String serviceUrl;
private FabricService( final ScpCfService cfService ) throws CloudPlatformException
{
this.cfService = cfService;
if( cfService.getServiceLocationInfo() == null ) {
throw new CloudPlatformException("Missing service location info on given service: " + cfService);
}
serviceUrl = cfService.getServiceLocationInfo();
}
/**
* Create a service instance looking for an instance of service type 'hyperledger-fabric'.
*
* @return the new instance
* @throws CloudPlatformException
* if service information cannot be found or is incompletely specified
*/
@Nonnull
public static FabricService create()
throws CloudPlatformException
{
return getFabricService("hyperledger-fabric");
}
/**
* Access a function exposed by a chaincode.
*
* @param invocationType
* lightweight query or transaction via invoke.
* @param chainCodeId
* identifying the chain code.
* @param function
* the name of the function to be invoked.
* @param params
* parameters to the function call.
*
* @return the string response returned by the function, decoded as UTF-8 string.
*
* @throws IllegalArgumentException
* if the chaincodeId argument contains whitespace.
* @throws FabricServiceFailureException
* if the service invocation fails.
* @throws JsonIOException
* if the encoding of the request fails.
* @throws CloudPlatformException
* if there is a problem authorizing the access.
*/
@Nonnull
public String invokeOrQuery(
@Nonnull final FabricInvocationType invocationType,
@Nonnull final String chainCodeId,
@Nonnull final String function,
@Nonnull final String... params )
throws FabricServiceFailureException,
IllegalArgumentException,
JsonIOException,
CloudPlatformException
{
final List argsList = Arrays.asList(params);
if( log.isTraceEnabled() ) {
log.trace("{} {}:{}({})", invocationType, chainCodeId, function, argsList);
} else {
log.debug("{} {}:{}", invocationType, chainCodeId, function);
}
if( Strings.isNullOrEmpty(chainCodeId) || chainCodeId.matches(".*\\s.*") ) {
throw new IllegalArgumentException("Given chaincode id invalid - empty string or contains whitespace");
}
final HttpPost chaincodeEndpointRequest;
try {
chaincodeEndpointRequest =
new HttpPost(
new URI(
getServiceUrl()
+ "/chaincodes/"
+ chainCodeId
+ "/latest/"
+ invocationType.getPathParamValue()).normalize());
}
catch( final URISyntaxException e ) {
throw new ShouldNotHappenException("URL for accessing fabric service problematic: " + e.getMessage(), e);
}
cfService.addBearerTokenHeader(chaincodeEndpointRequest);
chaincodeEndpointRequest.setHeader("Accept", "application/json;charset=UTF-8");
final Map requestBody = new LinkedHashMap<>();
requestBody.put("function", function);
requestBody.put("arguments", argsList);
requestBody.put("async", false);
final String json;
try {
json = new Gson().toJson(requestBody);
}
catch( final JsonIOException e ) {
log.debug("Could not encode request: " + e);
throw e;
}
log.trace("Request to be sent: {}", json);
chaincodeEndpointRequest.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
// Overriding the content type to NOT contain the charset is required due to bug in (newer?) Fabric services
chaincodeEndpointRequest.setHeader("Content-Type", "application/json");
try {
final HttpResponse response = HttpClientAccessor.getHttpClient().execute(chaincodeEndpointRequest);
final StatusLine statusLine = response.getStatusLine();
final String responseBody = HttpEntityUtil.getResponseBody(response);
if( responseBody == null ) {
throw new FabricServiceFailureException(
"Failed to execute request to function " + function + ": no body returned in response.");
}
if( log.isTraceEnabled() ) {
log.trace("Response status: {}, content: {}", statusLine, responseBody);
} else {
log.debug("Response status: {}", statusLine);
}
switch( statusLine.getStatusCode() ) {
case HttpStatus.SC_OK:
return responseBody;
default:
throw new FabricServiceFailureException(
"Failed to execute request to function "
+ function
+ ": "
+ "Service request failed with status: "
+ statusLine
+ ".");
}
}
catch( final IOException | HttpClientInstantiationException e ) {
final String message = "Failed to execute request to function " + function + ": " + e.getMessage();
log.debug(message, e);
throw new FabricServiceFailureException(message, e);
}
finally {
chaincodeEndpointRequest.releaseConnection();
}
}
static FabricService getFabricService( @Nonnull final String serviceType )
throws CloudPlatformException
{
final ScpCfService cfService =
ScpCfService.of(
serviceType,
null,
"credentials/oAuth/url",
"credentials/oAuth/clientId",
"credentials/oAuth/clientSecret",
"credentials/serviceUrl");
return new FabricService(cfService);
}
/**
* Access to the Fabric service root URL.
*
* @return the url
*/
@Nonnull
public String getServiceUrl()
{
return serviceUrl;
}
@Override
@Nonnull
public String toString()
{
return "FabricService{" + "cfService=" + cfService + '}';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy