From 2bc31ee0fbecd761e5265d20a24f706111335845 Mon Sep 17 00:00:00 2001 From: Chamila Adhikarinayake Date: Thu, 30 May 2024 16:45:28 +0530 Subject: [PATCH] Support import/export SOAPToREST APIs with custom mediation policies --- .../wso2/carbon/apimgt/api/APIProvider.java | 10 +++ .../carbon/apimgt/impl/APIProviderImpl.java | 10 +++ .../apimgt/persistence/APIPersistence.java | 11 +++ .../persistence/RegistryPersistenceImpl.java | 74 ++++++++++++++++++- .../v1/common/mappings/ExportUtils.java | 10 +-- .../v1/common/mappings/ImportUtils.java | 63 +++++++++++++++- .../common/mappings/PublisherCommonUtils.java | 51 +++++++++++-- 7 files changed, 215 insertions(+), 14 deletions(-) diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIProvider.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIProvider.java index c7e9ac392513..7d497f72f01a 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIProvider.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIProvider.java @@ -1908,4 +1908,14 @@ boolean isPolicyMetadataExists(String gatewayPolicyMappingId) * @throws APIManagementException */ int getPolicyUsageByPolicyUUIDInGatewayPolicies(String commonPolicyUUID) throws APIManagementException; + + /** + * Update SoapToRest Sequences for the given API. + * @param organization Organization + * @param apiId API ID + * @param sequences list of SOAPToRestSequence. + * @throws APIPersistenceException + */ + void updateSoapToRestSequences(String organization, String apiId, List sequences) + throws APIManagementException; } diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIProviderImpl.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIProviderImpl.java index 3025e4581cfe..dfb36d676a91 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIProviderImpl.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIProviderImpl.java @@ -7255,5 +7255,15 @@ private Map> getGatewayPolicyDeploymentMap(List sequences) + throws APIManagementException { + Organization org = new Organization(organization); + try { + apiPersistenceInstance.updateSoapToRestSequences(org, apiId, sequences); + } catch (APIPersistenceException e) { + throw new APIManagementException("Error while sequences to the api " + apiId, e); + } } } diff --git a/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/APIPersistence.java b/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/APIPersistence.java index 33a198506b4e..63592cd946d8 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/APIPersistence.java +++ b/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/APIPersistence.java @@ -17,6 +17,7 @@ package org.wso2.carbon.apimgt.persistence; import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.api.model.SOAPToRestSequence; import org.wso2.carbon.apimgt.api.model.Tag; import org.wso2.carbon.apimgt.persistence.dto.AdminContentSearchResult; import org.wso2.carbon.apimgt.persistence.dto.DevPortalAPI; @@ -519,6 +520,16 @@ PublisherAPIProductSearchResult searchAPIProductsForPublisher(Organization org, * @return list of all the tags of an organization */ Set getAllTags(Organization org, UserContext ctx) throws APIPersistenceException; + + /** + * Update SoapToRest Sequences for the given API. + * @param org Organization the API product is owned by + * @param apiId API ID + * @param sequences list of SOAPToRestSequence. + * @throws APIPersistenceException + */ + void updateSoapToRestSequences(Organization org, String apiId, List sequences) + throws APIPersistenceException; void changeApiProvider(String providerName, String apiId, String org) throws APIManagementException, APIPersistenceException; diff --git a/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/RegistryPersistenceImpl.java b/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/RegistryPersistenceImpl.java index 9fc4d90ddfca..8530ce4dd70a 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/RegistryPersistenceImpl.java +++ b/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/RegistryPersistenceImpl.java @@ -30,7 +30,6 @@ import org.wso2.carbon.apimgt.api.APIMgtResourceNotFoundException; import org.wso2.carbon.apimgt.api.ExceptionCodes; import org.wso2.carbon.apimgt.api.model.*; -import org.wso2.carbon.apimgt.api.model.Tag; import org.wso2.carbon.apimgt.api.model.SOAPToRestSequence.Direction; import org.wso2.carbon.apimgt.persistence.dto.*; import org.wso2.carbon.apimgt.persistence.dto.Documentation; @@ -3920,4 +3919,77 @@ public AdminContentSearchResult searchContentForAdmin(String org, String searchQ return result; } + public void updateSoapToRestSequences(Organization org, String apiId, List sequences) + throws APIPersistenceException { + + boolean transactionCommitted = false; + boolean tenantFlowStarted = false; + Registry registry = null; + try { + RegistryHolder holder = getRegistry(org.getName()); + registry = holder.getRegistry(); + tenantFlowStarted = holder.isTenantFlowStarted(); + registry.beginTransaction(); + GenericArtifact artifact = getAPIArtifact(apiId, registry); + + for (SOAPToRestSequence soapToRestSequence : sequences) { + + String apiResourceName = soapToRestSequence.getPath(); + if (apiResourceName.startsWith("/")) { + apiResourceName = apiResourceName.substring(1); + } + String resourcePath = APIConstants.API_ROOT_LOCATION + RegistryConstants.PATH_SEPARATOR + + RegistryPersistenceUtil + .replaceEmailDomain(artifact.getAttribute(APIConstants.API_OVERVIEW_PROVIDER)) + + RegistryConstants.PATH_SEPARATOR + artifact.getAttribute(APIConstants.API_OVERVIEW_NAME) + + RegistryConstants.PATH_SEPARATOR + artifact.getAttribute(APIConstants.API_OVERVIEW_VERSION) + + RegistryConstants.PATH_SEPARATOR; + if (soapToRestSequence.getDirection() == Direction.OUT) { + resourcePath = resourcePath + "soap_to_rest" + RegistryConstants.PATH_SEPARATOR + "out" + + RegistryConstants.PATH_SEPARATOR; + } else { + resourcePath = resourcePath + "soap_to_rest" + RegistryConstants.PATH_SEPARATOR + "in" + + RegistryConstants.PATH_SEPARATOR; + } + resourcePath = resourcePath + apiResourceName + "_" + soapToRestSequence.getMethod() + ".xml"; + + Resource regResource; + if (!registry.resourceExists(resourcePath)) { + regResource = registry.newResource(); + } else { + regResource = registry.get(resourcePath); + } + regResource.setContent(soapToRestSequence.getContent()); + regResource.addProperty("method", soapToRestSequence.getMethod()); + if (regResource.getProperty("resourcePath") != null) { + regResource.removeProperty("resourcePath"); + } + regResource.addProperty("resourcePath", apiResourceName); + regResource.setMediaType("text/xml"); + registry.put(resourcePath, regResource); + } + registry.commitTransaction(); + transactionCommitted = true; + } catch (Exception e) { + try { + registry.rollbackTransaction(); + } catch (RegistryException re) { + // Throwing an error from this level will mask the original exception + log.error("Error while rolling back the transaction for API: " + apiId, re); + } + throw new APIPersistenceException("Error while performing registry transaction operation ", e); + } finally { + if (tenantFlowStarted) { + RegistryPersistenceUtil.endTenantFlow(); + } + try { + if (registry != null && !transactionCommitted) { + registry.rollbackTransaction(); + } + } catch (RegistryException ex) { + log.error("Error occurred while rolling back the transaction.", ex); + } + } + + } } 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/ExportUtils.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/ExportUtils.java index 4e0ddd56dbcb..488e9179ee6c 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/ExportUtils.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/ExportUtils.java @@ -194,11 +194,11 @@ public static File exportApi(APIProvider apiProvider, APIIdentifier apiIdentifie addThumbnailToArchive(archivePath, apiIdentifier, apiProvider); addDocumentationToArchive(archivePath, apiIdentifier, exportFormat, apiProvider, APIConstants.API_IDENTIFIER_TYPE); - } else { - if (StringUtils.equals(apiDtoToReturn.getType().toString().toLowerCase(), - APIConstants.API_TYPE_SOAPTOREST.toLowerCase())) { - addSOAPToRESTMediationToArchive(archivePath, api); - } + } + + if (StringUtils.equals(apiDtoToReturn.getType().toString().toLowerCase(), + APIConstants.API_TYPE_SOAPTOREST.toLowerCase())) { + addSOAPToRESTMediationToArchive(archivePath, api); } if (StringUtils 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 44d3d40ae0e7..06d464bbc705 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 @@ -59,6 +59,8 @@ import org.wso2.carbon.apimgt.api.model.OperationPolicyData; import org.wso2.carbon.apimgt.api.model.OperationPolicyDefinition; import org.wso2.carbon.apimgt.api.model.OperationPolicySpecification; +import org.wso2.carbon.apimgt.api.model.SOAPToRestSequence; +import org.wso2.carbon.apimgt.api.model.SOAPToRestSequence.Direction; import org.wso2.carbon.apimgt.api.model.Scope; import org.wso2.carbon.apimgt.api.model.URITemplate; import org.wso2.carbon.apimgt.api.model.graphql.queryanalysis.GraphqlComplexityInfo; @@ -330,7 +332,7 @@ public static ImportedAPIDTO importApi(String extractedFolderPath, APIDTO import } importedApi = PublisherCommonUtils.updateApiAndDefinition(targetApi, importedApiDTO, RestApiCommonUtil.getLoggedInUserProvider(), tokenScopes, - validationResponse); + validationResponse, false); } else { if (targetApi == null && Boolean.TRUE.equals(overwrite)) { log.info("Cannot find : " + importedApiDTO.getName() + "-" + importedApiDTO.getVersion() @@ -348,7 +350,7 @@ public static ImportedAPIDTO importApi(String extractedFolderPath, APIDTO import && !APIConstants.APITransportType.GRAPHQL.toString().equalsIgnoreCase(apiType)) { // Add the validated swagger separately since the UI does the same procedure PublisherCommonUtils.updateSwagger(importedApi.getUuid(), validationResponse, false, - organization); + organization, false); importedApi = apiProvider.getAPIbyUUID(importedApi.getUuid(), currentTenantDomain); } } else { @@ -400,7 +402,11 @@ public static ImportedAPIDTO importApi(String extractedFolderPath, APIDTO import } if (StringUtils .equals(importedApi.getType().toLowerCase(), APIConstants.API_TYPE_SOAPTOREST.toLowerCase())) { - addSOAPToREST(importedApi, validationResponse.getContent(), apiProvider); + List sequences = getSOAPToRESTSequences(extractedFolderPath); + if (sequences != null && !sequences.isEmpty()) { + String tenantDomain = RestApiCommonUtil.getLoggedInUserTenantDomain(); + apiProvider.updateSoapToRestSequences(tenantDomain, importedApi.getUuid(), sequences); + } } if (!isAdvertiseOnlyAPI(importedApiDTO)) { @@ -2321,7 +2327,58 @@ private static void addSOAPToREST(API importedApi, String swaggerContent, APIPro PublisherCommonUtils .updateAPIBySettingGenerateSequencesFromSwagger(swaggerContent, importedApi, apiProvider, tenantDomain); } + + /** + * This method retrieve soap to rest sequences from the exported zip file. + * + * @param extractedFolderPath folder path. + * @return List list of soap to rest sequences + * @throws APIManagementException + */ + private static List getSOAPToRESTSequences(String extractedFolderPath) + throws APIManagementException { + + List list = new ArrayList(); + try { + String folderName = extractedFolderPath + File.separator + SOAPTOREST; + String[] directions = { IN, OUT }; + if (APIUtil.checkFileExistence(folderName)) { + for (int i = 0; i < directions.length; i++) { + String sequenceFolderName = folderName + File.separator + directions[i]; + if (APIUtil.checkFileExistence(sequenceFolderName)) { + File sequenceFolder = new File(sequenceFolderName); + File[] listOfFiles = sequenceFolder.listFiles(); + if (listOfFiles != null) { + for (File file : listOfFiles) { + if (file.isFile() && file.getName().endsWith(".xml")) { + if (log.isDebugEnabled()) { + log.debug("Found sequence " + file.getName() + " in " + sequenceFolder); + } + String operation = file.getName().replace(".xml", ""); + // Find the last underscore in the string + int lastUnderscoreIndex = operation.lastIndexOf('_'); + // Split the string from the last underscore + //String path = "/" + operation.substring(0, lastUnderscoreIndex); + String path = operation.substring(0, lastUnderscoreIndex); + String method = operation.substring(lastUnderscoreIndex + 1); + String content = FileUtils.readFileToString(file); + Direction direction = IN.equals(directions[i]) ? Direction.IN : Direction.OUT; + SOAPToRestSequence seq = new SOAPToRestSequence(method, path, content, direction); + list.add(seq); + } + } + } + } + } + } + } catch (IOException e) { + throw new APIManagementException("Error while reading sequences from path: " + extractedFolderPath, e, + ExceptionCodes.ERROR_READING_META_DATA); + } + return list; + } + public static List retrieveSoapToRestFlowMediations(String pathToArchive, String type) throws APIManagementException { 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 b24d7ff31113..f0c2d69f7ecf 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 @@ -133,12 +133,34 @@ public static API updateApiAndDefinition(API originalAPI, APIDTO apiDtoToUpdate, String[] tokenScopes, APIDefinitionValidationResponse response) throws APIManagementException, ParseException, CryptoException, FaultGatewaysException { + return updateApiAndDefinition(originalAPI, apiDtoToUpdate, apiProvider, tokenScopes, response, true); + } + + /** + * Update API and API definition. Soap to rest sequence is updated on demand. + * + * @param originalAPI existing API + * @param apiDtoToUpdate DTO object with updated API data + * @param apiProvider API Provider + * @param tokenScopes token scopes + * @param generateSoapToRestSequences Option to generate soap to rest sequences. + * @param response response of the API definition validation + * @return updated API + * @throws APIManagementException If an error occurs while updating the API and API definition + * @throws ParseException If an error occurs while parsing the endpoint configuration + * @throws CryptoException If an error occurs while encrypting the secret key of API + * @throws FaultGatewaysException If an error occurs while updating manage of an existing API + */ + public static API updateApiAndDefinition(API originalAPI, APIDTO apiDtoToUpdate, APIProvider apiProvider, + String[] tokenScopes, APIDefinitionValidationResponse response, boolean generateSoapToRestSequences) + throws APIManagementException, ParseException, CryptoException, FaultGatewaysException { + API apiToUpdate = prepareForUpdateApi(originalAPI, apiDtoToUpdate, apiProvider, tokenScopes); String organization = RestApiCommonUtil.getLoggedInUserTenantDomain(); if (!PublisherCommonUtils.isStreamingAPI(apiDtoToUpdate) && !APIConstants.APITransportType.GRAPHQL.toString() .equalsIgnoreCase(apiDtoToUpdate.getType().toString())) { prepareForUpdateSwagger(originalAPI.getUuid(), response, false, apiProvider, organization, - response.getParser(), apiToUpdate); + response.getParser(), apiToUpdate, generateSoapToRestSequences); } apiProvider.updateAPI(apiToUpdate, originalAPI); return apiProvider.getAPIbyUUID(originalAPI.getUuid(), originalAPI.getOrganization()); @@ -1405,11 +1427,30 @@ public static String updateSwagger(String apiId, APIDefinitionValidationResponse String organization) throws APIManagementException, FaultGatewaysException { + return updateSwagger(apiId, response, isServiceAPI, organization, true); + } + + /** + * update swagger definition of the given api. For Soap To Rest APIs, sequences are generated on demand. + * + * @param apiId API Id + * @param response response of a swagger definition validation call + * @param organization Organization Identifier + * @param generateSoapToRestSequences Option to generate soap to rest sequences. + * @return updated swagger definition + * @throws APIManagementException when error occurred updating swagger + * @throws FaultGatewaysException when error occurred publishing API to the gateway + */ + public static String updateSwagger(String apiId, APIDefinitionValidationResponse response, boolean isServiceAPI, + String organization, boolean generateSoapToRestSequences) + throws APIManagementException, FaultGatewaysException { + APIProvider apiProvider = RestApiCommonUtil.getLoggedInUserProvider(); //this will fail if user does not have access to the API or the API does not exist API existingAPI = apiProvider.getAPIbyUUID(apiId, organization); APIDefinition oasParser = response.getParser(); - prepareForUpdateSwagger(apiId, response, isServiceAPI, apiProvider, organization, oasParser, existingAPI); + prepareForUpdateSwagger(apiId, response, isServiceAPI, apiProvider, organization, oasParser, existingAPI, + generateSoapToRestSequences); //Update API is called to update URITemplates and scopes of the API API unModifiedAPI = apiProvider.getAPIbyUUID(apiId, organization); @@ -1418,7 +1459,7 @@ public static String updateSwagger(String apiId, APIDefinitionValidationResponse //retrieves the updated swagger definition String apiSwagger = apiProvider.getOpenAPIDefinition(apiId, organization); // TODO see why we need to get it - // instead of passing same + //instead of passing same return oasParser.getOASDefinitionForPublisher(existingAPI, apiSwagger); } @@ -1436,7 +1477,7 @@ public static String updateSwagger(String apiId, APIDefinitionValidationResponse */ private static void prepareForUpdateSwagger(String apiId, APIDefinitionValidationResponse response, boolean isServiceAPI, APIProvider apiProvider, String organization, - APIDefinition oasParser, API existingAPI) + APIDefinition oasParser, API existingAPI, boolean genSoapToRestSequence) throws APIManagementException { String apiDefinition = response.getJsonContent(); @@ -1445,7 +1486,7 @@ private static void prepareForUpdateSwagger(String apiId, APIDefinitionValidatio } else { apiDefinition = OASParserUtil.preProcess(apiDefinition); } - if (APIConstants.API_TYPE_SOAPTOREST.equals(existingAPI.getType())) { + if (APIConstants.API_TYPE_SOAPTOREST.equals(existingAPI.getType()) && genSoapToRestSequence) { List sequenceList = SequenceGenerator.generateSequencesFromSwagger(apiDefinition); existingAPI.setSoapToRestSequences(sequenceList); }