Skip to content

Commit

Permalink
Add: Custom Sequence support for the APIS
Browse files Browse the repository at this point in the history
  • Loading branch information
BLasan committed Aug 15, 2024
1 parent 29b4573 commit a9442fd
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,9 @@ public enum ExceptionCodes implements ErrorHandler {
// API import/export related codes
ERROR_READING_META_DATA(900907, "Error while reading meta information from the definition", 400,
"Error while reading meta information from the definition"),

ERROR_READING_CUSTOM_SEQUENCE(900908, "Error while reading Custom Sequence from the API Endpoint Configuration", 400,
"Error while reading Custom Sequence from the API Endpoint Configuration"),
ERROR_READING_PARAMS_FILE(900908, "Error while reading meta information from the params file", 400,
"Error while reading meta information from the params file"),
ERROR_FETCHING_DEFINITION_FILE(900909, "Cannot find the definition file of the project", 400,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1759,7 +1759,6 @@ private ConfigParameters() {
public static final String ENDPOINT_TYPE_ADDRESS = "address";
public static final String ENDPOINT_TYPE_AWSLAMBDA = "awslambda";
public static final String ENDPOINT_TYPE_SEQUENCE = "custom_sequence";

public static final String SEQUENCE_DATA = "sequence";
public static final String ENDPOINT_PRODUCTION_FAILOVERS = "production_failovers";
public static final String ENDPOINT_SANDBOX_FAILOVERS = "sandbox_failovers";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2909,6 +2909,17 @@ public static void validateAPIContext(String context, String apiName) throws API
}
}

public static void validateAPIEndpointConfig(Object endpointConfigObject, String apiType, String apiName)
throws APIManagementException {
Map endpointConfigMap = (Map) endpointConfigObject;
if (endpointConfigMap.containsKey("endpoint_type") && APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointConfigMap.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))
&& !APIConstants.APITransportType.GRAPHQL.toString().equalsIgnoreCase(apiType)
&& !APIConstants.API_TYPE_HTTP.equalsIgnoreCase(apiType)) {
throw new APIManagementException("Invalid endpoint configuration provided for the API " + apiName);
}
}

/**
* Check whether the parentheses are balanced
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONObject;
import org.osgi.service.component.annotations.Component;
import org.wso2.carbon.apimgt.api.APIDefinition;
import org.wso2.carbon.apimgt.api.APIDefinitionValidationResponse;
Expand Down Expand Up @@ -106,22 +105,17 @@ public RuntimeArtifactDto generateGatewayArtifact(List<APIRuntimeArtifactDto> ap
tenantDomain, extractedFolderPath);
} else {
APIDTO apidto = ImportUtils.retrievedAPIDto(extractedFolderPath);
if (apidto.getEndpointConfig() != null) {
// convert sequence to string
JSONObject endpointObject = (JSONObject) apidto.getEndpointConfig();
if (APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointObject.get(APIConstants.ENDPOINT_TYPE_SEQUENCE))) {
try (InputStream sequence = (InputStream) endpointObject.get(
APIConstants.SEQUENCE_DATA)) {
File dir = CommonUtil.createTempDirectory(null);
String path = ImportUtils.getArchivePathOfExtractedDirectory(
dir.getAbsolutePath(), sequence);
String content = ImportUtils.retrieveXMLContent(path);
endpointObject.put("sequence", content);
apidto.setEndpointConfig(endpointObject);
}
}
}
// if (apidto.getEndpointConfig() != null) {
// org.json.JSONObject endpointConfig = new org.json.JSONObject(
// new Gson().toJson(apidto.getEndpointConfig()));
// if (APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
// endpointConfig.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))) {
// String content = ImportUtils.retrieveXMLContent(
// (InputStream) endpointConfig.get(APIConstants.SEQUENCE_DATA));
// endpointConfig.put("sequence", content);
// }
// apidto.setEndpointConfig(endpointConfig);
// }
API api = APIMappingUtil.fromDTOtoAPI(apidto, apidto.getProvider());
api.setUUID(apidto.getId());
if (APIConstants.APITransportType.GRAPHQL.toString().equals(api.getType())) {
Expand All @@ -145,7 +139,7 @@ public RuntimeArtifactDto generateGatewayArtifact(List<APIRuntimeArtifactDto> ap
// // if sequence is passed, then override the existing config
// gatewayAPIDTO.setApiDefinition(api.getAsyncApiDefinition());
} else if (api.getType() != null &&
(APIConstants.APITransportType.HTTP.toString().equals(api.getType())
(APIConstants.APITransportType.HTTP.toString ().equals(api.getType())
|| APIConstants.API_TYPE_SOAP.equals(api.getType())
|| APIConstants.API_TYPE_SOAPTOREST.equals(api.getType())
|| APIConstants.APITransportType.WEBHOOK.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,16 @@
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
Expand Down Expand Up @@ -263,6 +264,21 @@ public static ImportedAPIDTO importApi(String extractedFolderPath, APIDTO import
// validate the API context
APIUtil.validateAPIContext(importedApiDTO.getContext(), importedApiDTO.getName());

// Get the endpoint config object updated
APIUtil.validateAPIEndpointConfig(importedApiDTO.getEndpointConfig(), importedApiDTO.getType().toString(),
importedApiDTO.getName());

Map endpointConfig = (Map) importedApiDTO.getEndpointConfig();

// if a valid one then update the sequence file path
if (endpointConfig != null && APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointConfig.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))) {
String sequenceFileName = endpointConfig.get("sequence").toString();
String path = extractedFolderPath + File.separator + sequenceFileName;
endpointConfig.put("sequence", path);
importedApiDTO.setEndpointConfig(endpointConfig);
}

API targetApi = retrieveApiToOverwrite(importedApiDTO.getName(), importedApiDTO.getVersion(),
currentTenantDomain, apiProvider, Boolean.TRUE, organization);

Expand Down Expand Up @@ -1288,8 +1304,15 @@ public static APIProductDTO retrieveAPIProductDto(String pathToArchive) throws I
return new Gson().fromJson(jsonObject, APIProductDTO.class);
}

public static String retrieveXMLContent(String pathToArchive) throws IOException, APIManagementException {
return FileUtils.readFileToString(new File(pathToArchive), Charset.defaultCharset());
public static String retrieveXMLContent(InputStream sequenceInputStream) throws IOException {
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(sequenceInputStream))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
}
return result.toString();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import graphql.schema.validation.SchemaValidationError;
import graphql.schema.validation.SchemaValidator;
import io.swagger.v3.parser.ObjectMapperFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
Expand Down Expand Up @@ -93,9 +94,11 @@
import org.wso2.carbon.core.util.CryptoUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -273,6 +276,26 @@ private static API prepareForUpdateApi(API originalAPI, APIDTO apiDtoToUpdate, A
encryptEndpointSecurityOAuthCredentials(endpointConfig, cryptoUtil, oldProductionApiSecret, oldSandboxApiSecret,
apiDtoToUpdate);

// update endpointConfig with the provided custom sequence
if (endpointConfig != null) {
if (APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointConfig.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))) {
try {
if (endpointConfig.get("sequence") != null) {
String pathToSequence = endpointConfig.get("sequence").toString();
String sequence = FileUtils.readFileToString(new File(pathToSequence),
Charset.defaultCharset());
endpointConfig.put("sequence", sequence);
apiDtoToUpdate.setEndpointConfig(endpointConfig);
}
} catch (IOException ex) {
throw new APIManagementException(
"Error while reading Custom Sequence of API: " + apiDtoToUpdate.getId(), ex,
ExceptionCodes.ERROR_READING_CUSTOM_SEQUENCE);
}
}
}

// AWS Lambda: secret key encryption while updating the API
if (apiDtoToUpdate.getEndpointConfig() != null) {
if (endpointConfig.containsKey(APIConstants.AMZN_SECRET_KEY)) {
Expand Down Expand Up @@ -923,6 +946,25 @@ public static API addAPIWithGeneratedSwaggerDefinition(APIDTO apiDto, String oas
encryptEndpointSecurityOAuthCredentials(endpointConfig, cryptoUtil, StringUtils.EMPTY, StringUtils.EMPTY,
apiDto);

// update endpointConfig with the provided custom sequence
if (endpointConfig != null) {
if (APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointConfig.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))) {
try {
if (endpointConfig.get("sequence") != null) {
String pathToSequence = endpointConfig.get("sequence").toString();
String sequence = FileUtils.readFileToString(new File(pathToSequence),
Charset.defaultCharset());
endpointConfig.put("sequence", sequence);
apiDto.setEndpointConfig(endpointConfig);
}
} catch (IOException ex) {
throw new APIManagementException("Error while reading Custom Sequence of API: " + apiDto.getId(),
ex, ExceptionCodes.ERROR_READING_CUSTOM_SEQUENCE);
}
}
}

// AWS Lambda: secret key encryption while creating the API
if (apiDto.getEndpointConfig() != null) {
if (endpointConfig.containsKey(APIConstants.AMZN_SECRET_KEY)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,28 @@ public Response createNewAPIVersion( @NotNull @Size(max=30) @ApiParam(value = "V
return delegate.createNewAPIVersion(newVersion, apiId, defaultVersion, serviceVersion, securityContext);
}

@PUT
@Path("/{apiId}/custom-sequence")
@Consumes({ "multipart/form-data" })
@Produces({ "application/json" })
@ApiOperation(value = "Upload Custom Sequence as the Endpoint of the API", notes = "This operation can be used to change the endpoint of the API to Custom Sequence", response = APIDTO.class, authorizations = {
@Authorization(value = "OAuth2Security", scopes = {
@AuthorizationScope(scope = "apim:api_create", description = "Create API"),
@AuthorizationScope(scope = "apim:api_manage", description = "Manage all API related operations"),
@AuthorizationScope(scope = "apim:api_publish", description = "Publish API")
})
}, tags={ "APIs", })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK. Successful response with updated API object ", response = APIDTO.class),
@ApiResponse(code = 400, message = "Bad Request. Invalid request or validation error.", response = ErrorDTO.class),
@ApiResponse(code = 403, message = "Forbidden. The request must be conditional but no condition has been specified.", response = ErrorDTO.class),
@ApiResponse(code = 404, message = "Not Found. The specified resource does not exist.", response = ErrorDTO.class),
@ApiResponse(code = 409, message = "Conflict. Specified resource already exists.", response = ErrorDTO.class),
@ApiResponse(code = 412, message = "Precondition Failed. The request has not been performed because one of the preconditions is not met.", response = ErrorDTO.class) })
public Response customSequenceUpdate(@ApiParam(value = "**API ID** consisting of the **UUID** of the API. ",required=true) @PathParam("apiId") String apiId, @ApiParam(value = "Validator for conditional requests; based on ETag. " )@HeaderParam("If-Match") String ifMatch, @Multipart(value = "sequence", required = false) InputStream sequenceInputStream, @Multipart(value = "sequence" , required = false) Attachment sequenceDetail, @Multipart(value = "type", required = false) String type, @Multipart(value = "apiData", required = false) String apiData) throws APIManagementException{
return delegate.customSequenceUpdate(apiId, ifMatch, sequenceInputStream, sequenceDetail, type, apiData, securityContext);
}

@DELETE
@Path("/{apiId}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public interface ApisApiService {
public Response createAPI(APIDTO APIDTO, String openAPIVersion, MessageContext messageContext) throws APIManagementException;
public Response createAPIRevision(String apiId, APIRevisionDTO apIRevisionDTO, MessageContext messageContext) throws APIManagementException;
public Response createNewAPIVersion(String newVersion, String apiId, Boolean defaultVersion, String serviceVersion, MessageContext messageContext) throws APIManagementException;
public Response customSequenceUpdate(String apiId, String ifMatch, InputStream sequenceInputStream, Attachment sequenceDetail, String type, String apiData, MessageContext messageContext) throws APIManagementException;
public Response deleteAPI(String apiId, String ifMatch, MessageContext messageContext) throws APIManagementException;
public Response deleteAPIClientCertificateByAlias(String alias, String apiId, MessageContext messageContext) throws APIManagementException;
public Response deleteAPIDocument(String apiId, String documentId, String ifMatch, MessageContext messageContext) throws APIManagementException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import com.amazonaws.SdkClientException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
Expand Down Expand Up @@ -199,6 +203,31 @@ public Response getAPI(String apiId, String xWSO2Tenant, String ifNoneMatch,
return Response.ok().entity(apiToReturn).build();
}

@Override
public Response customSequenceUpdate(String apiId, String ifMatch, InputStream sequenceInputStream,
Attachment sequenceDetail, String type, String apiData, MessageContext messageContext)
throws APIManagementException {
APIDTO apidto = new Gson().fromJson(apiData, APIDTO.class);
// Get the endpoint config object updated
APIUtil.validateAPIEndpointConfig(apidto.getEndpointConfig(), apidto.getType().toString(),
apidto.getName());
if (apidto.getEndpointConfig() != null) {
org.json.JSONObject endpointConfig = new org.json.JSONObject(new Gson().toJson(apidto.getEndpointConfig()));
if (APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointConfig.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))) {
try {
String content = ImportUtils.retrieveXMLContent(sequenceInputStream);
endpointConfig.put("sequence", content);
} catch (IOException ex) {
RestApiUtil.handleInternalServerError("Failed to read Custom Sequence of " + apiId, ex, log);
}
}
apidto.setEndpointConfig(new Gson().fromJson(endpointConfig.toString(), Object.class));
}
updateAPI(apiId, apidto, ifMatch, messageContext);
return Response.ok().entity(null).build();
}

@Override
public Response addCommentToAPI(String apiId, PostRequestBodyDTO postRequestBodyDTO, String replyTo, MessageContext
messageContext) throws APIManagementException {
Expand Down Expand Up @@ -3764,6 +3793,14 @@ public Response deployAPIRevision(String apiId, String revisionId,

//validate whether the API is advertise only
APIDTO apiDto = getAPIByID(apiId, apiProvider, organization);

// Cannot deploy an API with custom sequence to the APK gateway
Map endpointConfigMap = (Map) apiDto.getEndpointConfig();
if (APIConstants.WSO2_APK_GATEWAY.equals(apiDto.getGatewayType()) && APIConstants.ENDPOINT_TYPE_SEQUENCE.equals(
endpointConfigMap.get(APIConstants.API_ENDPOINT_CONFIG_PROTOCOL_TYPE))) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Cannot Deploy an API with a Custom Sequence to APK Gateway: " + apiId).build();
}
// Reject the request if API lifecycle is 'RETIRED'.
if (apiDto.getLifeCycleStatus().equals(APIConstants.RETIRED)) {
return Response.status(Response.Status.BAD_REQUEST).entity("Deploying API Revisions is not supported for retired APIs. ApiId: "
Expand Down

0 comments on commit a9442fd

Please sign in to comment.