diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java index 6bd53129acaa..a83c24d9542a 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java @@ -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, diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java index 58674292037b..1c544562761d 100755 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java @@ -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"; diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java index 8d18c7c9f486..23babe28d8da 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java @@ -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 * diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/SynapseArtifactGenerator.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/SynapseArtifactGenerator.java index 8851aeb1733f..c066efddd2f0 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/SynapseArtifactGenerator.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/SynapseArtifactGenerator.java @@ -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; @@ -106,22 +105,17 @@ public RuntimeArtifactDto generateGatewayArtifact(List 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())) { @@ -145,7 +139,7 @@ public RuntimeArtifactDto generateGatewayArtifact(List 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() diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java index 29e034b09d81..9b9b87acd031 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java @@ -103,6 +103,7 @@ 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; @@ -110,8 +111,8 @@ 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; @@ -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); @@ -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(); } /** diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java index 1fba20fff132..b6b7e7e88e4d 100755 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java @@ -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; @@ -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; @@ -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)) { @@ -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)) { diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java index ed7d69ed0f06..3d93c922ee17 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java @@ -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}") diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java index 10f13b24633d..20d072ceb0ce 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java @@ -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; diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java index 3d4e3ba1e684..f193d312e5f9 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java @@ -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; @@ -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 { @@ -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: "