From d2710f6ee2c984d7426e54f8e323a023aecfdcd5 Mon Sep 17 00:00:00 2001 From: hiranyakavishani Date: Thu, 28 Sep 2023 00:19:48 +0530 Subject: [PATCH] Adding versioning support for APIProducts --- .../wso2/carbon/apimgt/api/APIManager.java | 15 + .../wso2/carbon/apimgt/api/APIProvider.java | 13 + .../carbon/apimgt/api/ExceptionCodes.java | 1 + .../carbon/apimgt/api/model/APIProduct.java | 20 +- .../api/model/APIProductIdentifier.java | 6 +- .../apimgt/api/model/ApiTypeWrapper.java | 32 +- .../wso2/carbon/apimgt/impl/APIConstants.java | 4 +- .../carbon/apimgt/impl/APIConsumerImpl.java | 3 +- .../carbon/apimgt/impl/APIProviderImpl.java | 191 +++++- .../apimgt/impl/AbstractAPIManager.java | 10 +- .../carbon/apimgt/impl/dao/ApiMgtDAO.java | 647 ++++++++++++++---- .../impl/dao/SubscriptionValidationDAO.java | 90 ++- .../impl/dao/constants/SQLConstants.java | 35 +- .../SubscriptionValidationSQLConstants.java | 28 +- .../utils/APIProductVersionComparator.java | 61 ++ .../apimgt/impl/utils/LifeCycleUtils.java | 267 ++++++-- .../apimgt/impl/APIProviderImplTest.java | 2 +- .../apimgt/impl/dao/test/APIMgtDAOTest.java | 32 +- .../swagger.json | 4 +- .../persistence/RegistryPersistenceImpl.java | 16 +- .../persistence/mapper/APIProductMapper.java | 1 + .../api/publisher/v1/dto/APIProductDTO.java | 42 +- .../publisher/v1/dto/APIProductInfoDTO.java | 22 +- .../v1/common/mappings/APIMappingUtil.java | 43 +- .../common/mappings/PublisherCommonUtils.java | 99 ++- .../v1/common/template/APIConfigContext.java | 4 +- .../mappings/PublisherCommonUtilsTest.java | 200 ++++++ .../rest/api/publisher/v1/ApiProductsApi.java | 18 + .../publisher/v1/ApiProductsApiService.java | 1 + .../v1/impl/ApiProductsApiServiceImpl.java | 58 +- .../src/main/resources/publisher-api.yaml | 66 ++ .../api/store/v1/mappings/APIMappingUtil.java | 1 + 32 files changed, 1730 insertions(+), 302 deletions(-) create mode 100644 components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIProductVersionComparator.java diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIManager.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIManager.java index d15659ff54fc..94ed069bbc9c 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIManager.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/APIManager.java @@ -120,6 +120,21 @@ public interface APIManager { */ boolean isContextExist(String context, String organization) throws APIManagementException; + + /** + * Checks whether the given API Product context is already registered in the system for API products + * + * @param context A String representing an API product context + * @param contextWithVersion A String representing an API context appended with the version + * @param organization Organization + * @return true if the context already exists and false otherwise + * @throws APIManagementException if failed to check the context availability + */ + boolean isContextExistForAPIProducts(String context, String contextWithVersion, String organization) + throws APIManagementException; + + /** + /** * Checks whether the given API name is already registered in the system * 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 8ef6014c8f03..8f5e94335250 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 @@ -328,6 +328,19 @@ List getSubscriptionsOfAPI(String apiName, String apiVersion, Str API createNewAPIVersion(String apiId, String newVersion, Boolean defaultVersion, String organization) throws APIManagementException; + /** + * Create a new version of the apiProduct, with version newVersion + * + * @param apiProductId The id of the API Product to be copied + * @param newVersion The version of the new API Product + * @param defaultVersion whether this version is default or not + * @param organization Identifier of an organization + * @return apiProduct created apiProduct + * @throws APIManagementException If an error occurs while trying to create + * * the new version of the API Product + */ + APIProduct createNewAPIProductVersion(String apiProductId, String newVersion, Boolean defaultVersion, + String organization) throws APIManagementException; /** * Retrieve the Key of the Service used in the API * @param apiId Unique Identifier of the API 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 a387f492fc3e..f6774f033805 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 @@ -34,6 +34,7 @@ public enum ExceptionCodes implements ErrorHandler { API_VERSION_ALREADY_EXISTS(900252, "The API version already exists.", 409, "An API with version '%s' already exists for API '%s'"), API_PRODUCT_CONTEXT_ALREADY_EXISTS(900275, "The API Product context already exists.", 409, "An API Product with context '%s' already exists"), + API_PRODUCT_VERSION_ALREADY_EXISTS(900276, "The API Product version already exists.", 409, "An API Product with version '%s' already exists for API Product '%s'"), API_CONTEXT_MALFORMED_EXCEPTION(900253, "The API context is malformed.", 400, "'%s'"), API_ALREADY_EXISTS(900300, "The API already exists.", 409, "The API already exists"), diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProduct.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProduct.java index dac2ef0e7fb8..12a1de214316 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProduct.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProduct.java @@ -17,6 +17,7 @@ */ package org.wso2.carbon.apimgt.api.model; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -29,7 +30,7 @@ import org.apache.commons.lang3.StringUtils; import org.json.simple.JSONObject; -public class APIProduct { +public class APIProduct implements Serializable { // TODO add rest of the properties private APIProductIdentifier id; private String uuid; @@ -131,7 +132,8 @@ public class APIProduct { * Used to set the workflow status in lifecycle state change workflow */ private String workflowStatus = null; - + private Boolean isDefaultVersion = true; + private boolean isPublishedDefaultVersion = false; public APIProduct(){} public APIProduct(APIProductIdentifier id) { @@ -194,6 +196,20 @@ public String getTechnicalOwnerEmail() { public void setTechnicalOwnerEmail(String technicalOwnerEmail) { this.technicalOwnerEmail = technicalOwnerEmail; } + public void setDefaultVersion(Boolean isDefaultVersion) { + this.isDefaultVersion = isDefaultVersion; + } + public void setAsPublishedDefaultVersion(boolean value) { + isPublishedDefaultVersion = value; + } + + public Boolean isDefaultVersion() { + return isDefaultVersion; + } + + public Boolean isPublishedDefaultVersion() { + return isPublishedDefaultVersion; + } public String getType() { return type; diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProductIdentifier.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProductIdentifier.java index df464037b37f..1d9f24631914 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProductIdentifier.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/APIProductIdentifier.java @@ -44,14 +44,14 @@ public APIProductIdentifier(String providerName, String apiProductName, String v this.apiProductName = apiProductName; this.providerName = providerName; - this.version = "1.0.0"; + this.version = version == null ? "1.0.0" : version; } public APIProductIdentifier(String providerName, String apiProductName, String version, String uuid) { this.apiProductName = apiProductName; this.providerName = providerName; - this.version = "1.0.0"; + this.version = version == null ? "1.0.0" : version; this.uuid = uuid; } @@ -122,7 +122,7 @@ public int hashCode() { @Override public String toString() { - return this.getProviderName() + '-' + this.getName(); + return this.getProviderName() + '-' + this.getName() + '-' + this.getVersion(); } @Override diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/ApiTypeWrapper.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/ApiTypeWrapper.java index 3726ada21e6c..efb7339ac891 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/ApiTypeWrapper.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/ApiTypeWrapper.java @@ -77,6 +77,22 @@ public void setContext(String context) { } } + public void setAsDefaultVersion(boolean value) { + if (isAPIProduct) { + apiProduct.setDefaultVersion(value); + } else { + api.setDefaultVersion(value); + } + } + + public void setAsPublishedDefaultVersion(boolean value) { + if (isAPIProduct) { + apiProduct.setAsPublishedDefaultVersion(value); + } else { + api.setAsPublishedDefaultVersion(value); + } + } + public String getContext() { return isAPIProduct ? apiProduct.getContext() : api.getContext(); } @@ -160,10 +176,24 @@ public String getAccessControlRoles() { return api.getAccessControlRoles(); } - public String geType() { + public String getType() { if (isAPIProduct){ return apiProduct.getType(); } return api.getType(); } + + public boolean getPublishedDefaultVersion() { + if (isAPIProduct){ + return apiProduct.isPublishedDefaultVersion(); + } + return api.isPublishedDefaultVersion(); + } + + public boolean getDefaultVersion() { + if (isAPIProduct){ + return apiProduct.isDefaultVersion(); + } + return api.isDefaultVersion(); + } } 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 064bd6409c6b..047139c8c59a 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 @@ -1740,7 +1740,9 @@ public enum RegistryResourceTypesForUI { public static final String API_LC_ACTION_DEPRECATE = "Deprecate"; public static final String DEPRECATE_CHECK_LIST_ITEM = "Deprecate old versions after publishing the API"; + public static final String DEPRECATE_CHECK_LIST_ITEM_API_PRODUCT = "Deprecate old versions after publishing the API Product"; public static final String RESUBSCRIBE_CHECK_LIST_ITEM = "Requires re-subscription when publishing the API"; + public static final String RESUBSCRIBE_CHECK_LIST_ITEM_API_PRODUCT = "Requires re-subscription when publishing the API Product"; public static final String PUBLISH_IN_PRIVATE_JET_MODE = "Publish In Private-Jet Mode"; public static final String METRICS_PREFIX = "org.wso2.am"; @@ -1913,7 +1915,7 @@ public enum RegistryResourceTypesForUI { public static final String ENABLE_STORE = "enableStore"; //api-product related constants - public static final String API_PRODUCT_VERSION = "1.0.0"; + public static final String API_PRODUCT_VERSION_1_0_0 = "1.0.0"; public static final String API_IDENTIFIER_TYPE = "API"; public static final String API_PRODUCT_IDENTIFIER_TYPE = "API Product"; public static final String[] API_SUPPORTED_TYPE_LIST = {"HTTP", "WS", "SOAPTOREST", "GRAPHQL", "SOAP", "WEBSUB", diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConsumerImpl.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConsumerImpl.java index e816b2f541db..ebf369de9669 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConsumerImpl.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConsumerImpl.java @@ -3636,6 +3636,7 @@ public ApiTypeWrapper getAPIorAPIProductByUUID(String uuid, String organization) apiProduct.setID(new APIProductIdentifier(devPortalApi.getProviderName(), devPortalApi.getApiName(), devPortalApi.getVersion())); populateAPIProductInformation(uuid, organization, apiProduct); + populateDefaultVersion(apiProduct); populateAPIStatus(apiProduct); apiProduct = addTiersToAPI(apiProduct, organization); return new ApiTypeWrapper(apiProduct); @@ -3816,7 +3817,7 @@ private ApiTypeWrapper getAPIorAPIProductByUUIDWithoutPermissionCheck(String uui apiProduct.setID(new APIProductIdentifier(devPortalApi.getProviderName(), devPortalApi.getApiName(), devPortalApi.getVersion())); populateAPIProductInformation(uuid, organization, apiProduct); - + populateDefaultVersion(apiProduct); return new ApiTypeWrapper(apiProduct); } else { API api = APIMapper.INSTANCE.toApi(devPortalApi); 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 d819b8337f6b..1f0c9c876d72 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 @@ -25,8 +25,10 @@ import org.apache.axiom.om.util.AXIOMUtil; import org.apache.axis2.Constants; import org.apache.axis2.util.JavaUtils; +import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -95,6 +97,7 @@ import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.databridge.commons.Event; import org.wso2.carbon.governance.custom.lifecycles.checklist.util.CheckListItem; +import org.wso2.carbon.identity.application.common.model.IdentityProvider; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreManager; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; @@ -104,6 +107,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -738,31 +742,36 @@ public boolean hasValidLength(String field, int maxLength) { return field.length() <= maxLength; } - private String getDefaultVersion(APIIdentifier apiid) throws APIManagementException { + private String getDefaultVersion(Identifier apiid) throws APIManagementException { String defaultVersion = null; try { defaultVersion = apiMgtDAO.getDefaultVersion(apiid); } catch (APIManagementException e) { - handleException("Error while getting default version :" + apiid.getApiName(), e); + handleException("Error while getting default version :" + apiid.getName(), e); } return defaultVersion; } - public String getPublishedDefaultVersion(APIIdentifier apiid) throws APIManagementException { + public String getPublishedDefaultVersion(Identifier apiId) throws APIManagementException { String defaultVersion = null; try { - defaultVersion = apiMgtDAO.getPublishedDefaultVersion(apiid); + if (apiId instanceof APIIdentifier) { + defaultVersion = apiMgtDAO.getPublishedDefaultVersion((APIIdentifier) apiId); + } else if (apiId instanceof APIProductIdentifier) { + defaultVersion = apiMgtDAO.getPublishedDefaultVersion((APIProductIdentifier) apiId); + } } catch (APIManagementException e) { - handleException("Error while getting published default version :" + apiid.getApiName(), e); + handleException("Error while getting published default version :" + apiId.getName(), e); } return defaultVersion; } - private void sendUpdateEventToPreviousDefaultVersion(APIIdentifier apiIdentifier, String organization) throws APIManagementException { + private void sendUpdateEventToPreviousDefaultVersion(APIIdentifier apiIdentifier, String organization) + throws APIManagementException { API api = apiMgtDAO.getLightWeightAPIInfoByAPIIdentifier(apiIdentifier, organization); APIEvent apiEvent = new APIEvent(UUID.randomUUID().toString(), System.currentTimeMillis(), APIConstants.EventType.API_UPDATE.name(), tenantId, organization, apiIdentifier.getApiName(), @@ -1915,6 +1924,87 @@ public API createNewAPIVersion(String existingApiId, String newVersion, Boolean return getAPIbyUUID(newAPIId, organization); } + /** + * Create a new API Product version from an existing API Product + * @param existingApiProductId The id of the API Product to be copied + * @param newVersion The version of the new API Product + * @param isDefaultVersion whether this version is default or not + * @param organization Identifier of an organization + * @return APIProduct object + * @throws APIManagementException + */ + public APIProduct createNewAPIProductVersion(String existingApiProductId, String newVersion, + Boolean isDefaultVersion, String organization) throws APIManagementException { + + APIProductIdentifier apiProductIdentifier = APIUtil.getAPIProductIdentifierFromUUID(existingApiProductId); + if (apiProductIdentifier == null) { + throw new APIMgtResourceNotFoundException("Couldn't retrieve existing API Product with ID: " + + existingApiProductId, ExceptionCodes.from(ExceptionCodes.API_PRODUCT_NOT_FOUND, + existingApiProductId)); + } + + //Get all existing versions of APIProducts + Set apiProductVersions = getAPIVersions(apiProductIdentifier.getProviderName(), + apiProductIdentifier.getName(), organization); + + if (apiProductVersions.contains(newVersion)) { + throw new APIMgtResourceAlreadyExistsException( + "Version " + newVersion + " exists for API product " + apiProductIdentifier.getName(), + ExceptionCodes.from(ExceptionCodes.API_PRODUCT_VERSION_ALREADY_EXISTS, newVersion, + apiProductIdentifier.getName())); + } + + APIProduct existingAPIProduct = getAPIProductbyUUID(existingApiProductId, organization); + + APIProduct clonedAPIProduct = cloneExistingAPIProduct(existingAPIProduct); + clonedAPIProduct.setOrganization(organization); + APIProductIdentifier newApiProductId = new APIProductIdentifier( + clonedAPIProduct.getId().getProviderName(), clonedAPIProduct.getId().getName(), newVersion); + clonedAPIProduct.setID(newApiProductId); + clonedAPIProduct.setUuid(null); + clonedAPIProduct.setState(APIConstants.CREATED); + clonedAPIProduct.setDefaultVersion(isDefaultVersion); + clonedAPIProduct.setVersionTimestamp(""); + clonedAPIProduct.setContext(clonedAPIProduct.getContextTemplate().replace("{version}", newVersion)); + + //Add new version of the API Product + Map> apiToProductResourceMapping = addAPIProductWithoutPublishingToGateway( + clonedAPIProduct); + + APIProduct createdApiProduct = getAPIProduct(newApiProductId); + String newAPIProductUUId = createdApiProduct.getUuid(); + + // add swagger + addAPIProductSwagger(newAPIProductUUId, apiToProductResourceMapping, createdApiProduct, organization); + + // copy docs + List existingDocs = getAllDocumentation(existingApiProductId, organization); + if (existingDocs != null) { + for (Documentation documentation : existingDocs) { + Documentation newDoc = addDocumentation(newAPIProductUUId, documentation, organization); + DocumentationContent content = getDocumentationContent(existingApiProductId, documentation.getId(), + organization); + if (content != null) { + addDocumentationContent(newAPIProductUUId, newDoc.getId(), organization, content); + } + } + } + + // copy icon + ResourceFile icon = getIcon(existingApiProductId, organization); + if (icon != null) { + setThumbnailToAPI(newAPIProductUUId, icon, organization); + } + + return getAPIProductbyUUID(newAPIProductUUId, organization); + } + + private APIProduct cloneExistingAPIProduct(APIProduct apiProduct) { + + Gson gson = new Gson(); + return gson.fromJson(gson.toJson(apiProduct), APIProduct.class); + } + private void cloneAPIPoliciesForNewAPIVersion(String oldAPIUuid, API newAPI, Map> extractedOperationPoliciesMap, List extractedAPILevelPolicies) throws APIManagementException { @@ -4094,12 +4184,32 @@ public Map> addAPIProductWithoutPublishingToGatewa String apiProductUUID = createAPIProduct(product); product.setUuid(apiProductUUID); + //If the context template ends with {version} this means that the version will be at the end of the context. + String contextTemplate = product.getContextTemplate(); + if (contextTemplate.endsWith("/" + APIConstants.VERSION_PLACEHOLDER)) { + //Remove the {version} part from the context template. + contextTemplate = contextTemplate.split(Pattern.quote("/" + APIConstants.VERSION_PLACEHOLDER))[0]; + } + product.setContextTemplate(contextTemplate); + // Add to database apiMgtDAO.addAPIProduct(product, product.getOrganization()); return apiToProductResourceMapping; } + private static void validateAPIProductContextTemplate(APIProduct product) throws APIManagementException { + String contextTemplate = product.getContextTemplate(); + + //Validate if the API Product has an unsupported context before executing the query + String invalidContext = "/" + APIConstants.VERSION_PLACEHOLDER; + if (invalidContext.equals(contextTemplate)) { + throw new APIManagementException( + "Cannot add API Product : " + product.getId() + " with unsupported context : " + + contextTemplate); + } + } + private String calculateVersionTimestamp(String provider, String name, String version, String org) throws APIManagementException { @@ -4213,6 +4323,8 @@ public Map> updateAPIProduct(APIProduct product) Map> apiToProductResourceMapping = new HashMap<>(); //validate resources and set api identifiers and resource ids to product List resources = product.getProductResources(); + String publishedDefaultVersion = getPublishedDefaultVersion(product.getId()); + String prevDefaultVersion = getDefaultVersion(product.getId()); for (APIProductResource apiProductResource : resources) { API api; APIProductIdentifier productIdentifier = apiProductResource.getProductIdentifier(); @@ -4297,10 +4409,32 @@ public Map> updateAPIProduct(APIProduct product) //todo : check whether permissions need to be updated and pass it along updateApiProductArtifact(product, true, true); + + if (product.isDefaultVersion() == null) { + product.setDefaultVersion(true); + } apiMgtDAO.updateAPIProduct(product, userNameWithoutChange); + if (publishedDefaultVersion != null && product.isPublishedDefaultVersion() && !product.getId().getVersion() + .equals(publishedDefaultVersion)) { + sendUpdateEventToPreviousDefaultVersion(product.getId().getProviderName(), product.getId().getName(), + publishedDefaultVersion); + } + APIConstants.EventAction action = null; int productId = apiMgtDAO.getAPIProductId(product.getId()); + if (product.isDefaultVersion() ^ product.getId().getVersion().equals(prevDefaultVersion)) { + action = APIConstants.EventAction.DEFAULT_VERSION; + } + if (product.isDefaultVersion() ^ product.getId().getVersion().equals(prevDefaultVersion)) { + APIEvent apiEventToNotifyDefaultVersionAPIProduct = + new APIEvent(UUID.randomUUID().toString(), System.currentTimeMillis(), + APIConstants.EventType.API_UPDATE.name(), tenantId, organization, product.getId().getName(), + productId, product.getUuid(), product.getId().getVersion(), product.getType(), product.getContext(), + APIUtil.replaceEmailDomainBack(product.getId().getProviderName()), product.getState(), action); + APIUtil.sendNotification(apiEventToNotifyDefaultVersionAPIProduct, APIConstants.NotifierType.API.name()); + } + APIEvent apiEvent = new APIEvent(UUID.randomUUID().toString(), System.currentTimeMillis(), APIConstants.EventType.API_UPDATE.name(), tenantId, organization, product.getId().getName(), productId, product.getId().getUUID(), product.getId().getVersion(), product.getType(), product.getContext(), @@ -4310,6 +4444,13 @@ public Map> updateAPIProduct(APIProduct product) return apiToProductResourceMapping; } + + private void sendUpdateEventToPreviousDefaultVersion(String providerName, String name, String publishedDefaultVersion) + throws APIManagementException { + APIIdentifier previousDefaultVersionIdentifier = new APIIdentifier(providerName, name, publishedDefaultVersion); + sendUpdateEventToPreviousDefaultVersion(previousDefaultVersionIdentifier, organization); + } + @Override public List getResourcePathsOfAPI(APIIdentifier apiId) throws APIManagementException { return apiMgtDAO.getResourcePathsOfAPI(apiId); @@ -4341,7 +4482,15 @@ private void validateApiProductInfo(APIProduct product) throws APIManagementExce handleException("API Name contains one or more illegal characters " + "( " + APIConstants.REGEX_ILLEGAL_CHARACTERS_FOR_API_METADATA + " )"); } - //version is not a mandatory field for now + String apiVersion = product.getId().getVersion(); + + if (apiVersion == null) { + handleException("API Version is required."); + } else if (containsIllegals(apiVersion)) { + handleException("API Version contains one or more illegal characters " + + "( " + APIConstants.REGEX_ILLEGAL_CHARACTERS_FOR_API_METADATA + " )"); + } + if (!hasValidLength(apiName, APIConstants.MAX_LENGTH_API_NAME) || !hasValidLength(product.getId().getVersion(), APIConstants.MAX_LENGTH_VERSION) || !hasValidLength(product.getId().getProviderName(), APIConstants.MAX_LENGTH_PROVIDER) @@ -4349,6 +4498,8 @@ private void validateApiProductInfo(APIProduct product) throws APIManagementExce throw new APIManagementException("Character length exceeds the allowable limit", ExceptionCodes.LENGTH_EXCEEDS); } + + validateAPIProductContextTemplate(product); } /** * Create an Api Product @@ -4959,6 +5110,7 @@ public APIProduct getAPIProductbyUUID(String uuid, String organization) throws A populateAPIProductInformation(uuid, organization, product); populateAPIStatus(product); populateAPITier(product); + populateDefaultVersion(product); return product; } else { String msg = "Failed to get API Product. API Product artifact corresponding to artifactId " + uuid @@ -5352,13 +5504,11 @@ public Map searchPaginatedAPIProducts(String searchQuery, String if (searchAPIs != null) { List list = searchAPIs.getPublisherAPIProductInfoList(); - List apiList = new ArrayList<>(); for (PublisherAPIProductInfo publisherAPIInfo : list) { APIProduct mappedAPI = new APIProduct(new APIProductIdentifier(publisherAPIInfo.getProviderName(), publisherAPIInfo.getApiProductName(), publisherAPIInfo.getVersion())); mappedAPI.setUuid(publisherAPIInfo.getId()); mappedAPI.setState(publisherAPIInfo.getState()); - mappedAPI.setContext(publisherAPIInfo.getContext()); mappedAPI.setApiSecurity(publisherAPIInfo.getApiSecurity()); mappedAPI.setThumbnailUrl(publisherAPIInfo.getThumbnail()); mappedAPI.setBusinessOwner(publisherAPIInfo.getBusinessOwner()); @@ -5366,6 +5516,8 @@ public Map searchPaginatedAPIProducts(String searchQuery, String mappedAPI.setTechnicalOwner(publisherAPIInfo.getTechnicalOwner()); mappedAPI.setTechnicalOwnerEmail(publisherAPIInfo.getTechnicalOwnerEmail()); mappedAPI.setMonetizationEnabled(publisherAPIInfo.getMonetizationStatus()); + mappedAPI.setContextTemplate(publisherAPIInfo.getContext()); + populateDefaultVersion(mappedAPI); populateAPIStatus(mappedAPI); productList.add(mappedAPI); } @@ -6067,8 +6219,7 @@ public void deployAPIProductRevision(String apiProductId, String apiRevisionId, } APIProduct product = getAPIProductbyUUID(apiRevisionId, tenantDomain); product.setUuid(apiProductId); - List currentApiRevisionDeploymentList = - apiMgtDAO.getAPIRevisionDeploymentsByApiUUID(apiProductId); + List currentApiRevisionDeploymentList = apiMgtDAO.getAPIRevisionDeploymentsByApiUUID(apiProductId); APIGatewayManager gatewayManager = APIGatewayManager.getInstance(); Set environmentsToAdd = new HashSet<>(); Map gatewayVhosts = new HashMap<>(); @@ -6091,10 +6242,28 @@ public void deployAPIProductRevision(String apiProductId, String apiRevisionId, .addAndRemovePublishedGatewayLabels(apiProductId, apiRevisionId, environmentsToAdd, gatewayVhosts, environmentsToRemove); apiMgtDAO.addAPIRevisionDeployment(apiRevisionId, apiRevisionDeployments); + if (environmentsToAdd.size() > 0) { gatewayManager.deployToGateway(product, tenantDomain, environmentsToAdd); } + String publishedDefaultVersion = getPublishedDefaultVersion(apiProductIdentifier); + String defaultVersion = getDefaultVersion(apiProductIdentifier); + apiMgtDAO.updateDefaultAPIPublishedVersion(apiProductIdentifier); + + if (publishedDefaultVersion != null) { + if (apiProductIdentifier.getVersion().equals(defaultVersion)) { + product.setAsPublishedDefaultVersion(true); + } else { + product.setAsPublishedDefaultVersion(false); + } + + if (product.isPublishedDefaultVersion() && !apiProductIdentifier.getVersion() + .equals(publishedDefaultVersion)) { + sendUpdateEventToPreviousDefaultVersion(product.getId().getProviderName(), product.getId().getName(), + publishedDefaultVersion); + } + } } @Override diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/AbstractAPIManager.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/AbstractAPIManager.java index 4b58f282f155..dd1bdedf325a 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/AbstractAPIManager.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/AbstractAPIManager.java @@ -171,9 +171,11 @@ protected String getTenantDomain(Identifier identifier) { } protected void populateDefaultVersion(API api) throws APIManagementException { - apiMgtDAO.setDefaultVersion(api); } + protected void populateDefaultVersion(APIProduct apiProduct) throws APIManagementException { + apiMgtDAO.setDefaultVersion(apiProduct); + } private boolean isTenantDomainNotMatching(String tenantDomain) { @@ -504,6 +506,12 @@ public boolean isContextExist(String context, String organization) throws APIMan return apiMgtDAO.isContextExist(context, organization); } + public boolean isContextExistForAPIProducts(String context, String contextWithVersion, String organization) + throws APIManagementException { + + return apiMgtDAO.isContextExistForAPIProducts(context, contextWithVersion, organization); + } + protected String getTenantDomainFromUrl(String url) { return MultitenantUtils.getTenantDomainFromUrl(url); diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/ApiMgtDAO.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/ApiMgtDAO.java index 3c804ee03f35..82589dde4348 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/ApiMgtDAO.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/ApiMgtDAO.java @@ -2077,7 +2077,7 @@ public Set getSubscribersOfAPI(APIIdentifier identifier) throws APIM * @return Set of subscribers * @throws APIManagementException if failed to get subscribers for given provider */ - public Set getSubscribersOfAPIWithoutDuplicates(APIIdentifier identifier, + public Set getSubscribersOfAPIWithoutDuplicates(Identifier identifier, Map subscriberMap) throws APIManagementException { Set subscribers = new HashSet(); @@ -2086,7 +2086,7 @@ public Set getSubscribersOfAPIWithoutDuplicates(APIIdentifier identi PreparedStatement ps = connection.prepareStatement(SQLConstants.GET_SUBSCRIBERS_OF_API_SQL);) { ps.setString(1, APIUtil.replaceEmailDomainBack(identifier.getProviderName())); - ps.setString(2, identifier.getApiName()); + ps.setString(2, identifier.getName()); ps.setString(3, identifier.getVersion()); try (ResultSet resultSet = ps.executeQuery()) { @@ -2101,7 +2101,7 @@ public Set getSubscribersOfAPIWithoutDuplicates(APIIdentifier identi } } } catch (SQLException e) { - handleException("Failed to get subscribers for :" + identifier.getApiName(), e); + handleException("Failed to get subscribers for :" + identifier.getName(), e); } return subscribers; } @@ -4983,13 +4983,19 @@ private void changeAPILifeCycleStatus(Connection connection, int apiId, String u } } - public void updateDefaultAPIPublishedVersion(APIIdentifier identifier) + public void updateDefaultAPIPublishedVersion(Identifier identifier) throws APIManagementException { try (Connection conn = APIMgtDBUtil.getConnection()) { try { conn.setAutoCommit(false); - String defaultVersion = getDefaultVersion(conn, identifier); + String defaultVersion = null; + if (identifier instanceof APIIdentifier) { + defaultVersion = getDefaultVersion(conn, (APIIdentifier) identifier); + } else if (identifier instanceof APIProductIdentifier) { + defaultVersion = getDefaultVersion(conn, (APIProductIdentifier) identifier); + } + if (identifier.getVersion().equals(defaultVersion)) { setPublishedDefVersion(identifier, conn, identifier.getVersion()); } @@ -5040,93 +5046,91 @@ public List getLifeCycleEvents(String uuid) throws APIManagement return events; } - public List makeKeysForwardCompatible(ApiTypeWrapper apiTypeWrapper, List oldAPIVersions) throws APIManagementException { + /** + * + * This method is used to copy the subscription new for new API Version + * + * @param apiTypeWrapper apiTypeWrapper + * @param oldAPIVersions oldAPIVersions + * @return List list of Subscribed APIs + * @throws APIManagementException APIManagementException + */ + public List makeKeysForwardCompatibleForNewAPIVersion(ApiTypeWrapper apiTypeWrapper, + List oldAPIVersions) throws APIManagementException { + int versionCount; List subscribedAPISet = new ArrayList<>(); + //if there are no previous versions, there is no need to copy subscriptions - if (oldAPIVersions == null || oldAPIVersions.isEmpty()) { + versionCount = getNoOfVersionsToCopySubscription(apiTypeWrapper, oldAPIVersions, null); + if (versionCount == 0) { return subscribedAPISet; } + String getSubscriptionDataQuery = SQLConstants.GET_SUBSCRIPTION_DATA_SQL.replaceAll("_API_VERSION_LIST_", - String.join(",", Collections.nCopies(oldAPIVersions.size(), "?"))); - APIIdentifier apiIdentifier = apiTypeWrapper.getApi().getId(); + String.join(",", Collections.nCopies(versionCount, "?"))); + try { // Retrieve all the existing subscription for the old version try (Connection connection = APIMgtDBUtil.getConnection()) { connection.setAutoCommit(false); try (PreparedStatement prepStmt = connection.prepareStatement(getSubscriptionDataQuery)) { - prepStmt.setString(1, APIUtil.replaceEmailDomainBack(apiIdentifier.getProviderName())); - prepStmt.setString(2, apiIdentifier.getApiName()); + prepStmt.setString(1, + APIUtil.replaceEmailDomainBack(apiTypeWrapper.getId().getProviderName())); + prepStmt.setString(2, apiTypeWrapper.getId().getName()); int index = 3; for (API oldAPI : oldAPIVersions) { prepStmt.setString(index++, oldAPI.getId().getVersion()); } - try (ResultSet rs = prepStmt.executeQuery()) { - List subscriptionData = new ArrayList(); - while (rs.next() && !(APIConstants.SubscriptionStatus.ON_HOLD.equals(rs.getString("SUB_STATUS" - )))) { - int subscriptionId = rs.getInt("SUBSCRIPTION_ID"); - String tierId = rs.getString("TIER_ID"); - int applicationId = rs.getInt("APPLICATION_ID"); - String apiVersion = rs.getString("VERSION"); - String subscriptionStatus = rs.getString("SUB_STATUS"); - SubscriptionInfo info = new SubscriptionInfo(subscriptionId, tierId, applicationId, - apiVersion, subscriptionStatus); - subscriptionData.add(info); - } - // To keep track of already added subscriptions (apps) - List addedApplications = new ArrayList<>(); - for (int i = oldAPIVersions.size() - 1; i >= 0; i--) { - API oldAPI = oldAPIVersions.get(i); - for (SubscriptionInfo info : subscriptionData) { - try { - if (info.getApiVersion().equals(oldAPI.getId().getVersion()) && - !addedApplications.contains(info.getApplicationId())) { - String subscriptionStatus; - if (APIConstants.SubscriptionStatus.BLOCKED.equalsIgnoreCase(info.getSubscriptionStatus())) { - subscriptionStatus = APIConstants.SubscriptionStatus.BLOCKED; - } else if (APIConstants.SubscriptionStatus.UNBLOCKED.equalsIgnoreCase(info.getSubscriptionStatus())) { - subscriptionStatus = APIConstants.SubscriptionStatus.UNBLOCKED; - } else if (APIConstants.SubscriptionStatus.PROD_ONLY_BLOCKED.equalsIgnoreCase(info.getSubscriptionStatus())) { - subscriptionStatus = APIConstants.SubscriptionStatus.PROD_ONLY_BLOCKED; - } else if (APIConstants.SubscriptionStatus.REJECTED.equalsIgnoreCase(info.getSubscriptionStatus())) { - subscriptionStatus = APIConstants.SubscriptionStatus.REJECTED; - } else { - subscriptionStatus = APIConstants.SubscriptionStatus.ON_HOLD; - } - apiTypeWrapper.setTier(info.getTierId()); - Application application = getLightweightApplicationById(connection, - info.getApplicationId()); - String subscriptionUUID = UUID.randomUUID().toString(); - int subscriptionId = addSubscription(connection, apiTypeWrapper, application, - subscriptionStatus, apiIdentifier.getProviderName(), subscriptionUUID); - if (subscriptionId == -1) { - String msg = - "Unable to add a new subscription for the API: " + apiIdentifier.getName() + - ":v" + apiIdentifier.getVersion(); - log.error(msg); - throw new APIManagementException(msg); - } - SubscribedAPI subscribedAPI = new SubscribedAPI(subscriptionUUID); - subscribedAPI.setApplication(application); - subscribedAPI.setTier(new Tier(info.getTierId())); - subscribedAPI.setOrganization(apiTypeWrapper.getOrganization()); - subscribedAPI.setIdentifier(apiTypeWrapper); - subscribedAPI.setSubStatus(subscriptionStatus); - subscribedAPI.setSubscriptionId(subscriptionId); - addedApplications.add(info.getApplicationId()); - subscribedAPISet.add(subscribedAPI); - } - // catching the exception because when copy the api without the option "require - // re-subscription" - // need to go forward rather throwing the exception - } catch (SubscriptionAlreadyExistingException e) { - log.error("Error while adding subscription " + e.getMessage(), e); - } catch (SubscriptionBlockedException e) { - log.info("Subscription is blocked: " + e.getMessage()); - } - } - } + retrieveSubscriptionDataOfAPIs(apiTypeWrapper, oldAPIVersions, versionCount, subscribedAPISet, + connection, prepStmt); + connection.commit(); + } catch (SQLException e) { + connection.rollback(); + throw e; + } + } + } catch (SQLException e) { + handleException("Error when executing the SQL queries", e); + } + return subscribedAPISet; + } + + /** + * + * @param apiTypeWrapper apiTypeWrapper + * @param oldAPIProductVersions oldAPIProductVersions + * @return List list of Subscribed APIProducts + * @throws APIManagementException APIManagementException + */ + public List makeKeysForwardCompatibleForNewAPIProductVersion + (ApiTypeWrapper apiTypeWrapper, List oldAPIProductVersions) + throws APIManagementException { + int versionCount = 0; + List subscribedAPISet = new ArrayList<>(); + + //if there are no previous versions, there is no need to copy subscriptions + versionCount = getNoOfVersionsToCopySubscription(apiTypeWrapper, null, oldAPIProductVersions); + if (versionCount == 0) { + return subscribedAPISet; + } + + String getSubscriptionDataQuery = SQLConstants.GET_SUBSCRIPTION_DATA_SQL.replaceAll("_API_VERSION_LIST_", + String.join(",", Collections.nCopies(versionCount, "?"))); + + try { + // Retrieve all the existing subscription for the old version + try (Connection connection = APIMgtDBUtil.getConnection()) { + connection.setAutoCommit(false); + try (PreparedStatement prepStmt = connection.prepareStatement(getSubscriptionDataQuery)) { + prepStmt.setString(1, + APIUtil.replaceEmailDomainBack(apiTypeWrapper.getId().getProviderName())); + prepStmt.setString(2, apiTypeWrapper.getId().getName()); + int index = 3; + for (APIProduct oldAPIProduct : oldAPIProductVersions) { + prepStmt.setString(index++, oldAPIProduct.getId().getVersion()); } + retrieveSubscriptionDataOfAPIProducts(apiTypeWrapper, oldAPIProductVersions, versionCount, + subscribedAPISet, connection, prepStmt); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -5139,6 +5143,132 @@ public List makeKeysForwardCompatible(ApiTypeWrapper apiTypeWrapp return subscribedAPISet; } + private void retrieveSubscriptionDataOfAPIs(ApiTypeWrapper apiTypeWrapper, List oldAPIVersions, + int versionCount, List subscribedAPISet, + Connection connection, PreparedStatement prepStmt) throws SQLException, APIManagementException { + ApiTypeWrapper oldApiTypeWrapperCopy; + try (ResultSet rs = prepStmt.executeQuery()) { + List subscriptionData = new ArrayList(); + while (rs.next() && !(APIConstants.SubscriptionStatus.ON_HOLD.equals( + rs.getString("SUB_STATUS")))) { + int subscriptionId = rs.getInt("SUBSCRIPTION_ID"); + String tierId = rs.getString("TIER_ID"); + int applicationId = rs.getInt("APPLICATION_ID"); + String apiVersion = rs.getString("VERSION"); + String subscriptionStatus = rs.getString("SUB_STATUS"); + SubscriptionInfo info = new SubscriptionInfo(subscriptionId, tierId, applicationId, + apiVersion, subscriptionStatus); + subscriptionData.add(info); + } + // To keep track of already added subscriptions (apps) + List addedApplications = new ArrayList<>(); + for (int i = versionCount - 1; i >= 0; i--) { + oldApiTypeWrapperCopy = new ApiTypeWrapper(oldAPIVersions.get(i)); + addSubscriptionData(apiTypeWrapper, subscribedAPISet, connection, oldApiTypeWrapperCopy, + subscriptionData, addedApplications); + } + } + } + + private void retrieveSubscriptionDataOfAPIProducts(ApiTypeWrapper apiTypeWrapper, + List oldAPIProductVersions, int versionCount, List subscribedAPISet, + Connection connection, PreparedStatement prepStmt) throws SQLException, APIManagementException { + ApiTypeWrapper oldApiTypeWrapperCopy; + try (ResultSet rs = prepStmt.executeQuery()) { + List subscriptionData = new ArrayList(); + while (rs.next() && !(APIConstants.SubscriptionStatus.ON_HOLD.equals( + rs.getString("SUB_STATUS")))) { + int subscriptionId = rs.getInt("SUBSCRIPTION_ID"); + String tierId = rs.getString("TIER_ID"); + int applicationId = rs.getInt("APPLICATION_ID"); + String apiVersion = rs.getString("VERSION"); + String subscriptionStatus = rs.getString("SUB_STATUS"); + SubscriptionInfo info = new SubscriptionInfo(subscriptionId, tierId, applicationId, + apiVersion, subscriptionStatus); + subscriptionData.add(info); + } + // To keep track of already added subscriptions (apps) + List addedApplications = new ArrayList<>(); + for (int i = versionCount - 1; i >= 0; i--) { + oldApiTypeWrapperCopy = new ApiTypeWrapper(oldAPIProductVersions.get(i)); + addSubscriptionData(apiTypeWrapper, subscribedAPISet, connection, oldApiTypeWrapperCopy, + subscriptionData, addedApplications); + } + } + } + + private void addSubscriptionData(ApiTypeWrapper apiTypeWrapper, List subscribedAPISet, + Connection connection, ApiTypeWrapper oldApiTypeWrapperCopy, List subscriptionData, + List addedApplications) throws SQLException, APIManagementException { + for (SubscriptionInfo info : subscriptionData) { + try { + if (info.getApiVersion().equals(oldApiTypeWrapperCopy.getId() + .getVersion()) && !addedApplications.contains(info.getApplicationId())) { + String subscriptionStatus; + if (APIConstants.SubscriptionStatus.BLOCKED.equalsIgnoreCase( + info.getSubscriptionStatus())) { + subscriptionStatus = APIConstants.SubscriptionStatus.BLOCKED; + } else if (APIConstants.SubscriptionStatus.UNBLOCKED.equalsIgnoreCase( + info.getSubscriptionStatus())) { + subscriptionStatus = APIConstants.SubscriptionStatus.UNBLOCKED; + } else if (APIConstants.SubscriptionStatus.PROD_ONLY_BLOCKED.equalsIgnoreCase( + info.getSubscriptionStatus())) { + subscriptionStatus = APIConstants.SubscriptionStatus.PROD_ONLY_BLOCKED; + } else if (APIConstants.SubscriptionStatus.REJECTED.equalsIgnoreCase( + info.getSubscriptionStatus())) { + subscriptionStatus = APIConstants.SubscriptionStatus.REJECTED; + } else { + subscriptionStatus = APIConstants.SubscriptionStatus.ON_HOLD; + } + apiTypeWrapper.setTier(info.getTierId()); + Application application = getLightweightApplicationById(connection, + info.getApplicationId()); + String subscriptionUUID = UUID.randomUUID().toString(); + int subscriptionId = addSubscription(connection, apiTypeWrapper, application, + subscriptionStatus, oldApiTypeWrapperCopy.getId().getProviderName(), + subscriptionUUID); + if (subscriptionId == -1) { + String msg = "Unable to add a new subscription for the API: " + + oldApiTypeWrapperCopy.getName() + ":v" + oldApiTypeWrapperCopy.getId() + .getVersion(); + log.error(msg); + throw new APIManagementException(msg); + } + SubscribedAPI subscribedAPI = new SubscribedAPI(subscriptionUUID); + subscribedAPI.setApplication(application); + subscribedAPI.setTier(new Tier(info.getTierId())); + subscribedAPI.setOrganization(apiTypeWrapper.getOrganization()); + subscribedAPI.setIdentifier(apiTypeWrapper); + subscribedAPI.setSubStatus(subscriptionStatus); + subscribedAPI.setSubscriptionId(subscriptionId); + addedApplications.add(info.getApplicationId()); + subscribedAPISet.add(subscribedAPI); + } + // catching the exception because when copy the api without the option "require + // re-subscription" + // need to go forward rather throwing the exception + } catch (SubscriptionAlreadyExistingException e) { + log.error("Error while adding subscription " + e.getMessage(), e); + } catch (SubscriptionBlockedException e) { + log.info("Subscription is blocked: " + e.getMessage()); + } + } + } + + private int getNoOfVersionsToCopySubscription(ApiTypeWrapper apiTypeWrapper, List oldAPIVersions, + List oldAPIProductVersions) { + int count = 0; + if (!apiTypeWrapper.isAPIProduct()) { + if (oldAPIVersions != null && !oldAPIVersions.isEmpty()) + count = oldAPIVersions.size(); + } else { + if (oldAPIProductVersions != null && !oldAPIProductVersions.isEmpty()) { + count = oldAPIProductVersions.size(); + } + } + return count; + } + private int addSubscription(Connection connection, ApiTypeWrapper apiTypeWrapper, Application application, String subscriptionStatus, String subscriber) throws APIManagementException, SQLException { @@ -5436,8 +5566,10 @@ public int addAPI(API api, int tenantId, String organization) throws APIManageme recordAPILifeCycleEvent(apiId, null, APIStatus.CREATED.toString(), tenantUserName, tenantId, connection); //If the api is selected as default version, it is added/replaced into AM_API_DEFAULT_VERSION table + + ApiTypeWrapper apiTypeWrapper = new ApiTypeWrapper(api); if (api.isDefaultVersion()) { - addUpdateAPIAsDefaultVersion(api, connection); + addUpdateAPIAsDefaultVersion(apiTypeWrapper, connection); } String serviceKey = api.getServiceInfo("key"); if (StringUtils.isNotEmpty(serviceKey)) { @@ -5460,12 +5592,12 @@ public int addAPI(API api, int tenantId, String organization) throws APIManageme return apiId; } - public String getDefaultVersion(APIIdentifier apiId) throws APIManagementException { + public String getDefaultVersion(Identifier apiId) throws APIManagementException { try (Connection connection = APIMgtDBUtil.getConnection()) { - return getDefaultVersion(connection, apiId); + return getDefaultVersion(connection, (APIIdentifier) apiId); } catch (SQLException e) { - handleException("Error while getting default version for " + apiId.getApiName(), e); + handleException("Error while getting default version for " + apiId.getName(), e); } return null; } @@ -5476,7 +5608,7 @@ private String getDefaultVersion(Connection connection, APIIdentifier apiId) thr String query = SQLConstants.GET_DEFAULT_VERSION_SQL; try (PreparedStatement prepStmt = connection.prepareStatement(query)) { - prepStmt.setString(1, apiId.getApiName()); + prepStmt.setString(1, apiId.getName()); prepStmt.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); try (ResultSet rs = prepStmt.executeQuery()) { if (rs.next()) { @@ -5487,6 +5619,39 @@ private String getDefaultVersion(Connection connection, APIIdentifier apiId) thr return null; } + private String getDefaultVersion(Connection connection, APIProductIdentifier apiId) throws SQLException { + + String query = SQLConstants.GET_DEFAULT_VERSION_SQL; + try (PreparedStatement prepStmt = connection.prepareStatement(query)) { + prepStmt.setString(1, apiId.getName()); + prepStmt.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); + try (ResultSet rs = prepStmt.executeQuery()) { + if (rs.next()) { + return rs.getString("DEFAULT_API_VERSION"); + } else { + return getMigratedAPIProductDefaultVersion(connection, apiId); + } + } + } + } + + private String getMigratedAPIProductDefaultVersion(Connection connection, APIProductIdentifier apiId) + throws SQLException { + + String query = SQLConstants.GET_MIGRATED_API_PRODUCT_DEFAULT_VERSION_SQL; + try (PreparedStatement prepStmt = connection.prepareStatement(query)) { + prepStmt.setString(1, apiId.getName()); + prepStmt.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); + prepStmt.setString(3, APIConstants.API_PRODUCT_VERSION_1_0_0); + try (ResultSet rs = prepStmt.executeQuery()) { + if (rs.next()) { + return rs.getString("API_VERSION"); + } + } + } + return null; + } + /** * Persists WorkflowDTO to Database * @@ -5707,7 +5872,7 @@ public List retrieveAllWorkflowFromInternalReference(String workflo return workflowDTOList; } - private void setPublishedDefVersion(APIIdentifier apiId, Connection connection, String value) + private void setPublishedDefVersion(Identifier apiId, Connection connection, String value) throws APIManagementException { String queryDefaultVersionUpdate = SQLConstants.UPDATE_PUBLISHED_DEFAULT_VERSION_SQL; @@ -5716,11 +5881,11 @@ private void setPublishedDefVersion(APIIdentifier apiId, Connection connection, try { prepStmtDefVersionUpdate = connection.prepareStatement(queryDefaultVersionUpdate); prepStmtDefVersionUpdate.setString(1, value); - prepStmtDefVersionUpdate.setString(2, apiId.getApiName()); + prepStmtDefVersionUpdate.setString(2, apiId.getName()); prepStmtDefVersionUpdate.setString(3, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); prepStmtDefVersionUpdate.execute(); } catch (SQLException e) { - handleException("Error while deleting the API default version entry: " + apiId.getApiName() + " from the " + + handleException("Error while deleting the API default version entry: " + apiId.getName() + " from the " + "database", e); } finally { APIMgtDBUtil.closeAllConnections(prepStmtDefVersionUpdate, null, null); @@ -5737,14 +5902,14 @@ private void setPublishedDefVersion(APIIdentifier apiId, Connection connection, * @return * @throws APIManagementException */ - private void removeAPIFromDefaultVersion(List apiIdList, Connection connection) throws + private void removeAPIFromDefaultVersion(List apiIdList, Connection connection) throws APIManagementException { // TODO: check list empty try (PreparedStatement prepStmtDefVersionDelete = connection.prepareStatement(SQLConstants.REMOVE_API_DEFAULT_VERSION_SQL)) { - for (APIIdentifier apiId : apiIdList) { - prepStmtDefVersionDelete.setString(1, apiId.getApiName()); + for (Identifier apiId : apiIdList) { + prepStmtDefVersionDelete.setString(1, apiId.getName()); prepStmtDefVersionDelete.setString(2, APIUtil. replaceEmailDomainBack(apiId.getProviderName())); prepStmtDefVersionDelete.addBatch(); @@ -5757,7 +5922,7 @@ private void removeAPIFromDefaultVersion(List apiIdList, Connecti log.error("Error while rolling back the failed operation", e1); } handleException("Error while deleting the API default version entry: " + apiIdList.stream(). - map(APIIdentifier::getApiName).collect(Collectors.joining(",")) + " from the " + + map(Identifier::getName).collect(Collectors.joining(",")) + " from the " + "database", e); } } @@ -5773,7 +5938,7 @@ public String getPublishedDefaultVersion(APIIdentifier apiId) throws APIManageme try { connection = APIMgtDBUtil.getConnection(); prepStmt = connection.prepareStatement(query); - prepStmt.setString(1, apiId.getApiName()); + prepStmt.setString(1, apiId.getName()); prepStmt.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); rs = prepStmt.executeQuery(); @@ -5782,19 +5947,72 @@ public String getPublishedDefaultVersion(APIIdentifier apiId) throws APIManageme publishedDefaultVersion = rs.getString("PUBLISHED_DEFAULT_API_VERSION"); } } catch (SQLException e) { - handleException("Error while getting default version for " + apiId.getApiName(), e); + handleException("Error while getting default version for " + apiId.getName(), e); + } finally { + APIMgtDBUtil.closeAllConnections(prepStmt, connection, rs); + } + return publishedDefaultVersion; + } + + + public String getPublishedDefaultVersion(APIProductIdentifier apiId) throws APIManagementException { + + Connection connection = null; + PreparedStatement prepStmt = null; + ResultSet rs = null; + String publishedDefaultVersion = null; + + String query = SQLConstants.GET_PUBLISHED_DEFAULT_VERSION_SQL; + try { + connection = APIMgtDBUtil.getConnection(); + prepStmt = connection.prepareStatement(query); + prepStmt.setString(1, apiId.getName()); + prepStmt.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); + rs = prepStmt.executeQuery(); + if (rs.next()) { + publishedDefaultVersion = rs.getString("PUBLISHED_DEFAULT_API_VERSION"); + } else { + publishedDefaultVersion = getMigratedAPIProductPublishedDefaultVersion(connection, apiId); + } + } catch (SQLException e) { + handleException("Error while getting default version for " + apiId.getName(), e); } finally { APIMgtDBUtil.closeAllConnections(prepStmt, connection, rs); } return publishedDefaultVersion; } - public void addUpdateAPIAsDefaultVersion(API api, Connection connection) throws APIManagementException { + private String getMigratedAPIProductPublishedDefaultVersion(Connection connection, APIProductIdentifier apiId) + throws SQLException { + + String query = SQLConstants.GET_MIGRATED_API_PRODUCT_PUBLISHED_DEFAULT_VERSION_SQL; + try (PreparedStatement prepStmt = connection.prepareStatement(query)) { + prepStmt.setString(1, apiId.getName()); + prepStmt.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); + prepStmt.setString(3, APIConstants.API_PRODUCT_VERSION_1_0_0); + try (ResultSet rs = prepStmt.executeQuery()) { + if (rs.next()) { + return rs.getString("API_VERSION"); + } + } + } + return null; + } + + + public void addUpdateAPIAsDefaultVersion(ApiTypeWrapper apiTypeWrapper, Connection connection) + throws APIManagementException { + + String publishedDefaultVersion; + if (apiTypeWrapper.isAPIProduct()) { + publishedDefaultVersion = getPublishedDefaultVersion((APIProductIdentifier) apiTypeWrapper.getId()); + } else { + publishedDefaultVersion = getPublishedDefaultVersion((APIIdentifier) apiTypeWrapper.getId()); + } - String publishedDefaultVersion = getPublishedDefaultVersion(api.getId()); - boolean deploymentAvailable = isDeploymentAvailableByAPIUUID(connection, api.getUuid()); - ArrayList apiIdList = new ArrayList() {{ - add(api.getId()); + boolean deploymentAvailable = isDeploymentAvailableByAPIUUID(connection, apiTypeWrapper.getUuid()); + ArrayList apiIdList = new ArrayList() {{ + add(apiTypeWrapper.getId()); }}; removeAPIFromDefaultVersion(apiIdList, connection); @@ -5802,21 +6020,22 @@ public void addUpdateAPIAsDefaultVersion(API api, Connection connection) throws String queryDefaultVersionAdd = SQLConstants.ADD_API_DEFAULT_VERSION_SQL; try { prepStmtDefVersionAdd = connection.prepareStatement(queryDefaultVersionAdd); - prepStmtDefVersionAdd.setString(1, api.getId().getApiName()); - prepStmtDefVersionAdd.setString(2, APIUtil.replaceEmailDomainBack(api.getId().getProviderName())); - prepStmtDefVersionAdd.setString(3, api.getId().getVersion()); + prepStmtDefVersionAdd.setString(1, apiTypeWrapper.getId().getName()); + prepStmtDefVersionAdd.setString(2, APIUtil.replaceEmailDomainBack(apiTypeWrapper.getId().getProviderName())); + prepStmtDefVersionAdd.setString(3, apiTypeWrapper.getId().getVersion()); if (deploymentAvailable) { - prepStmtDefVersionAdd.setString(4, api.getId().getVersion()); - api.setAsPublishedDefaultVersion(true); + prepStmtDefVersionAdd.setString(4, apiTypeWrapper.getId().getVersion()); + apiTypeWrapper.setAsPublishedDefaultVersion(true); } else { prepStmtDefVersionAdd.setString(4, publishedDefaultVersion); } - prepStmtDefVersionAdd.setString(5, api.getOrganization()); + prepStmtDefVersionAdd.setString(5, apiTypeWrapper.getOrganization()); prepStmtDefVersionAdd.execute(); } catch (SQLException e) { - handleException("Error while adding the API default version entry: " + api.getId().getApiName() + " to " + - "the database", e); + + handleException("Error while adding the default version entry for " + (apiTypeWrapper.isAPIProduct() ? + "API product :" : "API :") + apiTypeWrapper.getId().getName() + " to " + "the database", e); } finally { APIMgtDBUtil.closeAllConnections(prepStmtDefVersionAdd, null, null); } @@ -6870,12 +7089,12 @@ public void updateAPI(API api, String username) throws APIManagementException { // happen //If the api is selected as default version, it is added/replaced into AM_API_DEFAULT_VERSION table if (api.isDefaultVersion()) { - addUpdateAPIAsDefaultVersion(api, connection); + ApiTypeWrapper apiTypeWrapper = new ApiTypeWrapper(api); + addUpdateAPIAsDefaultVersion(apiTypeWrapper, connection); } else { //tick is removed - ArrayList apiIdList = new ArrayList() {{ + ArrayList apiIdList = new ArrayList() {{ add(api.getId()); }}; - removeAPIFromDefaultVersion(apiIdList, connection); } } @@ -7072,7 +7291,7 @@ public void deleteAPI(String uuid) throws APIManagementException { String curDefaultVersion = getDefaultVersion(identifier); String pubDefaultVersion = getPublishedDefaultVersion(identifier); if (identifier.getVersion().equals(curDefaultVersion)) { - ArrayList apiIdList = new ArrayList() {{ + ArrayList apiIdList = new ArrayList() {{ add(identifier); }}; removeAPIFromDefaultVersion(apiIdList, connection); @@ -7989,6 +8208,35 @@ public boolean isContextExist(String context, String organization) { return false; } + public boolean isContextExistForAPIProducts(String context, String contextWithVersion, String organization) { + + Connection connection = null; + ResultSet resultSet = null; + PreparedStatement prepStmt = null; + + String sql = SQLConstants.GET_API_CONTEXT_SQL_FOR_API_PRODUCTS; + try { + connection = APIMgtDBUtil.getConnection(); + prepStmt = connection.prepareStatement(sql); + prepStmt.setString(1, context); + prepStmt.setString(2, contextWithVersion); + prepStmt.setString(3, context); + prepStmt.setString(4, organization); + resultSet = prepStmt.executeQuery(); + + while (resultSet.next()) { + if (resultSet.getString(1) != null) { + return true; + } + } + } catch (SQLException e) { + log.error("Failed to retrieve the API Context ", e); + } finally { + APIMgtDBUtil.closeAllConnections(prepStmt, connection, resultSet); + } + return false; + } + /** * Get API Context using a new DB connection. * @@ -9608,15 +9856,24 @@ public APIInfo getAPIInfoByUUID(String apiId) throws APIManagementException { } try (ResultSet resultSet = preparedStatement.executeQuery()) { if (resultSet.next()) { + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + String context = resultSet.getString("CONTEXT"); + String apiType = resultSet.getString("API_TYPE"); + String version = resultSet.getString("API_VERSION"); + if (APIConstants.API_PRODUCT.equals(apiType) + && APIConstants.API_PRODUCT_VERSION_1_0_0.equals(version) + && StringUtils.isBlank(contextTemplate)) { + context = context + "/" + APIConstants.API_PRODUCT_VERSION_1_0_0; + } APIInfo.Builder apiInfoBuilder = new APIInfo.Builder(); apiInfoBuilder = apiInfoBuilder.id(resultSet.getString("API_UUID")) .name(resultSet.getString("API_NAME")) - .version(resultSet.getString("API_VERSION")) + .version(version) .provider(resultSet.getString("API_PROVIDER")) - .context(resultSet.getString("CONTEXT")) - .contextTemplate(resultSet.getString("CONTEXT_TEMPLATE")) + .context(context) + .contextTemplate(contextTemplate) .status(APIUtil.getApiStatus(resultSet.getString("STATUS"))) - .apiType(resultSet.getString("API_TYPE")) + .apiType(apiType) .createdBy(resultSet.getString("CREATED_BY")) .createdTime(resultSet.getString("CREATED_TIME")) .updatedBy(resultSet.getString("UPDATED_BY")) @@ -9683,21 +9940,53 @@ public String getAPIStatusFromAPIUUID(String uuid) throws APIManagementException public void setDefaultVersion(API api) throws APIManagementException { - APIIdentifier apiId = api.getId(); try (Connection connection = APIMgtDBUtil.getConnection()) { - try (PreparedStatement preparedStatement = - connection.prepareStatement(SQLConstants.RETRIEVE_DEFAULT_VERSION)) { - preparedStatement.setString(1, apiId.getApiName()); - preparedStatement.setString(2, APIUtil.replaceEmailDomainBack(apiId.getProviderName())); + try (PreparedStatement preparedStatement = connection.prepareStatement( + SQLConstants.RETRIEVE_DEFAULT_VERSION)) { + preparedStatement.setString(1, api.getId().getName()); + preparedStatement.setString(2, APIUtil.replaceEmailDomainBack(api.getId().getProviderName())); + try (ResultSet resultSet = preparedStatement.executeQuery()) { if (resultSet.next()) { - api.setDefaultVersion(apiId.getVersion().equals(resultSet.getString("DEFAULT_API_VERSION"))); - api.setAsPublishedDefaultVersion(apiId.getVersion().equals(resultSet.getString( - "PUBLISHED_DEFAULT_API_VERSION"))); + api.setDefaultVersion(api.getId().getVersion() + .equals(resultSet.getString("DEFAULT_API_VERSION"))); + api.setAsPublishedDefaultVersion(api.getId().getVersion() + .equals(resultSet.getString("PUBLISHED_DEFAULT_API_VERSION"))); } } } + } catch (SQLException e) { + throw new APIManagementException("Error while retrieving apimgt connection", e, + ExceptionCodes.INTERNAL_ERROR); + } + } + + public void setDefaultVersion(APIProduct apiProduct) throws APIManagementException { + try (Connection connection = APIMgtDBUtil.getConnection()) { + try (PreparedStatement preparedStatement = connection.prepareStatement( + SQLConstants.RETRIEVE_DEFAULT_VERSION_WITH_API_INFO)) { + preparedStatement.setString(1, apiProduct.getId().getName()); + preparedStatement.setString(2, + APIUtil.replaceEmailDomainBack(apiProduct.getId().getProviderName())); + preparedStatement.setString(3, apiProduct.getId().getVersion()); + + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + String defaultAPIVersion = resultSet.getString("DEFAULT_API_VERSION"); + String publishedDefaultAPIVersion = resultSet.getString("PUBLISHED_DEFAULT_API_VERSION"); + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + + if (StringUtils.isBlank(defaultAPIVersion) && StringUtils.isBlank(contextTemplate)) { + defaultAPIVersion = apiProduct.getId().getVersion(); + publishedDefaultAPIVersion = apiProduct.getId().getVersion(); + } + apiProduct.setDefaultVersion(apiProduct.getId().getVersion().equals(defaultAPIVersion)); + apiProduct.setAsPublishedDefaultVersion(apiProduct.getId().getVersion() + .equals(publishedDefaultAPIVersion)); + } + } + } } catch (SQLException e) { throw new APIManagementException("Error while retrieving apimgt connection", e, ExceptionCodes.INTERNAL_ERROR); @@ -9719,9 +10008,9 @@ public API getLightWeightAPIInfoByAPIIdentifier(APIIdentifier apiIdentifier, Str apiIdentifier.setId(resultSet.getInt("API_ID")); API api = new API(apiIdentifier); api.setUuid(resultSet.getString("API_UUID")); - api.setContext(resultSet.getString("CONTEXT")); api.setType(resultSet.getString("API_TYPE")); api.setStatus(resultSet.getString("STATUS")); + setContext(apiIdentifier, resultSet, api); return api; } } @@ -9734,6 +10023,19 @@ public API getLightWeightAPIInfoByAPIIdentifier(APIIdentifier apiIdentifier, Str return null; } + private static void setContext(APIIdentifier apiIdentifier, ResultSet resultSet, API api) throws SQLException { + String context = resultSet.getString("CONTEXT"); + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + // If context template is null for migrated API Products, set the default version as the Context/1.0.0 + if (APIConstants.API_PRODUCT_VERSION_1_0_0.equals(apiIdentifier.getVersion()) + && StringUtils.isBlank(contextTemplate)) { + context = context + "/" + APIConstants.API_PRODUCT_VERSION_1_0_0; + + } + api.setContext(context); + api.setContextTemplate(contextTemplate); + } + /** * Identify whether the loggedin user used his ordinal username or email * @@ -14444,15 +14746,16 @@ public void addAPIProduct(APIProduct apiProduct, String organization) throws API prepStmtAddAPIProduct.setString(2, identifier.getName()); prepStmtAddAPIProduct.setString(3, identifier.getVersion()); prepStmtAddAPIProduct.setString(4, apiProduct.getContext()); - prepStmtAddAPIProduct.setString(5, apiProduct.getProductLevelPolicy()); - prepStmtAddAPIProduct.setString(6, APIUtil.replaceEmailDomainBack(identifier.getProviderName())); - prepStmtAddAPIProduct.setTimestamp(7, new Timestamp(System.currentTimeMillis())); - prepStmtAddAPIProduct.setString(8, APIConstants.API_PRODUCT); - prepStmtAddAPIProduct.setString(9, apiProduct.getUuid()); - prepStmtAddAPIProduct.setString(10, apiProduct.getState()); - prepStmtAddAPIProduct.setString(11, organization); - prepStmtAddAPIProduct.setString(12, apiProduct.getGatewayVendor()); - prepStmtAddAPIProduct.setString(13, apiProduct.getVersionTimestamp()); + prepStmtAddAPIProduct.setString(5, apiProduct.getContextTemplate()); + prepStmtAddAPIProduct.setString(6, apiProduct.getProductLevelPolicy()); + prepStmtAddAPIProduct.setString(7, APIUtil.replaceEmailDomainBack(identifier.getProviderName())); + prepStmtAddAPIProduct.setTimestamp(8, new Timestamp(System.currentTimeMillis())); + prepStmtAddAPIProduct.setString(9, APIConstants.API_PRODUCT); + prepStmtAddAPIProduct.setString(10, apiProduct.getUuid()); + prepStmtAddAPIProduct.setString(11, apiProduct.getState()); + prepStmtAddAPIProduct.setString(12, organization); + prepStmtAddAPIProduct.setString(13, apiProduct.getGatewayVendor()); + prepStmtAddAPIProduct.setString(14, apiProduct.getVersionTimestamp()); prepStmtAddAPIProduct.execute(); rs = prepStmtAddAPIProduct.getGeneratedKeys(); @@ -14466,6 +14769,13 @@ public void addAPIProduct(APIProduct apiProduct, String organization) throws API } addAPIProductResourceMappings(apiProduct.getProductResources(), apiProduct.getOrganization(), connection); + + //If the apiproduct is selected as default version, it is added/replaced into AM_API_DEFAULT_VERSION table + if (apiProduct.isDefaultVersion()) { + ApiTypeWrapper apiTypeWrapper = new ApiTypeWrapper(apiProduct); + addUpdateAPIAsDefaultVersion(apiTypeWrapper, connection); + } + String tenantUserName = MultitenantUtils .getTenantAwareUsername(APIUtil.replaceEmailDomainBack(identifier.getProviderName())); int tenantId = APIUtil.getTenantId(APIUtil.replaceEmailDomainBack(identifier.getProviderName())); @@ -14751,6 +15061,18 @@ public void deleteAPIProduct(APIProductIdentifier productIdentifier) throws APIM deleteAllAPISpecificOperationPoliciesByAPIUUID(connection, productIdentifier.getUUID(), null); + // delete the default version if the deleted product is a default version + String curDefaultVersion = getDefaultVersion(productIdentifier); + String pubDefaultVersion = getPublishedDefaultVersion(productIdentifier); + if (productIdentifier.getVersion().equals(curDefaultVersion)) { + ArrayList apiIdList = new ArrayList() {{ + add(productIdentifier); + }}; + removeAPIFromDefaultVersion(apiIdList, connection); + } else if (productIdentifier.getVersion().equals(pubDefaultVersion)) { + setPublishedDefVersion(productIdentifier, connection, null); + } + connection.commit(); } catch (SQLException e) { handleException("Error while deleting api product " + productIdentifier, e); @@ -14793,7 +15115,7 @@ public int getAPIProductId(APIProductIdentifier identifier) throws APIManagement preparedStatement = conn.prepareStatement(queryGetProductId); preparedStatement.setString(1, identifier.getName()); preparedStatement.setString(2, APIUtil.replaceEmailDomainBack(identifier.getProviderName())); - preparedStatement.setString(3, APIConstants.API_PRODUCT_VERSION); //versioning is not supported atm + preparedStatement.setString(3, identifier.getVersion()); rs = preparedStatement.executeQuery(); @@ -14842,6 +15164,21 @@ public void updateAPIProduct(APIProduct product, String username) throws APIMana int productId = getAPIID(product.getUuid(), conn); updateAPIProductResourceMappings(product, productId, conn); + + String previousDefaultVersion = getDefaultVersion(product.getId()); + if (product.isDefaultVersion() ^ product.getId().getVersion().equals(previousDefaultVersion)) { + //If the api product is selected as default version, it is added/replaced into AM_API_DEFAULT_VERSION table + if (product.isDefaultVersion()) { + ApiTypeWrapper apiTypeWrapper = new ApiTypeWrapper(product); + addUpdateAPIAsDefaultVersion(apiTypeWrapper, conn); + } else { //tick is removed + ArrayList apiIdList = new ArrayList() {{ + add(product.getId()); + }}; + removeAPIFromDefaultVersion(apiIdList, conn); + } + } + conn.commit(); } catch (SQLException e) { if (conn != null) { @@ -16212,6 +16549,54 @@ public List getAllAPIVersions(String apiName, String apiProvider) throws AP return apiVersions; } + + /** + * Return ids of the versions for the given name for the given provider + * + * @param apiProductName apiProduct name + * @param apiProvider provider + * @return set ids + * @throws APIManagementException + */ + public List getAllAPIProductVersions(String apiProductName, String apiProvider) + throws APIManagementException { + + List apiProductVersions = new ArrayList(); + + try (Connection connection = APIMgtDBUtil.getConnection(); + PreparedStatement statement = connection.prepareStatement(SQLConstants.GET_API_VERSIONS_UUID)) { + statement.setString(1, APIUtil.replaceEmailDomainBack(apiProvider)); + statement.setString(2, apiProductName); + ResultSet resultSet = statement.executeQuery(); + + while (resultSet.next()) { + String version = resultSet.getString("API_VERSION"); + String status = resultSet.getString("STATUS"); + String versionTimestamp = resultSet.getString("VERSION_COMPARABLE"); + String context = resultSet.getString("CONTEXT"); + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + + String uuid = resultSet.getString("API_UUID"); + if (!APIConstants.API_PRODUCT.equals(resultSet.getString("API_TYPE"))) { + // skip api products + continue; + } + APIProduct apiProduct = new APIProduct(new APIProductIdentifier(apiProvider, apiProductName, + version, uuid)); + apiProduct.setUuid(uuid); + apiProduct.setState(status); + apiProduct.setVersionTimestamp(versionTimestamp); + apiProduct.setContext(context); + apiProduct.setContextTemplate(contextTemplate); + apiProductVersions.add(apiProduct); + } + } catch (SQLException e) { + handleException("Error while retrieving versions for apiProduct " + apiProductName + + " for the provider " + apiProvider, e); + } + return apiProductVersions; + } + /** * Get count of the revisions created for a particular API. * diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/SubscriptionValidationDAO.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/SubscriptionValidationDAO.java index e45bce8c65d9..101bf44ca2b3 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/SubscriptionValidationDAO.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/SubscriptionValidationDAO.java @@ -21,6 +21,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jetbrains.annotations.Nullable; import org.wso2.carbon.apimgt.api.APIManagementException; import org.wso2.carbon.apimgt.api.dto.ConditionDTO; import org.wso2.carbon.apimgt.api.dto.ConditionGroupDTO; @@ -417,9 +418,12 @@ public List getAllApis(String organization, boolean isExpand) { api.setStatus(resultSet.getString("STATUS")); api.setOrganization(resultSet.getString("ORGANIZATION")); String publishedDefaultApiVersion = resultSet.getString("PUBLISHED_DEFAULT_API_VERSION"); - if (StringUtils.isNotBlank(publishedDefaultApiVersion)) { - api.setIsDefaultVersion(true); - } + String context = resultSet.getString("CONTEXT"); + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + + setDefaultVersionContext(apiType, api, version, publishedDefaultApiVersion, context, + contextTemplate); + if (isExpand) { String revision = resultSet.getString("REVISION_UUID"); api.setPolicy(getAPILevelTier(connection, apiUuid, revision)); @@ -1081,6 +1085,7 @@ public List getAllApis(String organization, String deployment, boolean isEx API api = new API(); String provider = resultSet.getString("API_PROVIDER"); String name = resultSet.getString("API_NAME"); + String context = resultSet.getString("CONTEXT"); String version = resultSet.getString("API_VERSION"); String apiUuid = resultSet.getString("API_UUID"); api.setApiUUID(apiUuid); @@ -1091,12 +1096,13 @@ public List getAllApis(String organization, String deployment, boolean isEx api.setApiType(apiType); api.setStatus(resultSet.getString("STATUS")); api.setPolicy(resultSet.getString("API_TIER")); - api.setContext(resultSet.getString("CONTEXT")); api.setOrganization(resultSet.getString("ORGANIZATION")); String publishedDefaultApiVersion = resultSet.getString("PUBLISHED_DEFAULT_API_VERSION"); - if (StringUtils.isNotBlank(publishedDefaultApiVersion)) { - api.setIsDefaultVersion(true); - } + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + + setDefaultVersionContext(apiType, api, version, publishedDefaultApiVersion, context, + contextTemplate); + if (isExpand) { String revision = resultSet.getString("REVISION_UUID"); api.setPolicy(getAPILevelTier(connection, apiUuid, revision)); @@ -1118,6 +1124,25 @@ public List getAllApis(String organization, String deployment, boolean isEx return apiList; } + private static void setDefaultVersionContext(String apiType, API api, String version, + String publishedDefaultApiVersion, String context, String contextTemplate) { + + if (StringUtils.isNotBlank(publishedDefaultApiVersion) + && StringUtils.equals(version, publishedDefaultApiVersion)) { + api.setIsDefaultVersion(true); + } + + if (APIConstants.API_PRODUCT.equals(apiType) + && APIConstants.API_PRODUCT_VERSION_1_0_0.equals(version) + && StringUtils.isBlank(contextTemplate)) { + if (StringUtils.isBlank(publishedDefaultApiVersion)) { + api.setIsDefaultVersion(true); + } + String synapseContext = context + "/" + APIConstants.API_PRODUCT_VERSION_1_0_0; + api.setContext(synapseContext); + } + } + private void attachURlMappingDetailsOfApiProduct(Connection connection, API api) throws SQLException { String sql = SubscriptionValidationSQLConstants.GET_ALL_API_PRODUCT_URI_TEMPLATES_SQL; @@ -1150,10 +1175,20 @@ private void attachURlMappingDetailsOfApiProduct(Connection connection, API api) public API getAPIByContextAndVersion(String context, String version, String deployment, boolean isExpand) { String sql = SubscriptionValidationSQLConstants.GET_API_BY_CONTEXT_AND_VERSION_SQL; + String contextWhenContextTemplateIsNull = context; + + String versionInContext = "/" + version; + int lastIndex = context.lastIndexOf(versionInContext); + if (lastIndex >= 0) { + contextWhenContextTemplateIsNull = context.substring(0, lastIndex) + + context.substring(lastIndex + versionInContext.length()); + } + try (Connection connection = APIMgtDBUtil.getConnection()) { try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { - preparedStatement.setString(1, context); - preparedStatement.setString(2, version); + preparedStatement.setString(1, contextWhenContextTemplateIsNull); + preparedStatement.setString(2, context); + preparedStatement.setString(3, version); try (ResultSet resultSet = preparedStatement.executeQuery()) { while (resultSet.next()) { String deploymentName = resultSet.getString("DEPLOYMENT_NAME"); @@ -1256,12 +1291,15 @@ public API getApiByUUID(String apiId, String deployment, String organization, bo try (ResultSet resultSet = preparedStatement.executeQuery()) { while (resultSet.next()) { String deploymentName = resultSet.getString("DEPLOYMENT_NAME"); + String context = resultSet.getString("CONTEXT"); + if (!deployment.equals(deploymentName)) { continue; } String apiType = resultSet.getString("API_TYPE"); String version = resultSet.getString("API_VERSION"); String apiUuid = resultSet.getString("API_UUID"); + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); API api = new API(); String provider = resultSet.getString("API_PROVIDER"); String name = resultSet.getString("API_NAME"); @@ -1273,10 +1311,13 @@ public API getApiByUUID(String apiId, String deployment, String organization, bo api.setApiType(apiType); api.setOrganization(resultSet.getString("ORGANIZATION")); api.setPolicy(resultSet.getString("API_TIER")); - api.setContext(resultSet.getString("CONTEXT")); + api.setContext(context); api.setStatus(resultSet.getString("STATUS")); String revision = resultSet.getString("REVISION_UUID"); - api.setIsDefaultVersion(isAPIDefaultVersion(connection, provider, name, version)); + String publishedDefaultApiVersion = getAPIDefaultVersion(connection, provider, name); + + setDefaultVersionContext(apiType, api, version, publishedDefaultApiVersion, context, + contextTemplate); if (isExpand) { api.setPolicy(getAPILevelTier(connection, apiUuid, revision)); if (APIConstants.API_PRODUCT.equals(apiType)) { @@ -1297,6 +1338,24 @@ public API getApiByUUID(String apiId, String deployment, String organization, bo return null; } + private String getAPIDefaultVersion(Connection connection, String provider, String name) + throws SQLException { + + try (PreparedStatement preparedStatement = + connection.prepareStatement(SubscriptionValidationSQLConstants.GET_API_DEFAULT_VERSION_STRING_SQL)) { + preparedStatement.setString(1, name); + preparedStatement.setString(2, provider); + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getString("PUBLISHED_DEFAULT_API_VERSION"); + } + } + } catch (SQLException e) { + log.error("Error while loading default version", e); + } + return null; + } + private String getAPILevelTier(Connection connection, String apiUUID, String revisionUUID) throws SQLException { try (PreparedStatement preparedStatement = @@ -1328,6 +1387,7 @@ public List getAllApisByLabel(String gatewayLabel, Boolean expand) { API api = new API(); String provider = resultSet.getString("API_PROVIDER"); String name = resultSet.getString("API_NAME"); + String context = resultSet.getString("CONTEXT"); String version = resultSet.getString("API_VERSION"); api.setApiUUID(apiUuid); api.setApiId(resultSet.getInt("API_ID")); @@ -1342,9 +1402,11 @@ public List getAllApisByLabel(String gatewayLabel, Boolean expand) { api.setRevision(revision); api.setEnvironment(deploymentName); String publishedDefaultApiVersion = resultSet.getString("PUBLISHED_DEFAULT_API_VERSION"); - if (StringUtils.isNotBlank(publishedDefaultApiVersion)) { - api.setIsDefaultVersion(true); - } + String contextTemplate = resultSet.getString("CONTEXT_TEMPLATE"); + + setDefaultVersionContext(apiType, api, version, publishedDefaultApiVersion, context, + contextTemplate); + if (expand) { api.setPolicy(getAPILevelTier(connection, apiUuid, revision)); if (APIConstants.API_PRODUCT.equals(apiType)) { diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SQLConstants.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SQLConstants.java index db9f675773ad..4785b8cc8565 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SQLConstants.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SQLConstants.java @@ -1353,9 +1353,10 @@ public class SQLConstants { "SELECT API.API_ID FROM AM_API API WHERE API.API_PROVIDER = ? AND API.API_NAME = ? AND API.API_VERSION = ? "; public static final String GET_API_ID_SQL_BY_UUID = "SELECT API.API_ID FROM AM_API API WHERE API.API_UUID = ?"; + public static final String GET_LIGHT_WEIGHT_API_INFO_BY_API_IDENTIFIER = "SELECT API_ID,API_UUID,API_PROVIDER," + - "API_NAME,API_VERSION,CONTEXT,API_TYPE,STATUS FROM AM_API WHERE API_PROVIDER = ? AND API_NAME = ? AND " + - "API_VERSION = ? AND ORGANIZATION = ?"; + "API_NAME, API_VERSION, CONTEXT_TEMPLATE, CONTEXT, API_TYPE, STATUS FROM AM_API WHERE API_PROVIDER = ? " + + "AND API_NAME = ? AND API_VERSION = ? AND ORGANIZATION = ?"; public static final String GET_API_PRODUCT_ID_SQL = "SELECT API_ID FROM AM_API WHERE API_PROVIDER = ? AND API_NAME = ? " @@ -1459,6 +1460,10 @@ public class SQLConstants { public static final String GET_DEFAULT_VERSION_SQL = "SELECT DEFAULT_API_VERSION FROM AM_API_DEFAULT_VERSION WHERE API_NAME= ? AND API_PROVIDER= ? "; + public static final String GET_MIGRATED_API_PRODUCT_DEFAULT_VERSION_SQL = + "SELECT API_VERSION FROM AM_API WHERE API_NAME= ? AND API_PROVIDER= ? AND API_VERSION= ? " + + "AND CONTEXT_TEMPLATE IS NULL"; + public static final String ADD_WORKFLOW_ENTRY_SQL = " INSERT INTO AM_WORKFLOWS (WF_REFERENCE,WF_TYPE,WF_STATUS,WF_CREATED_TIME,WF_STATUS_DESC,TENANT_ID," + "TENANT_DOMAIN,WF_EXTERNAL_REFERENCE,WF_METADATA,WF_PROPERTIES)" + @@ -1519,6 +1524,10 @@ public class SQLConstants { public static final String GET_PUBLISHED_DEFAULT_VERSION_SQL = "SELECT PUBLISHED_DEFAULT_API_VERSION FROM AM_API_DEFAULT_VERSION WHERE API_NAME= ? AND API_PROVIDER= ? "; + public static final String GET_MIGRATED_API_PRODUCT_PUBLISHED_DEFAULT_VERSION_SQL = + "SELECT API_VERSION FROM AM_API WHERE API_NAME= ? AND API_PROVIDER= ? AND API_VERSION= ? AND " + + "CONTEXT_TEMPLATE IS NULL AND REVISIONS_CREATED != 0"; + public static final String ADD_API_DEFAULT_VERSION_SQL = " INSERT INTO " + " AM_API_DEFAULT_VERSION(API_NAME,API_PROVIDER,DEFAULT_API_VERSION,PUBLISHED_DEFAULT_API_VERSION," @@ -1935,6 +1944,9 @@ public class SQLConstants { public static final String GET_API_CONTEXT_SQL = "SELECT CONTEXT FROM AM_API WHERE CONTEXT= ? AND ORGANIZATION = ?"; + public static final String GET_API_CONTEXT_SQL_FOR_API_PRODUCTS = + "SELECT CONTEXT FROM AM_API WHERE (CONTEXT= ? OR CONTEXT= ? OR CONTEXT_TEMPLATE= ?) AND ORGANIZATION = ?"; + public static final String GET_API_IDENTIFIER_BY_UUID_SQL = "SELECT API_PROVIDER, API_NAME, API_VERSION FROM AM_API WHERE API_UUID = ?"; public static final String GET_API_OR_API_PRODUCT_IDENTIFIER_BY_UUID_SQL = @@ -2716,11 +2728,9 @@ public class SQLConstants { "SELECT URL_PATTERN , URL_MAPPING_ID, HTTP_METHOD FROM AM_API API , AM_API_URL_MAPPING URL " + "WHERE API.API_ID = URL.API_ID AND API.API_UUID =? AND URL.REVISION_UUID IS NULL"; - public static final String ADD_API_PRODUCT = - "INSERT INTO " - + "AM_API(API_PROVIDER, API_NAME, API_VERSION, CONTEXT," - + "API_TIER, CREATED_BY, CREATED_TIME, API_TYPE, API_UUID, STATUS, ORGANIZATION, GATEWAY_VENDOR, VERSION_COMPARABLE) VALUES (?,?,?,?,?,?,?,?,?" - + ",?,?,?,?)"; + public static final String ADD_API_PRODUCT = "INSERT INTO " + "AM_API(API_PROVIDER, API_NAME, API_VERSION, " + + "CONTEXT, CONTEXT_TEMPLATE, API_TIER, CREATED_BY, CREATED_TIME, API_TYPE, API_UUID, STATUS, " + + "ORGANIZATION, GATEWAY_VENDOR, VERSION_COMPARABLE) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; public static final String GET_RESOURCES_OF_PRODUCT = "SELECT API_UM.URL_MAPPING_ID, API_UM.URL_PATTERN, API_UM.HTTP_METHOD, API_UM.AUTH_SCHEME, " + @@ -3003,6 +3013,17 @@ public class SQLConstants { " ORGANIZATION, REVISIONS_CREATED, STATUS FROM AM_API WHERE API_UUID = ?"; public static final String RETRIEVE_DEFAULT_VERSION = "SELECT DEFAULT_API_VERSION,PUBLISHED_DEFAULT_API_VERSION " + "FROM AM_API_DEFAULT_VERSION WHERE API_NAME = ? AND API_PROVIDER =?"; + + public static final String RETRIEVE_DEFAULT_VERSION_WITH_API_INFO = "SELECT AM_API.API_PROVIDER, AM_API.API_NAME, " + + "AM_API.API_VERSION, AM_API.CONTEXT_TEMPLATE, AM_API_DEFAULT_VERSION.PUBLISHED_DEFAULT_API_VERSION AS " + + "PUBLISHED_DEFAULT_API_VERSION, AM_API_DEFAULT_VERSION.DEFAULT_API_VERSION AS DEFAULT_API_VERSION " + + "FROM AM_API " + + "LEFT JOIN AM_API_DEFAULT_VERSION " + + "ON AM_API_DEFAULT_VERSION.API_NAME = AM_API.API_NAME AND " + + "AM_API_DEFAULT_VERSION.API_PROVIDER = AM_API.API_PROVIDER AND " + + "AM_API_DEFAULT_VERSION.ORGANIZATION = AM_API.ORGANIZATION " + + "WHERE AM_API.API_NAME = ? AND AM_API.API_PROVIDER = ? AND AM_API.API_VERSION = ?"; + public static final String UPDATE_REVISION_CREATED_BY_API_SQL = "UPDATE AM_API SET REVISIONS_CREATED = ? WHERE " + "API_UUID = ?"; public static final String ADD_API_REVISION_METADATA = "INSERT INTO AM_API_REVISION_METADATA (API_UUID," + diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SubscriptionValidationSQLConstants.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SubscriptionValidationSQLConstants.java index e740b69c76cd..de6206a46e82 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SubscriptionValidationSQLConstants.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/dao/constants/SubscriptionValidationSQLConstants.java @@ -553,10 +553,10 @@ public class SubscriptionValidationSQLConstants { public static final String GET_API_BY_UUID_SQL = "SELECT " + - "AM_API.API_PROVIDER,AM_API.API_NAME,AM_API.CONTEXT,AM_API.API_UUID,AM_API.API_ID,AM_API" + - ".API_TIER,AM_API.API_VERSION,AM_API.API_TYPE,AM_API.STATUS,AM_REVISION.REVISION_UUID AS " + - "REVISION_UUID,AM_DEPLOYMENT_REVISION_MAPPING.NAME AS DEPLOYMENT_NAME,AM_API.ORGANIZATION " + - "FROM " + + "AM_API.API_PROVIDER,AM_API.API_NAME,AM_API.CONTEXT, AM_API.CONTEXT_TEMPLATE, AM_API.API_UUID, " + + "AM_API.API_ID, AM_API.API_TIER, AM_API.API_VERSION, AM_API.API_TYPE, AM_API.STATUS, " + + "AM_REVISION.REVISION_UUID AS REVISION_UUID,AM_DEPLOYMENT_REVISION_MAPPING.NAME AS " + + "DEPLOYMENT_NAME,AM_API.ORGANIZATION FROM " + "AM_API LEFT JOIN AM_REVISION ON AM_API.API_UUID = AM_REVISION.API_UUID " + "LEFT JOIN AM_DEPLOYMENT_REVISION_MAPPING " + "ON AM_REVISION.REVISION_UUID=AM_DEPLOYMENT_REVISION_MAPPING.REVISION_UUID " + @@ -565,6 +565,9 @@ public class SubscriptionValidationSQLConstants { public static final String GET_DEFAULT_VERSION_API_SQL = "SELECT PUBLISHED_DEFAULT_API_VERSION FROM " + "AM_API_DEFAULT_VERSION WHERE API_NAME = ? AND API_PROVIDER = ? AND PUBLISHED_DEFAULT_API_VERSION = ?"; + public static final String GET_API_DEFAULT_VERSION_STRING_SQL = "SELECT PUBLISHED_DEFAULT_API_VERSION FROM " + + "AM_API_DEFAULT_VERSION WHERE API_NAME = ? AND API_PROVIDER = ?"; + public static final String GET_URI_TEMPLATES_BY_API_SQL = "SELECT AM_API_URL_MAPPING.HTTP_METHOD," + "AM_API_URL_MAPPING.AUTH_SCHEME,AM_API_URL_MAPPING.URL_PATTERN,AM_API_URL_MAPPING.THROTTLING_TIER," + "AM_API_RESOURCE_SCOPE_MAPPING.SCOPE_NAME FROM AM_API_URL_MAPPING LEFT JOIN AM_API_RESOURCE_SCOPE_MAPPING" + @@ -572,7 +575,7 @@ public class SubscriptionValidationSQLConstants { "AM_API_URL_MAPPING.API_ID = ? AND AM_API_URL_MAPPING.REVISION_UUID = ?"; public static final String GET_ALL_APIS_BY_ORGANIZATION_AND_DEPLOYMENT_SQL = "SELECT AM_API.API_PROVIDER,AM_API" + - ".API_NAME,AM_API.CONTEXT,AM_API.API_UUID,AM_API.API_ID,AM_API.API_TIER,AM_API.API_VERSION,AM_API" + + ".API_NAME,AM_API.CONTEXT, AM_API.CONTEXT_TEMPLATE, AM_API.API_UUID,AM_API.API_ID,AM_API.API_TIER,AM_API.API_VERSION,AM_API" + ".API_TYPE,AM_API.STATUS,AM_REVISION.REVISION_UUID AS REVISION_UUID,AM_DEPLOYMENT_REVISION_MAPPING.NAME " + "AS DEPLOYMENT_NAME, " + "AM_API_DEFAULT_VERSION.PUBLISHED_DEFAULT_API_VERSION AS PUBLISHED_DEFAULT_API_VERSION,AM_API.ORGANIZATION " + @@ -580,7 +583,6 @@ public class SubscriptionValidationSQLConstants { "AM_DEPLOYMENT_REVISION_MAPPING ON AM_REVISION.REVISION_UUID=AM_DEPLOYMENT_REVISION_MAPPING.REVISION_UUID" + " LEFT JOIN AM_API_DEFAULT_VERSION ON AM_API_DEFAULT_VERSION.API_NAME = AM_API.API_NAME AND " + "AM_API_DEFAULT_VERSION.API_PROVIDER=AM_API.API_PROVIDER AND " + - "AM_API_DEFAULT_VERSION.PUBLISHED_DEFAULT_API_VERSION = AM_API.API_VERSION AND " + "AM_API_DEFAULT_VERSION.ORGANIZATION = AM_API.ORGANIZATION "; public static final String GET_ALL_API_PRODUCT_URI_TEMPLATES_SQL = "SELECT AM_API_URL_MAPPING.URL_MAPPING_ID," + @@ -590,10 +592,12 @@ public class SubscriptionValidationSQLConstants { ".URL_MAPPING_ID WHERE AM_API_URL_MAPPING.URL_MAPPING_ID IN (SELECT URL_MAPPING_ID FROM " + "AM_API_PRODUCT_MAPPING WHERE API_ID = ? )"; public static final String GET_API_BY_CONTEXT_AND_VERSION_SQL = "SELECT AM_API.API_PROVIDER,AM_API.API_NAME," + - "AM_API.CONTEXT,AM_API.API_UUID,AM_API.API_ID,AM_API.API_TIER,AM_API.API_VERSION,AM_API.API_TYPE,AM_API" + - ".STATUS,AM_REVISION.REVISION_UUID AS REVISION_UUID,AM_DEPLOYMENT_REVISION_MAPPING.NAME AS " + - "DEPLOYMENT_NAME,AM_API.ORGANIZATION FROM AM_API LEFT JOIN AM_REVISION ON " + - "AM_API.API_UUID=AM_REVISION.API_UUID LEFT JOIN AM_DEPLOYMENT_REVISION_MAPPING ON " + - "AM_REVISION.REVISION_UUID=AM_DEPLOYMENT_REVISION_MAPPING.REVISION_UUID WHERE AM_API.CONTEXT = ? AND " + - "AM_API.API_VERSION = ?"; + "AM_API.CONTEXT, AM_API.CONTEXT_TEMPLATE, AM_API.API_UUID, AM_API.API_ID,AM_API.API_TIER, " + + "AM_API.API_VERSION,AM_API.API_TYPE, AM_API.STATUS, AM_REVISION.REVISION_UUID AS REVISION_UUID, " + + "AM_DEPLOYMENT_REVISION_MAPPING.NAME AS DEPLOYMENT_NAME,AM_API.ORGANIZATION FROM AM_API LEFT JOIN " + + "AM_REVISION ON AM_API.API_UUID=AM_REVISION.API_UUID LEFT JOIN AM_DEPLOYMENT_REVISION_MAPPING ON " + + "AM_REVISION.REVISION_UUID=AM_DEPLOYMENT_REVISION_MAPPING.REVISION_UUID WHERE " + + "((AM_API.CONTEXT_TEMPLATE IS NULL AND AM_API.CONTEXT = ?) " + + "OR (AM_API.CONTEXT_TEMPLATE IS NOT NULL AND AM_API.CONTEXT = ?)) AND AM_API.API_VERSION = ?"; + } diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIProductVersionComparator.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIProductVersionComparator.java new file mode 100644 index 000000000000..ac32fe39932b --- /dev/null +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIProductVersionComparator.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.carbon.apimgt.impl.utils; + +import org.wso2.carbon.apimgt.api.model.API; +import org.wso2.carbon.apimgt.api.model.APIProduct; + +import java.io.Serializable; +import java.util.Comparator; + +/** + *

Compares APIs by their versions. This comparator supports following version string + * format.

+ *
    + *
  • VersionString := VersionToken+
  • + *
  • VersionToken := VersionNumber | VersionSuffix | VersionNumber VersionSuffix
  • + *
  • VersionNumber := [0-9]+
  • + *
  • VersionSuffix := ~(0-9) AnyChar*
  • + *
+ *

Some example version strings supported by the comparator are given below.

+ *
    + *
  • 1.5
  • + *
  • 2.1.1
  • + *
  • 2.1.2b
  • + *
  • 1.3-SNAPSHOT
  • + *
  • 2.0.0.wso2v4
  • + *
+ *

Version matching is carried out by comparing the version strings token by token. Version + * numbers are compared in the conventional manner and the suffixes are compared + * lexicographically.

+ */ +public class APIProductVersionComparator implements Comparator,Serializable { + + private APIVersionStringComparator stringComparator = new APIVersionStringComparator(); + + @Override + public int compare(APIProduct apiProduct1, APIProduct apiProduct2) { + if (apiProduct1.getId().getProviderName().equals(apiProduct2.getId().getProviderName()) && + apiProduct1.getId().getName().equals(apiProduct2.getId().getName())) { + return stringComparator.compare(apiProduct1.getId().getVersion(), apiProduct2.getId().getVersion()); + } else { + APIProductNameComparator apiproductNameComparator = new APIProductNameComparator(); + return apiproductNameComparator.compare(apiProduct1, apiProduct2); + } + + } +} diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/LifeCycleUtils.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/LifeCycleUtils.java index 45db0603aef3..5a57a201a9e3 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/LifeCycleUtils.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/LifeCycleUtils.java @@ -57,28 +57,26 @@ public static void changeLifecycle(String user, APIProvider apiProvider, String Boolean> checklist) throws APIPersistenceException, APIManagementException { String targetStatus; String apiName = apiTypeWrapper.getName(); - String apiType = apiTypeWrapper.geType(); + String apiType = apiTypeWrapper.getType(); String apiContext = apiTypeWrapper.getContext(); String uuid = apiTypeWrapper.getUuid(); String currentStatus = apiTypeWrapper.getStatus(); targetStatus = LCManagerFactory.getInstance().getLCManager().getStateForTransition(action); - apiPersistence.changeAPILifeCycle(new Organization(orgId), apiTypeWrapper.getUuid(), targetStatus); - if (!apiTypeWrapper.isAPIProduct()) { - API api = apiTypeWrapper.getApi(); - api.setOrganization(orgId); - changeLifeCycle(apiProvider, api, currentStatus, targetStatus, checklist, orgId); - //Sending Notifications to existing subscribers - if (APIConstants.PUBLISHED.equals(targetStatus)) { - sendEmailNotification(api, orgId); - } - } else { - APIProduct apiProduct = apiTypeWrapper.getApiProduct(); - apiProduct.setOrganization(orgId); - changeLifecycle(apiProvider, apiProduct, currentStatus, targetStatus); + + // Update lifecycle state in the registry + updateLifeCycleState(apiProvider, orgId, apiTypeWrapper, checklist, targetStatus, currentStatus); + + //Sending Notifications to existing subscribers + if (APIConstants.PUBLISHED.equals(targetStatus)) { + sendEmailNotification(apiTypeWrapper, orgId); } + + // Change the lifecycle state in the database addLCStateChangeInDatabase(user, apiTypeWrapper, currentStatus, targetStatus, uuid); - // Event need to be sent after database status update. - sendLCStateChangeNotification(apiName, apiType, apiContext, apiTypeWrapper.getId().getVersion(), targetStatus, apiTypeWrapper.getId().getProviderName(), + + // Add LC state change event to the event queue + sendLCStateChangeNotification(apiName, apiType, apiContext, apiTypeWrapper.getId().getVersion(), targetStatus, + apiTypeWrapper.getId().getProviderName(), apiTypeWrapper.getId().getId(), uuid, orgId); // Remove revisions and subscriptions after API retire @@ -102,6 +100,19 @@ public static void changeLifecycle(String user, APIProvider apiProvider, String } } + private static void updateLifeCycleState(APIProvider apiProvider, String orgId, ApiTypeWrapper apiTypeWrapper, + Map checklist, String targetStatus, String currentStatus) throws APIManagementException { + if (!apiTypeWrapper.isAPIProduct()) { + API api = apiTypeWrapper.getApi(); + api.setOrganization(orgId); + changeAPILifeCycle(apiProvider, api, currentStatus, targetStatus, checklist); + } else { + APIProduct apiProduct = apiTypeWrapper.getApiProduct(); + apiProduct.setOrganization(orgId); + changeAPIProductLifecycle(apiProvider, apiProduct, currentStatus, targetStatus, checklist); + } + } + /** * Update the lifecycle of API Product in registry * @@ -110,7 +121,8 @@ public static void changeLifecycle(String user, APIProvider apiProvider, String * @param targetState Target state of the API Product * @throws APIManagementException Exception when updating the lc state of API Product */ - private static void changeLifecycle(APIProvider apiProvider, APIProduct apiProduct, String currentState, String targetState) + private static void changeAPIProductLifecycle(APIProvider apiProvider, APIProduct apiProduct, String currentState, + String targetState, Map checklist) throws APIManagementException { if (targetState != null) { @@ -126,10 +138,13 @@ private static void changeLifecycle(APIProvider apiProvider, APIProduct apiProdu } else { throw new APIManagementException("Invalid Lifecycle status provided for default APIExecutor"); } + + // If the API Product status is CREATED/PROTOTYPED ,check for check list items of lifecycle + executeLifeCycleChecklist(apiProvider, new ApiTypeWrapper(apiProduct), currentState, targetState, checklist); } - private static void changeLifeCycle(APIProvider apiProvider, API api, String currentState, String targetState, - Map checklist, String organization) + private static void changeAPILifeCycle(APIProvider apiProvider, API api, String currentState, String targetState, + Map checklist) throws APIManagementException { String oldStatus = currentState.toUpperCase(); @@ -176,8 +191,8 @@ private static void changeLifeCycle(APIProvider apiProvider, API api, String cur api.getOrganization()); API otherApi = apiProvider.getLightweightAPIByUUID(uuid, api.getOrganization()); APIEvent apiEvent = new APIEvent(UUID.randomUUID().toString(), System.currentTimeMillis(), - APIConstants.EventType.API_UPDATE.name(), APIUtil.getInternalOrganizationId(organization), - organization, + APIConstants.EventType.API_UPDATE.name(), + APIUtil.getInternalOrganizationId(api.getOrganization()), api.getOrganization(), otherApi.getId().getApiName(), otherApi.getId().getId(), otherApi.getUuid(), version, api.getType(), otherApi.getContext(), otherApi.getId().getProviderName(), otherApi.getStatus()); @@ -197,7 +212,6 @@ private static void changeLifeCycle(APIProvider apiProvider, API api, String cur // update api related information for state change updateAPIforStateChange(LifeCycleUtils.apiPersistence, api, currentState, newStatus); - if (log.isDebugEnabled()) { String logMessage = "API related information successfully updated. API Name: " + api.getId().getApiName() + ", API Version " + api.getId().getVersion() + ", API Context: " @@ -208,6 +222,21 @@ private static void changeLifeCycle(APIProvider apiProvider, API api, String cur throw new APIManagementException("Invalid Lifecycle status for default APIExecutor :" + targetState); } + // If the API status is CREATED/PROTOTYPED ,check for check list items of lifecycle + executeLifeCycleChecklist(apiProvider, new ApiTypeWrapper(api), currentState, targetState, checklist); + } + + + private static void executeLifeCycleChecklist(APIProvider apiProvider, ApiTypeWrapper apiTypeWrapper, + String currentState, String targetState, Map checklist) throws APIManagementException { + + String oldStatus = currentState.toUpperCase(); + String newStatus = (null != targetState) ? targetState.toUpperCase() : null; + + boolean isCurrentCreatedOrPrototyped = APIConstants.CREATED.equals(oldStatus) + || APIConstants.PROTOTYPED.equals(oldStatus); + boolean isStateTransitionToPublished = isCurrentCreatedOrPrototyped && APIConstants.PUBLISHED.equals(newStatus); + boolean deprecateOldVersions = false; boolean makeKeysForwardCompatible = true; // If the API status is CREATED/PROTOTYPED ,check for check list items of lifecycle @@ -215,32 +244,79 @@ private static void changeLifeCycle(APIProvider apiProvider, API api, String cur if (checklist != null) { if (checklist.containsKey(APIConstants.DEPRECATE_CHECK_LIST_ITEM)) { deprecateOldVersions = checklist.get(APIConstants.DEPRECATE_CHECK_LIST_ITEM); + } else if (checklist.containsKey(APIConstants.DEPRECATE_CHECK_LIST_ITEM_API_PRODUCT)) { + deprecateOldVersions = checklist.get(APIConstants.DEPRECATE_CHECK_LIST_ITEM_API_PRODUCT); } + if (checklist.containsKey(APIConstants.RESUBSCRIBE_CHECK_LIST_ITEM)) { makeKeysForwardCompatible = !checklist.get(APIConstants.RESUBSCRIBE_CHECK_LIST_ITEM); + } else if (checklist.containsKey(APIConstants.RESUBSCRIBE_CHECK_LIST_ITEM_API_PRODUCT)) { + makeKeysForwardCompatible = !checklist.get(APIConstants.RESUBSCRIBE_CHECK_LIST_ITEM_API_PRODUCT); } } } if (isStateTransitionToPublished) { if (makeKeysForwardCompatible) { - makeAPIKeysForwardCompatible(apiProvider, api); + makeAPIKeysForwardCompatible(apiProvider, apiTypeWrapper); } if (deprecateOldVersions) { - String provider = APIUtil.replaceEmailDomain(api.getId().getProviderName()); - String apiName = api.getId().getName(); - List apiList = getAPIVersionsByProviderAndName(provider, apiName, api.getOrganization()); - APIVersionComparator versionComparator = new APIVersionComparator(); - for (API oldAPI : apiList) { - if (oldAPI.getId().getApiName().equals(api.getId().getApiName()) - && versionComparator.compare(oldAPI, api) < 0 - && (APIConstants.PUBLISHED.equals(oldAPI.getStatus()))) { - apiProvider.changeLifeCycleStatus(organization, new ApiTypeWrapper( - apiProvider.getAPIbyUUID(oldAPI.getUuid(), organization)), - APIConstants.API_LC_ACTION_DEPRECATE, null); + deprecateOldVersions(apiProvider, apiTypeWrapper); + } + } + } + + private static void deprecateOldVersions(APIProvider apiProvider, ApiTypeWrapper apiTypeWrapper) + throws APIManagementException { + + String provider = APIUtil.replaceEmailDomain(apiTypeWrapper.getId().getProviderName()); + if (apiTypeWrapper.isAPIProduct()) { + deprecateOldAPIProductVersions(apiProvider, apiTypeWrapper.getApiProduct(), provider); + } else { + deprecateOldAPIVersions(apiProvider, apiTypeWrapper.getApi(), provider); + } + } + + private static void deprecateOldAPIVersions(APIProvider apiProvider, API api, String provider) + throws APIManagementException { + String apiName = api.getId().getName(); + if (log.isDebugEnabled()) { + log.debug("Deprecating old versions of API " + apiName + " of provider " + provider); + } + + List apiList = getAPIVersionsByProviderAndName(provider, apiName); + APIVersionComparator versionComparator = new APIVersionComparator(); + for (API oldAPI : apiList) { + if (oldAPI.getId().getApiName().equals(api.getId().getName()) + && versionComparator.compare(oldAPI, api) < 0 + && (APIConstants.PUBLISHED.equals(oldAPI.getStatus()))) { + apiProvider.changeLifeCycleStatus(api.getOrganization(), new ApiTypeWrapper( + apiProvider.getAPIbyUUID(oldAPI.getUuid(), api.getOrganization())), + APIConstants.API_LC_ACTION_DEPRECATE, null); + + } + } + } + + private static void deprecateOldAPIProductVersions(APIProvider apiProvider, APIProduct apiProduct, String provider) + throws APIManagementException { + String apiProductName = apiProduct.getId().getName(); + if (log.isDebugEnabled()) { + log.debug( + "Deprecating old versions of APIProduct " + apiProductName + " of provider " + provider); + } + + List apiProductList = getAPIProductVersionsByProviderAndName(provider, apiProductName); + APIProductVersionComparator versionComparator = new APIProductVersionComparator(); + for (APIProduct oldAPIProduct : apiProductList) { + if (oldAPIProduct.getId().getName() + .equals(apiProduct.getId().getName()) && versionComparator.compare(oldAPIProduct, apiProduct) < 0 + && (APIConstants.PUBLISHED.equals( + oldAPIProduct.getState()))) { + apiProvider.changeLifeCycleStatus(apiProduct.getOrganization(), new ApiTypeWrapper( + apiProvider.getAPIbyUUID(oldAPIProduct.getUuid(), + apiProduct.getOrganization())), APIConstants.API_LC_ACTION_DEPRECATE, null); - } - } } } } @@ -248,10 +324,11 @@ private static void changeLifeCycle(APIProvider apiProvider, API api, String cur /** * This method used to send notifications to the previous subscribers of older versions of a given API * - * @param api API object. + * @param apiTypeWrapper apiTypeWrapper object. * @throws APIManagementException */ - private static void sendEmailNotification(API api, String organization) throws APIManagementException { + private static void sendEmailNotification(ApiTypeWrapper apiTypeWrapper, String organization) + throws APIManagementException { try { JSONObject tenantConfig = APIUtil.getTenantConfig(organization); @@ -264,16 +341,16 @@ private static void sendEmailNotification(API api, String organization) throws A if (JavaUtils.isTrueExplicitly(isNotificationEnabled)) { Map subscriberMap = new HashMap<>(); - List apiIdentifiers = getOldPublishedAPIList(api); - for (APIIdentifier oldAPI : apiIdentifiers) { + List identifiers = getOldPublishedAPIOrAPIProductList(apiTypeWrapper); + + for (Identifier identifier : identifiers) { Properties prop = new Properties(); - prop.put(NotifierConstants.API_KEY, oldAPI); - prop.put(NotifierConstants.NEW_API_KEY, api.getId()); + prop.put(NotifierConstants.API_KEY, identifier); + prop.put(NotifierConstants.NEW_API_KEY, apiTypeWrapper.getId()); - Set subscribersOfAPI = apiMgtDAO.getSubscribersOfAPIWithoutDuplicates(oldAPI, + Set subscribersOfAPI = apiMgtDAO.getSubscribersOfAPIWithoutDuplicates(identifier, subscriberMap); prop.put(NotifierConstants.SUBSCRIBERS_PER_API, subscribersOfAPI); - NotificationDTO notificationDTO = new NotificationDTO(prop, NotifierConstants.NOTIFICATION_TYPE_NEW_VERSION); notificationDTO.setTenantID(tenantId); @@ -340,52 +417,102 @@ private static void extractRecommendationDetails(API api, String organization) { /** * This method returns a list of previous versions of a given API * - * @param api + * @param apiTypeWrapper apiTypeWrapper object. * @return oldPublishedAPIList * @throws APIManagementException */ - private static List getOldPublishedAPIList(API api) throws APIManagementException { - List oldPublishedAPIList = new ArrayList(); - List apiList = getAPIVersionsByProviderAndName(api.getId().getProviderName(), api.getId().getName(), - api.getOrganization()); + private static List getOldPublishedAPIOrAPIProductList(ApiTypeWrapper apiTypeWrapper) + throws APIManagementException { + List oldPublishedAPIList = new ArrayList(); APIVersionComparator versionComparator = new APIVersionComparator(); - for (API oldAPI : apiList) { - if (oldAPI.getId().getApiName().equals(api.getId().getApiName()) && - versionComparator.compare(oldAPI, api) < 0 && - (oldAPI.getStatus().equals(APIConstants.PUBLISHED))) { - oldPublishedAPIList.add(oldAPI.getId()); + APIProductVersionComparator apiProductVersionComparator = new APIProductVersionComparator(); + + if (!apiTypeWrapper.isAPIProduct()) { + List apiList; + apiList = getAPIVersionsByProviderAndName(apiTypeWrapper.getId().getProviderName(), + apiTypeWrapper.getId().getName()); + for (API oldAPI : apiList) { + if (oldAPI.getId().getApiName().equals(apiTypeWrapper.getId().getName()) && versionComparator.compare( + oldAPI, apiTypeWrapper.getApi()) < 0 && (oldAPI.getStatus().equals(APIConstants.PUBLISHED))) { + oldPublishedAPIList.add(oldAPI.getId()); + } + } + } else { + List apiProductList = getAPIProductVersionsByProviderAndName( + apiTypeWrapper.getId().getProviderName(), apiTypeWrapper.getId().getName()); + + for (APIProduct oldAPIProduct : apiProductList) { + if (oldAPIProduct.getId().getName() + .equals(apiTypeWrapper.getId().getName()) && apiProductVersionComparator.compare(oldAPIProduct, + apiTypeWrapper.getApiProduct()) < 0 && (oldAPIProduct.getState() + .equals(APIConstants.PUBLISHED))) { + oldPublishedAPIList.add(oldAPIProduct.getId()); + } } } - return oldPublishedAPIList; } - private static List getAPIVersionsByProviderAndName(String provider, String apiName, String organization) + private static List getAPIVersionsByProviderAndName(String provider, String apiName) throws APIManagementException { return apiMgtDAO.getAllAPIVersions(apiName, provider); } - private static void makeAPIKeysForwardCompatible(APIProvider apiProvider, API api) throws APIManagementException { - String provider = api.getId().getProviderName(); - String apiName = api.getId().getApiName(); - Set versions = apiProvider.getAPIVersions(provider, apiName, api.getOrganization()); - APIVersionComparator comparator = new APIVersionComparator(); + private static List getAPIProductVersionsByProviderAndName(String provider, String apiProductName) + throws APIManagementException { + return apiMgtDAO.getAllAPIProductVersions(apiProductName, provider); + } + + private static void makeAPIKeysForwardCompatible(APIProvider apiProvider, ApiTypeWrapper apiTypeWrapper) + throws APIManagementException { + + String provider = apiTypeWrapper.getId().getProviderName(); + String apiName = apiTypeWrapper.getId().getName(); + Set versions = apiProvider.getAPIVersions(provider, apiName, apiTypeWrapper.getOrganization()); + APIVersionComparator apiComparator = new APIVersionComparator(); + APIProductVersionComparator apiProductComparator = new APIProductVersionComparator(); + List sortedAPIs = new ArrayList(); + List sortedAPIProducts = new ArrayList(); for (String version : versions) { - if (version.equals(api.getId().getVersion())) { + if (version.equals(apiTypeWrapper.getId().getVersion())) { continue; } - API otherApi = new API(new APIIdentifier(provider, apiName, version));//getAPI(new APIIdentifier(provider, apiName, version)); - if (comparator.compare(otherApi, api) < 0 && !APIConstants.RETIRED.equals(otherApi.getStatus())) { - sortedAPIs.add(otherApi); + if (!apiTypeWrapper.isAPIProduct()) { + API otherApi = new API(new APIIdentifier(provider, apiName, version)); + if (apiComparator.compare(otherApi, apiTypeWrapper.getApi()) < 0 && + !APIConstants.RETIRED.equals(otherApi.getStatus())) { + sortedAPIs.add(otherApi); + } + } else { + APIProduct otherAPIProduct = new APIProduct(new APIProductIdentifier(provider, apiName, version)); + if (apiProductComparator.compare(otherAPIProduct, apiTypeWrapper.getApiProduct()) < 0 && + !APIConstants.RETIRED.equals(otherAPIProduct.getState())) { + sortedAPIProducts.add(otherAPIProduct); + } } } - // Get the subscriptions from the latest api version first - Collections.sort(sortedAPIs, comparator); - List subscribedAPIS = apiMgtDAO.makeKeysForwardCompatible(new ApiTypeWrapper(api), sortedAPIs); - for (SubscribedAPI subscribedAPI : subscribedAPIS) { - SubscriptionEvent subscriptionEvent = new SubscriptionEvent(APIConstants.EventType.SUBSCRIPTIONS_CREATE.name(), subscribedAPI, APIUtil.getInternalOrganizationId(api.getOrganization()), api.getOrganization()); + if (apiTypeWrapper.isAPIProduct()) { + // Get the subscriptions from the latest api product version first + Collections.sort(sortedAPIProducts, apiProductComparator); + SendNotification(apiMgtDAO.makeKeysForwardCompatibleForNewAPIProductVersion(apiTypeWrapper, sortedAPIProducts), + apiTypeWrapper.getOrganization()); + + } else { + // Get the subscriptions from the latest api version first + Collections.sort(sortedAPIs, apiComparator); + SendNotification(apiMgtDAO.makeKeysForwardCompatibleForNewAPIVersion(apiTypeWrapper, sortedAPIs), + apiTypeWrapper.getOrganization()); + } + } + + private static void SendNotification(List subscribedAPIs, String organization) + throws APIManagementException { + for (SubscribedAPI subscribedAPI : subscribedAPIs) { + SubscriptionEvent subscriptionEvent = new SubscriptionEvent( + APIConstants.EventType.SUBSCRIPTIONS_CREATE.name(), subscribedAPI, + APIUtil.getInternalOrganizationId(organization), organization); APIUtil.sendNotification(subscriptionEvent, APIConstants.NotifierType.SUBSCRIPTIONS.name()); } } diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/APIProviderImplTest.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/APIProviderImplTest.java index cf01b917abef..3aa2b8ed1c5e 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/APIProviderImplTest.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/APIProviderImplTest.java @@ -734,7 +734,7 @@ public void testGetAPIUsageByAPIId() throws APIManagementException, RegistryExce private APIProduct createMockAPIProduct(String provider) { APIProductIdentifier productIdentifier = new APIProductIdentifier(provider, APIConstants.API_PRODUCT, - APIConstants.API_PRODUCT_VERSION); + APIConstants.API_PRODUCT_VERSION_1_0_0); APIProduct apiProduct = new APIProduct(productIdentifier); apiProduct.setContext("/test"); apiProduct.setState(APIConstants.CREATED); diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/dao/test/APIMgtDAOTest.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/dao/test/APIMgtDAOTest.java index e7205b1ebf49..e917900df447 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/dao/test/APIMgtDAOTest.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/test/java/org/wso2/carbon/apimgt/impl/dao/test/APIMgtDAOTest.java @@ -365,7 +365,7 @@ public void testAddGetApplicationByNameWithUserNameNullGroupIdNull() throws Exce } @Test - public void testKeyForwardCompatibility() throws Exception { + public void testKeyForwardCompatibilityWhenNewAPIVersion() throws Exception { List oldApiVersionList = new ArrayList<>(); API apiOld = new API(new APIIdentifier("SUMEDHA", "API1", "V1.0.0")); oldApiVersionList.add(apiOld); @@ -377,7 +377,25 @@ public void testKeyForwardCompatibility() throws Exception { api.setUUID(UUID.randomUUID().toString()); api.getId().setId(apiMgtDAO.addAPI(api, -1234, "testOrg")); ApiTypeWrapper apiTypeWrapper = new ApiTypeWrapper(api); - apiMgtDAO.makeKeysForwardCompatible(apiTypeWrapper, oldApiVersionList); + apiMgtDAO.makeKeysForwardCompatibleForNewAPIVersion(apiTypeWrapper, oldApiVersionList); + } + + @Test + public void testKeyForwardCompatibilityWhenNewAPIProductVersion() throws Exception { + List oldApiProductVersionList = new ArrayList<>(); + APIProduct apiProductOld = new APIProduct(new APIProductIdentifier("SUMEDHA", + "APIPRODUCT1", "V1.0.0")); + oldApiProductVersionList.add(apiProductOld); + + APIProduct apiProduct = new APIProduct(new APIProductIdentifier("SUMEDHA", + "APIPRODUCT1", "V2.0.0")); + apiProduct.setContext("/context1"); + apiProduct.setContextTemplate("/context1/{version}"); + apiProduct.setVersionTimestamp(String.valueOf(System.currentTimeMillis())); + apiProduct.setUuid(UUID.randomUUID().toString()); + apiMgtDAO.addAPIProduct(apiProduct, "testOrg"); + ApiTypeWrapper apiTypeWrapper = new ApiTypeWrapper(apiProduct); + apiMgtDAO.makeKeysForwardCompatibleForNewAPIProductVersion(apiTypeWrapper, oldApiProductVersionList); } @Test @@ -419,8 +437,10 @@ public void testForwardingBlockedAndProdOnlyBlockedSubscriptionsToNewAPIVersion( // once API v2.0.0 is added, v1.0.0 becomes an older version hence add it to oldApiVersionList oldApiVersionList.add(api); + List oldApiProductVersionList = new ArrayList<>(); + ApiTypeWrapper apiTypeWrapper2 = new ApiTypeWrapper(api2); - apiMgtDAO.makeKeysForwardCompatible(apiTypeWrapper2, oldApiVersionList); + apiMgtDAO.makeKeysForwardCompatibleForNewAPIVersion(apiTypeWrapper2, oldApiVersionList); List subscriptionsOfAPI2 = apiMgtDAO.getSubscriptionsOfAPI(apiId2.getApiName(), "V2.0.0", apiId2.getProviderName()); @@ -433,7 +453,8 @@ public void testForwardingBlockedAndProdOnlyBlockedSubscriptionsToNewAPIVersion( "testOrg"); // Add the third version of the API - APIIdentifier apiId3 = new APIIdentifier("subForwardProvider", "SubForwardTestAPI", "V3.0.0"); + APIIdentifier apiId3 = new APIIdentifier("subForwardProvider", "SubForwardTestAPI", + "V3.0.0"); API api3 = new API(apiId3); api3.setContext("/context1"); api3.setContextTemplate("/context1/{version}"); @@ -443,8 +464,7 @@ public void testForwardingBlockedAndProdOnlyBlockedSubscriptionsToNewAPIVersion( oldApiVersionList.add(api2); ApiTypeWrapper apiTypeWrapper3 = new ApiTypeWrapper(api3); - apiMgtDAO.makeKeysForwardCompatible(apiTypeWrapper3, oldApiVersionList); - + apiMgtDAO.makeKeysForwardCompatibleForNewAPIVersion(apiTypeWrapper3, oldApiVersionList); List subscriptionsOfAPI3 = apiMgtDAO.getSubscriptionsOfAPI(apiId1.getApiName(), "V3.0.0", apiId1.getProviderName()); assertEquals(1, subscriptionsOfAPI3.size()); diff --git a/components/apimgt/org.wso2.carbon.apimgt.internal.service/swagger.json b/components/apimgt/org.wso2.carbon.apimgt.internal.service/swagger.json index aee01d85c4ae..9a89d4cfe705 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.internal.service/swagger.json +++ b/components/apimgt/org.wso2.carbon.apimgt.internal.service/swagger.json @@ -1242,8 +1242,8 @@ "type" : "string", "example" : "EXCHANGED", "description" : "The type of the tokens to be used (exchanged or without exchanged). Accepted values are EXCHANGED, DIRECT or BOTH.", - "enum" : [ "EXCHANGED", "DIRECT", "BOTH" ], - "default" : "DIRECT" + "default" : "DIRECT", + "enum" : [ "EXCHANGED", "DIRECT", "BOTH" ] } } }, 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 1ef1d62c5f30..8c6326d9e259 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 @@ -3338,7 +3338,7 @@ public PublisherAPIProductSearchResult searchAPIProductsForPublisher(Organizatio PublisherAPIProductInfo info = new PublisherAPIProductInfo(); info.setProviderName(artifact.getAttribute(APIConstants.API_OVERVIEW_PROVIDER)); - info.setContext(artifact.getAttribute(APIConstants.API_OVERVIEW_CONTEXT)); + info.setContext(artifact.getAttribute(APIConstants.API_OVERVIEW_CONTEXT_TEMPLATE)); info.setId(artifact.getId()); info.setApiProductName(artifact.getAttribute(APIConstants.API_OVERVIEW_NAME)); info.setState(artifact.getAttribute(APIConstants.API_OVERVIEW_STATUS)); @@ -3524,9 +3524,17 @@ public void deleteAPIProduct(Organization org, String apiId) throws APIPersisten String apiProductCollectionPath = APIConstants.API_ROOT_LOCATION + RegistryConstants.PATH_SEPARATOR + identifier.getProviderName() + RegistryConstants.PATH_SEPARATOR + identifier.getName(); if (registry.resourceExists(apiProductCollectionPath)) { - // at the moment product versioning is not supported so we are directly deleting this collection as - // this is known to be empty - registry.delete(apiProductCollectionPath); + Resource apiCollection = registry.get(apiProductCollectionPath); + CollectionImpl collection = (CollectionImpl) apiCollection; + //if there is no other versions of apis delete the directory of the api + if (collection.getChildCount() == 0) { + if (log.isDebugEnabled()) { + log.debug( + "No more versions of the APIProduct found, removing API Product collection " + + "from registry"); + } + registry.delete(apiProductCollectionPath); + } } String productProviderPath = APIConstants.API_ROOT_LOCATION + RegistryConstants.PATH_SEPARATOR diff --git a/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/mapper/APIProductMapper.java b/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/mapper/APIProductMapper.java index 3ea980f95481..beb634f16fa5 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/mapper/APIProductMapper.java +++ b/components/apimgt/org.wso2.carbon.apimgt.persistence/src/main/java/org/wso2/carbon/apimgt/persistence/mapper/APIProductMapper.java @@ -56,6 +56,7 @@ public interface APIProductMapper { //@Mapping(source = "id.apiProductName", target = "apiProductName") //@Mapping(source = "id.version", target = "version") @Mapping(source = "id.providerName", target = "providerName") + @Mapping(source = "id.version", target = "version") @Mapping(source = "thumbnailUrl", target = "thumbnail") @Mapping(source = "availableTiers", target = "availableTierNames") @Mapping(source = "uuid", target = "id") diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductDTO.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductDTO.java index 5ba4ac8b52d5..7c6ce8915944 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductDTO.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductDTO.java @@ -34,11 +34,13 @@ public class APIProductDTO { private String id = null; private String name = null; private String context = null; + private String version = null; private String description = null; private String provider = null; private Boolean hasThumbnail = null; private String state = "CREATED"; private Boolean enableSchemaValidation = null; + private Boolean isDefaultVersion = null; private Boolean isRevision = null; private String revisionedApiProductId = null; private Integer revisionId = null; @@ -255,6 +257,23 @@ public void setContext(String context) { this.context = context; } + /** + **/ + public APIProductDTO version(String version) { + this.version = version; + return this; + } + + + @ApiModelProperty(example = "1.0.0", value = "") + @JsonProperty("version") + @Pattern(regexp="^[^~!@#;:%^*()+={}|\\\\<>\"',&/$\\[\\]\\s+/]+$") @Size(min=1,max=30) public String getVersion() { + return version; + } + public void setVersion(String version) { + this.version = version; + } + /** * A brief description about the API **/ @@ -343,6 +362,23 @@ public void setEnableSchemaValidation(Boolean enableSchemaValidation) { this.enableSchemaValidation = enableSchemaValidation; } + /** + **/ + public APIProductDTO isDefaultVersion(Boolean isDefaultVersion) { + this.isDefaultVersion = isDefaultVersion; + return this; + } + + + @ApiModelProperty(example = "false", value = "") + @JsonProperty("isDefaultVersion") + public Boolean isIsDefaultVersion() { + return isDefaultVersion; + } + public void setIsDefaultVersion(Boolean isDefaultVersion) { + this.isDefaultVersion = isDefaultVersion; + } + /** **/ public APIProductDTO isRevision(Boolean isRevision) { @@ -939,11 +975,13 @@ public boolean equals(java.lang.Object o) { return Objects.equals(id, apIProduct.id) && Objects.equals(name, apIProduct.name) && Objects.equals(context, apIProduct.context) && + Objects.equals(version, apIProduct.version) && Objects.equals(description, apIProduct.description) && Objects.equals(provider, apIProduct.provider) && Objects.equals(hasThumbnail, apIProduct.hasThumbnail) && Objects.equals(state, apIProduct.state) && Objects.equals(enableSchemaValidation, apIProduct.enableSchemaValidation) && + Objects.equals(isDefaultVersion, apIProduct.isDefaultVersion) && Objects.equals(isRevision, apIProduct.isRevision) && Objects.equals(revisionedApiProductId, apIProduct.revisionedApiProductId) && Objects.equals(revisionId, apIProduct.revisionId) && @@ -981,7 +1019,7 @@ public boolean equals(java.lang.Object o) { @Override public int hashCode() { - return Objects.hash(id, name, context, description, provider, hasThumbnail, state, enableSchemaValidation, isRevision, revisionedApiProductId, revisionId, responseCachingEnabled, cacheTimeout, visibility, visibleRoles, visibleTenants, accessControl, accessControlRoles, apiType, transport, tags, policies, apiThrottlingPolicy, authorizationHeader, apiKeyHeader, securityScheme, subscriptionAvailability, subscriptionAvailableTenants, additionalProperties, additionalPropertiesMap, monetization, businessInformation, corsConfiguration, createdTime, lastUpdatedTime, lastUpdatedTimestamp, gatewayVendor, apis, scopes, categories, workflowStatus); + return Objects.hash(id, name, context, version, description, provider, hasThumbnail, state, enableSchemaValidation, isDefaultVersion, isRevision, revisionedApiProductId, revisionId, responseCachingEnabled, cacheTimeout, visibility, visibleRoles, visibleTenants, accessControl, accessControlRoles, apiType, transport, tags, policies, apiThrottlingPolicy, authorizationHeader, apiKeyHeader, securityScheme, subscriptionAvailability, subscriptionAvailableTenants, additionalProperties, additionalPropertiesMap, monetization, businessInformation, corsConfiguration, createdTime, lastUpdatedTime, lastUpdatedTimestamp, gatewayVendor, apis, scopes, categories, workflowStatus); } @Override @@ -992,11 +1030,13 @@ public String toString() { sb.append(" id: ").append(toIndentedString(id)).append("\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append(" context: ").append(toIndentedString(context)).append("\n"); + sb.append(" version: ").append(toIndentedString(version)).append("\n"); sb.append(" description: ").append(toIndentedString(description)).append("\n"); sb.append(" provider: ").append(toIndentedString(provider)).append("\n"); sb.append(" hasThumbnail: ").append(toIndentedString(hasThumbnail)).append("\n"); sb.append(" state: ").append(toIndentedString(state)).append("\n"); sb.append(" enableSchemaValidation: ").append(toIndentedString(enableSchemaValidation)).append("\n"); + sb.append(" isDefaultVersion: ").append(toIndentedString(isDefaultVersion)).append("\n"); sb.append(" isRevision: ").append(toIndentedString(isRevision)).append("\n"); sb.append(" revisionedApiProductId: ").append(toIndentedString(revisionedApiProductId)).append("\n"); sb.append(" revisionId: ").append(toIndentedString(revisionId)).append("\n"); diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductInfoDTO.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductInfoDTO.java index 836ca1083f53..e2c46f41ef95 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductInfoDTO.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/dto/APIProductInfoDTO.java @@ -27,6 +27,7 @@ public class APIProductInfoDTO { private String context = null; private String description = null; private String provider = null; + private String version = null; private Boolean hasThumbnail = null; private String state = null; private List securityScheme = new ArrayList(); @@ -126,6 +127,23 @@ public void setProvider(String provider) { this.provider = provider; } + /** + **/ + public APIProductInfoDTO version(String version) { + this.version = version; + return this; + } + + + @ApiModelProperty(example = "1.0.0", value = "") + @JsonProperty("version") + public String getVersion() { + return version; + } + public void setVersion(String version) { + this.version = version; + } + /** **/ public APIProductInfoDTO hasThumbnail(Boolean hasThumbnail) { @@ -296,6 +314,7 @@ public boolean equals(java.lang.Object o) { Objects.equals(context, apIProductInfo.context) && Objects.equals(description, apIProductInfo.description) && Objects.equals(provider, apIProductInfo.provider) && + Objects.equals(version, apIProductInfo.version) && Objects.equals(hasThumbnail, apIProductInfo.hasThumbnail) && Objects.equals(state, apIProductInfo.state) && Objects.equals(securityScheme, apIProductInfo.securityScheme) && @@ -309,7 +328,7 @@ public boolean equals(java.lang.Object o) { @Override public int hashCode() { - return Objects.hash(id, name, context, description, provider, hasThumbnail, state, securityScheme, gatewayVendor, monetizedInfo, businessOwner, businessOwnerEmail, technicalOwner, technicalOwnerEmail); + return Objects.hash(id, name, context, description, provider, version, hasThumbnail, state, securityScheme, gatewayVendor, monetizedInfo, businessOwner, businessOwnerEmail, technicalOwner, technicalOwnerEmail); } @Override @@ -322,6 +341,7 @@ public String toString() { sb.append(" context: ").append(toIndentedString(context)).append("\n"); sb.append(" description: ").append(toIndentedString(description)).append("\n"); sb.append(" provider: ").append(toIndentedString(provider)).append("\n"); + sb.append(" version: ").append(toIndentedString(version)).append("\n"); sb.append(" hasThumbnail: ").append(toIndentedString(hasThumbnail)).append("\n"); sb.append(" state: ").append(toIndentedString(state)).append("\n"); sb.append(" securityScheme: ").append(toIndentedString(securityScheme)).append("\n"); 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/APIMappingUtil.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/APIMappingUtil.java index 5195ed4b1d68..4c7249a62522 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/APIMappingUtil.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/APIMappingUtil.java @@ -1570,7 +1570,7 @@ private static void setMaxTpsFromModelToApiDTO(API api, APIDTO dto) { * @return REST API DTO representation of API Lifecycle state information */ public static LifecycleStateDTO fromLifecycleModelToDTO(Map apiLCData, - boolean apiOlderVersionExist) { + boolean apiOlderVersionExist, String apiType) { LifecycleStateDTO lifecycleStateDTO = new LifecycleStateDTO(); @@ -1601,7 +1601,12 @@ public static LifecycleStateDTO fromLifecycleModelToDTO(Map apiL } LifecycleStateCheckItemsDTO checkItemsDTO = new LifecycleStateCheckItemsDTO(); - checkItemsDTO.setName(checkListItem.getName()); + if (APIConstants.API_PRODUCT.equals(apiType)) { + checkItemsDTO.setName(checkListItem.getName().replace(APIConstants.API_IDENTIFIER_TYPE, + APIConstants.API_PRODUCT_IDENTIFIER_TYPE)); + } else { + checkItemsDTO.setName(checkListItem.getName()); + } checkItemsDTO.setValue(Boolean.getBoolean(checkListItem.getValue())); //todo: Set targets properly checkItemsDTO.setRequiredStates(new ArrayList<>()); @@ -2271,9 +2276,14 @@ public static APIProductListDTO fromAPIProductListtoDTO(List product APIProductInfoDTO productDto = new APIProductInfoDTO(); productDto.setName(apiProduct.getId().getName()); productDto.setProvider(APIUtil.replaceEmailDomainBack(apiProduct.getId().getProviderName())); - productDto.setContext(apiProduct.getContext()); + String context = apiProduct.getContextTemplate(); + if (context.endsWith(FORWARD_SLASH + RestApiConstants.API_VERSION_PARAM)) { + context = context.replace(FORWARD_SLASH + RestApiConstants.API_VERSION_PARAM, EMPTY_STRING); + } + productDto.setContext(context); productDto.setDescription(apiProduct.getDescription()); productDto.setState(apiProduct.getState()); + productDto.setVersion(apiProduct.getId().getVersion()); productDto.setId(apiProduct.getUuid()); productDto.setHasThumbnail(!StringUtils.isBlank(apiProduct.getThumbnailUrl())); if (apiProduct.getApiSecurity() != null) { @@ -2300,12 +2310,18 @@ public static APIProductDTO fromAPIProducttoDTO(APIProduct product) throws APIMa productDto.setName(product.getId().getName()); productDto.setProvider(APIUtil.replaceEmailDomainBack(product.getId().getProviderName())); productDto.setId(product.getUuid()); - productDto.setContext(product.getContext()); + productDto.setVersion(product.getId().getVersion()); productDto.setDescription(product.getDescription()); productDto.setApiType(APIProductDTO.ApiTypeEnum.fromValue(APIConstants.AuditLogConstants.API_PRODUCT)); productDto.setAuthorizationHeader(product.getAuthorizationHeader()); productDto.setApiKeyHeader(product.getApiKeyHeader()); productDto.setState(product.getState()); + String context = product.getContextTemplate(); + if (context.endsWith(FORWARD_SLASH + RestApiConstants.API_VERSION_PARAM)) { + context = context.replace(FORWARD_SLASH + RestApiConstants.API_VERSION_PARAM, EMPTY_STRING); + } + productDto.setContext(context); + productDto.setIsDefaultVersion(product.isDefaultVersion()); if (product.getGatewayVendor() == null) { productDto.setGatewayVendor(APIConstants.WSO2_GATEWAY_ENVIRONMENT); } else { @@ -2526,12 +2542,13 @@ public static APIProduct fromDTOtoAPIProduct(APIProductDTO dto, String provider) APIProduct product = new APIProduct(); APIProductIdentifier id = new APIProductIdentifier(APIUtil.replaceEmailDomain(provider), dto.getName(), - APIConstants.API_PRODUCT_VERSION); //todo: replace this with dto.getVersion + dto.getVersion()); product.setID(id); product.setUuid(dto.getId()); product.setDescription(dto.getDescription()); String context = dto.getContext(); + final String originalContext = context; if (context.endsWith(FORWARD_SLASH + RestApiConstants.API_VERSION_PARAM)) { context = context.replace(FORWARD_SLASH + RestApiConstants.API_VERSION_PARAM, EMPTY_STRING); @@ -2539,17 +2556,25 @@ public static APIProduct fromDTOtoAPIProduct(APIProductDTO dto, String provider) context = context.startsWith(FORWARD_SLASH) ? context : (FORWARD_SLASH + context); String providerDomain = MultitenantUtils.getTenantDomain(provider); - if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equalsIgnoreCase(providerDomain) && - dto.getId() == null) { + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equalsIgnoreCase(providerDomain) && dto.getId() == null + && !context.contains("/t/" + providerDomain)) { //Create tenant aware context for API context = "/t/" + providerDomain + context; } - product.setType(APIConstants.API_PRODUCT_IDENTIFIER_TYPE.replaceAll("\\s", EMPTY_STRING)); - product.setContext(context); + // This is to support the pluggable version strategy + // if the context does not contain any {version} segment, we use the default version strategy. context = checkAndSetVersionParam(context); product.setContextTemplate(context); + context = updateContextWithVersion(dto.getVersion(), originalContext, context); + product.setContext(context); + + product.setType(APIConstants.API_PRODUCT_IDENTIFIER_TYPE.replaceAll("\\s", EMPTY_STRING)); + if (dto.isIsDefaultVersion() != null) { + product.setDefaultVersion(dto.isIsDefaultVersion()); + } + List apiProductTags = dto.getTags(); Set tagsToReturn = new HashSet<>(apiProductTags); product.addTags(tagsToReturn); 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 a33cd065bb03..2250caf4a002 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 @@ -1841,18 +1841,9 @@ public static APIProduct addAPIProductWithGeneratedSwaggerDefinition(APIProductD apiProductDTO.setApiKeyHeader(APIConstants.API_KEY_HEADER_DEFAULT); } - //Remove the /{version} from the context. - if (context.endsWith("/" + RestApiConstants.API_VERSION_PARAM)) { - context = context.replace("/" + RestApiConstants.API_VERSION_PARAM, ""); - } - //Make sure context starts with "/". ex: /pizzaProduct - context = context.startsWith("/") ? context : ("/" + context); - //Check whether the context already exists - if (apiProvider.isContextExist(context, organization)) { - throw new APIManagementException( - "Error occurred while adding API Product. API Product with the context " + context + " already " + - "exists.", ExceptionCodes.from(ExceptionCodes.API_PRODUCT_CONTEXT_ALREADY_EXISTS, context)); - } + //isDefaultVersion is true for a new API Product. + apiProductDTO.setIsDefaultVersion(true); + checkDuplicateContext(apiProvider, apiProductDTO, username, organization); // Set default gatewayVendor if (apiProductDTO.getGatewayVendor() == null) { @@ -1876,6 +1867,71 @@ public static APIProduct addAPIProductWithGeneratedSwaggerDefinition(APIProductD return createdProduct; } + private static void checkDuplicateContext(APIProvider apiProvider, APIProductDTO apiProductDTO, String username, + String organization) + throws APIManagementException { + + String context = apiProductDTO.getContext(); + //Remove the /{version} from the context. + if (context.endsWith("/" + RestApiConstants.API_VERSION_PARAM)) { + context = context.replace("/" + RestApiConstants.API_VERSION_PARAM, ""); + } + + //Make sure context starts with "/". ex: /pizzaProduct + context = context.startsWith("/") ? context : ("/" + context); + + //Create tenant aware context for API + if (context.startsWith("/t/" + organization)) { + context = context.replace("/t/" + organization, ""); + } + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equalsIgnoreCase(organization) && + !context.contains("/t/" + organization)) { + context = "/t/" + organization + context; + } + + // Check whether the context already exists for migrated API products which were created with + // version appended context + String contextWithVersion = context; + if (contextWithVersion.contains("/" + RestApiConstants.API_VERSION_PARAM)) { + contextWithVersion = contextWithVersion.replace(RestApiConstants.API_VERSION_PARAM, + apiProductDTO.getVersion()); + } else { + contextWithVersion = contextWithVersion + "/" + apiProductDTO.getVersion(); + } + + //Get all existing versions of api product been adding + List apiVersions = apiProvider.getApiVersionsMatchingApiNameAndOrganization(apiProductDTO.getName(), + username, organization); + if (!apiVersions.isEmpty()) { + //If any previous version exists + for (String version : apiVersions) { + if (version.equalsIgnoreCase(apiProductDTO.getVersion())) { + //If version already exists + if (apiProvider.isDuplicateContextTemplateMatchingOrganization(context, organization)) { + throw new APIManagementException( + "Error occurred while adding the API Product. A duplicate API context already exists " + + "for " + context + " in the organization : " + organization, + ExceptionCodes.API_ALREADY_EXISTS); + } else { + throw new APIManagementException( + "Error occurred while adding API Product. API Product with name " + + apiProductDTO.getName() + " already exists with different context " + context + + " in the organization" + " : " + organization, + ExceptionCodes.API_ALREADY_EXISTS); + } + } + } + } else { + //If no any previous version exists + if (apiProvider.isContextExistForAPIProducts(context, contextWithVersion, organization)) { + throw new APIManagementException( + "Error occurred while adding the API Product. A duplicate API context already exists for " + + context + " in the organization" + " : " + organization, ExceptionCodes + .from(ExceptionCodes.API_CONTEXT_ALREADY_EXISTS, context)); + } + } + } + public static boolean isStreamingAPI(APIDTO apidto) { return APIDTO.TypeEnum.WS.equals(apidto.getType()) || APIDTO.TypeEnum.SSE.equals(apidto.getType()) || @@ -1997,15 +2053,16 @@ public static LifecycleStateDTO getLifecycleStateInformation(Identifier identifi APIProvider apiProvider = RestApiCommonUtil.getLoggedInUserProvider(); Map apiLCData = apiProvider.getAPILifeCycleData(identifier.getUUID(), organization); + String apiType; + if (identifier instanceof APIProductIdentifier) { + apiType = APIConstants.API_PRODUCT; + } else { + apiType = APIConstants.API_IDENTIFIER_TYPE; + } + if (apiLCData == null) { - String type; - if (identifier instanceof APIProductIdentifier) { - type = APIConstants.API_PRODUCT; - } else { - type = APIConstants.API_IDENTIFIER_TYPE; - } - throw new APIManagementException("Error while getting lifecycle state for " + type + " with ID " - + identifier, ExceptionCodes.from(ExceptionCodes.LIFECYCLE_STATE_INFORMATION_NOT_FOUND, type, + throw new APIManagementException("Error while getting lifecycle state for " + apiType + " with ID " + + identifier, ExceptionCodes.from(ExceptionCodes.LIFECYCLE_STATE_INFORMATION_NOT_FOUND, apiType, identifier.getUUID())); } else { boolean apiOlderVersionExist = false; @@ -2021,7 +2078,7 @@ public static LifecycleStateDTO getLifecycleStateInformation(Identifier identifi break; } } - return APIMappingUtil.fromLifecycleModelToDTO(apiLCData, apiOlderVersionExist); + return APIMappingUtil.fromLifecycleModelToDTO(apiLCData, apiOlderVersionExist, apiType); } } 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/template/APIConfigContext.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/template/APIConfigContext.java index e72daf72573f..04cfc5c0c448 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/template/APIConfigContext.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/template/APIConfigContext.java @@ -104,11 +104,11 @@ private void setApiProductVelocityContext(APIProduct apiProduct, VelocityContext APIProductIdentifier id = apiProduct.getId(); //set the api name version and context context.put("apiName", id.getName()); - context.put("apiVersion", "1.0.0"); + context.put("apiVersion", id.getVersion()); // We set the context pattern now to support plugable version strategy // context.put("apiContext", api.getContext()); - context.put("apiContext", apiProduct.getContext()); + context.put("apiContext", apiProduct.getContextTemplate()); //the api object will be passed on to the template so it properties can be used to // customise how the synapse config is generated. diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/test/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtilsTest.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/test/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtilsTest.java index 071df0cee43e..368edc25fef2 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/test/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtilsTest.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/test/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtilsTest.java @@ -29,6 +29,7 @@ import org.powermock.modules.junit4.PowerMockRunner; import org.wso2.carbon.apimgt.api.APIManagementException; import org.wso2.carbon.apimgt.api.APIProvider; +import org.wso2.carbon.apimgt.api.FaultGatewaysException; import org.wso2.carbon.apimgt.api.model.APIProduct; import org.wso2.carbon.apimgt.api.model.APIProductIdentifier; import org.wso2.carbon.apimgt.api.model.APIStatus; @@ -36,9 +37,11 @@ import org.wso2.carbon.apimgt.api.model.Tier; import org.wso2.carbon.apimgt.impl.APIConstants; import org.wso2.carbon.apimgt.impl.utils.APIUtil; +import org.wso2.carbon.apimgt.impl.utils.TierNameComparator; import org.wso2.carbon.apimgt.impl.workflow.WorkflowExecutorFactory; import org.wso2.carbon.apimgt.rest.api.common.RestApiCommonUtil; import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.APIDTO; +import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.APIProductDTO; import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.AdvertiseInfoDTO; import java.util.ArrayList; @@ -49,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import static org.mockito.Mockito.when; import static org.wso2.carbon.apimgt.impl.APIConstants.API_DATA_PRODUCTION_ENDPOINTS; @@ -63,8 +67,17 @@ public class PublisherCommonUtilsTest { private static final String API_PRODUCT_NAME = "test"; private static final String API_PRODUCT_VERSION = "1.0.0"; private static final String ORGANIZATION = "carbon.super"; + + private static final String TENANT_ORGANIZATION = "wso2.com"; private static final String UUID = "63e1e37e-a5b8-4be6-86a5-d6ae0749f131"; + private static final String API_PRODUCT_CONTEXT = "/test-context"; + private static final String API_PRODUCT_VERSION_APPENDED_CONTEXT = "/test-context/1.0.0"; + + private static final String API_PRODUCT_CONTEXT_FOR_TENANT = "/t/wso2.com/test-context"; + + private static final String API_PRODUCT_CONTEXT_TEMPLATE = "/test-context/{version}"; + @Test public void testGetInvalidTierNames() throws Exception { @@ -426,4 +439,191 @@ public void testValidateEndpointConfigs() { Assert.assertFalse(flag); } + + @Test + public void testCheckDuplicateContextForExistingVersions() throws APIManagementException { + + APIProductDTO apiProductDTO = getAPIProductDTOForDuplicateContextTest(); + APIProvider apiProvider = Mockito.mock(APIProvider.class); + + List apiVersions = new ArrayList<>(Arrays.asList("1.0.0", "2.0.0", "3.0.0")); + Set tiers = new TreeSet(new TierNameComparator()); + Mockito.when(apiProvider.getApiVersionsMatchingApiNameAndOrganization(API_PRODUCT_NAME, PROVIDER, ORGANIZATION)) + .thenReturn(apiVersions); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_CONTEXT, ORGANIZATION)) + .thenReturn(true); + Mockito.when(apiProvider.getTiers()).thenReturn(tiers); + + PowerMockito.mockStatic(APIUtil.class); + PowerMockito.mockStatic(RestApiCommonUtil.class); + PowerMockito.when(RestApiCommonUtil.getLoggedInUserProvider()).thenReturn(apiProvider); + + String expectedMessage = + "Error occurred while adding the API Product. A duplicate API context already exists for " + + API_PRODUCT_CONTEXT + " in the organization : " + ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for context with "/{version}" + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT_TEMPLATE); + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for context which has version already appended + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_VERSION_APPENDED_CONTEXT); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_VERSION_APPENDED_CONTEXT, + ORGANIZATION)).thenReturn(true); + expectedMessage = "Error occurred while adding the API Product. A duplicate API context already exists for " + + API_PRODUCT_VERSION_APPENDED_CONTEXT + " in the organization : " + ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for tenant context + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT_FOR_TENANT); + Mockito.when(apiProvider.getApiVersionsMatchingApiNameAndOrganization(API_PRODUCT_NAME, PROVIDER, + TENANT_ORGANIZATION)).thenReturn(apiVersions); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_CONTEXT_FOR_TENANT, + TENANT_ORGANIZATION)).thenReturn(true); + expectedMessage = "Error occurred while adding the API Product. A duplicate API context already exists for " + + API_PRODUCT_CONTEXT_FOR_TENANT + " in the organization : " + TENANT_ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, TENANT_ORGANIZATION, expectedMessage); + } + + @Test + public void testCheckDuplicateContextForSimilarAPINameWithDifferentContext() throws APIManagementException { + + APIProductDTO apiProductDTO = getAPIProductDTOForDuplicateContextTest(); + APIProvider apiProvider = Mockito.mock(APIProvider.class); + + List apiVersions = new ArrayList<>(Arrays.asList("1.0.0", "2.0.0", "3.0.0")); + Set tiers = new TreeSet(new TierNameComparator()); + Mockito.when(apiProvider.getApiVersionsMatchingApiNameAndOrganization(API_PRODUCT_NAME, PROVIDER, ORGANIZATION)) + .thenReturn(apiVersions); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_CONTEXT, ORGANIZATION)) + .thenReturn(false); + Mockito.when(apiProvider.getTiers()).thenReturn(tiers); + + PowerMockito.mockStatic(APIUtil.class); + PowerMockito.mockStatic(RestApiCommonUtil.class); + PowerMockito.when(RestApiCommonUtil.getLoggedInUserProvider()).thenReturn(apiProvider); + + String expectedMessage = "Error occurred while adding API Product. API Product with name " + API_PRODUCT_NAME + + " already exists with different context " + API_PRODUCT_CONTEXT + " in the organization : " + + ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for context with "/{version}" + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT_TEMPLATE); + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for context which has version already appended + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_VERSION_APPENDED_CONTEXT); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_VERSION_APPENDED_CONTEXT, + ORGANIZATION)).thenReturn(false); + expectedMessage = "Error occurred while adding API Product. API Product with name " + API_PRODUCT_NAME + + " already exists with different context " + API_PRODUCT_VERSION_APPENDED_CONTEXT + + " in the organization : " + ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for tenant context + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT_FOR_TENANT); + Mockito.when(apiProvider.getApiVersionsMatchingApiNameAndOrganization(API_PRODUCT_NAME, PROVIDER, + TENANT_ORGANIZATION)).thenReturn(apiVersions); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_CONTEXT_FOR_TENANT, + TENANT_ORGANIZATION)).thenReturn(false); + expectedMessage = "Error occurred while adding API Product. API Product with name " + API_PRODUCT_NAME + + " already exists with different context " + API_PRODUCT_CONTEXT_FOR_TENANT + " in the organization : " + + TENANT_ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, TENANT_ORGANIZATION, expectedMessage); + } + + @Test + public void testCheckDuplicateContextForNoPreviousVersions() throws APIManagementException { + + APIProductDTO apiProductDTO = getAPIProductDTOForDuplicateContextTest(); + APIProvider apiProvider = Mockito.mock(APIProvider.class); + + List apiVersions = new ArrayList<>(); + Set tiers = new TreeSet(new TierNameComparator()); + String contextWithVersion = API_PRODUCT_CONTEXT + "/" + API_PRODUCT_VERSION; + Mockito.when(apiProvider.getApiVersionsMatchingApiNameAndOrganization(API_PRODUCT_NAME, PROVIDER, ORGANIZATION)) + .thenReturn(apiVersions); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_CONTEXT, ORGANIZATION)) + .thenReturn(false); + Mockito.when(apiProvider.isContextExistForAPIProducts(API_PRODUCT_CONTEXT, contextWithVersion, ORGANIZATION)) + .thenReturn(true); + Mockito.when(apiProvider.getTiers()).thenReturn(tiers); + + PowerMockito.mockStatic(APIUtil.class); + PowerMockito.mockStatic(RestApiCommonUtil.class); + PowerMockito.when(RestApiCommonUtil.getLoggedInUserProvider()).thenReturn(apiProvider); + + String expectedMessage = + "Error occurred while adding the API Product. A duplicate API context already " + "exists for " + + API_PRODUCT_CONTEXT + " in the organization : " + ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for context with "/{version}" + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT_TEMPLATE); + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for context which has version already appended + contextWithVersion = API_PRODUCT_VERSION_APPENDED_CONTEXT + "/" + API_PRODUCT_VERSION; + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_VERSION_APPENDED_CONTEXT); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_VERSION_APPENDED_CONTEXT, + ORGANIZATION)).thenReturn(false); + Mockito.when(apiProvider.isContextExistForAPIProducts(API_PRODUCT_VERSION_APPENDED_CONTEXT, contextWithVersion, + ORGANIZATION)).thenReturn(true); + expectedMessage = "Error occurred while adding the API Product. A duplicate API context already exists for " + + API_PRODUCT_VERSION_APPENDED_CONTEXT + " in the organization : " + ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, ORGANIZATION, expectedMessage); + + // Test for tenant context + contextWithVersion = API_PRODUCT_CONTEXT_FOR_TENANT + "/" + API_PRODUCT_VERSION; + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT_FOR_TENANT); + Mockito.when(apiProvider.isDuplicateContextTemplateMatchingOrganization(API_PRODUCT_CONTEXT_FOR_TENANT, + TENANT_ORGANIZATION)).thenReturn(false); + Mockito.when(apiProvider.isContextExistForAPIProducts(API_PRODUCT_CONTEXT_FOR_TENANT, contextWithVersion, + TENANT_ORGANIZATION)).thenReturn(true); + expectedMessage = "Error occurred while adding the API Product. A duplicate API context already exists for " + + API_PRODUCT_CONTEXT_FOR_TENANT + " in the organization : " + TENANT_ORGANIZATION; + + testDuplicateContextValidation(apiProductDTO, PROVIDER, TENANT_ORGANIZATION, expectedMessage); + } + + private APIProductDTO getAPIProductDTOForDuplicateContextTest() { + + APIProductDTO apiProductDTO = Mockito.mock(APIProductDTO.class); + Mockito.when(apiProductDTO.getContext()).thenReturn(API_PRODUCT_CONTEXT); + Mockito.when(apiProductDTO.getVersion()).thenReturn(API_PRODUCT_VERSION); + Mockito.when(apiProductDTO.getName()).thenReturn(API_PRODUCT_NAME); + Mockito.when(apiProductDTO.getProvider()).thenReturn(PROVIDER); + Mockito.when(apiProductDTO.getPolicies()).thenReturn(new ArrayList<>()); + Mockito.when(apiProductDTO.getAdditionalProperties()).thenReturn(null); + Mockito.when(apiProductDTO.getVisibility()).thenReturn(APIProductDTO.VisibilityEnum.PUBLIC); + Mockito.when(apiProductDTO.getAuthorizationHeader()).thenReturn(APIConstants.AUTHORIZATION_HEADER_DEFAULT); + return apiProductDTO; + } + + private void testDuplicateContextValidation(APIProductDTO apiProductDTO, String provider, String organization, + String expectedMessage) { + + try { + PublisherCommonUtils.addAPIProductWithGeneratedSwaggerDefinition(apiProductDTO, provider, organization); + Assert.fail("Duplicate context did not get identified"); + } catch (APIManagementException e) { + Assert.assertTrue("Received an incorrect error message", e.getMessage().contains(expectedMessage)); + } catch (FaultGatewaysException e) { + Assert.fail("Received an incorrect exception"); + } + } + } diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApi.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApi.java index 32b907927a47..68d4f85034f8 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApi.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApi.java @@ -146,6 +146,24 @@ public Response createAPIProductRevision(@ApiParam(value = "**API Product ID** c return delegate.createAPIProductRevision(apiProductId, apIRevisionDTO, securityContext); } + @POST + @Path("/copy-api-products") + + @Produces({ "application/json" }) + @ApiOperation(value = "Create a New API Product Version", notes = "This operation can be used to create a new version of an existing API Products. The new version is specified as `newVersion` query parameter. New API Product will be in `CREATED` state. ", response = APIProductDTO.class, authorizations = { + @Authorization(value = "OAuth2Security", scopes = { + @AuthorizationScope(scope = "apim:api_publish", description = "Publish API"), + @AuthorizationScope(scope = "apim:api_manage", description = "Manage all API related operations") + }) + }, tags={ "API Products", }) + @ApiResponses(value = { + @ApiResponse(code = 201, message = "Created. Successful response with the newly created API Product as entity in the body. Location header contains URL of newly created API Product. ", response = APIProductDTO.class), + @ApiResponse(code = 400, message = "Bad Request. Invalid request or validation error.", response = ErrorDTO.class), + @ApiResponse(code = 404, message = "Not Found. The specified resource does not exist.", response = ErrorDTO.class) }) + public Response createNewAPIProductVersion( @NotNull @Size(max=30) @ApiParam(value = "Version of the new API Product.",required=true) @QueryParam("newVersion") String newVersion, @NotNull @ApiParam(value = "**API Product ID** consisting of the **UUID** of the API Product. The combination of the provider, name and the version of the API Product is also accepted as a valid API Product ID. Should be formatted as **provider-name-version**. ",required=true) @QueryParam("apiProductId") String apiProductId, @ApiParam(value = "Specifies whether new API Product should be added as default version.", defaultValue="false") @DefaultValue("false") @QueryParam("defaultVersion") Boolean defaultVersion) throws APIManagementException{ + return delegate.createNewAPIProductVersion(newVersion, apiProductId, defaultVersion, securityContext); + } + @DELETE @Path("/{apiProductId}") diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApiService.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApiService.java index 82f59c1aae6d..3c7edac81857 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApiService.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApiProductsApiService.java @@ -40,6 +40,7 @@ public interface ApiProductsApiService { public Response changeAPIProductLifecycle(String action, String apiProductId, String lifecycleChecklist, String ifMatch, MessageContext messageContext) throws APIManagementException; public Response createAPIProduct(APIProductDTO apIProductDTO, MessageContext messageContext) throws APIManagementException; public Response createAPIProductRevision(String apiProductId, APIRevisionDTO apIRevisionDTO, MessageContext messageContext) throws APIManagementException; + public Response createNewAPIProductVersion(String newVersion, String apiProductId, Boolean defaultVersion, MessageContext messageContext) throws APIManagementException; public Response deleteAPIProduct(String apiProductId, String ifMatch, MessageContext messageContext) throws APIManagementException; public Response deleteAPIProductDocument(String apiProductId, String documentId, String ifMatch, MessageContext messageContext) throws APIManagementException; public Response deleteAPIProductLifecycleStatePendingTasks(String apiProductId, 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/ApiProductsApiServiceImpl.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApiProductsApiServiceImpl.java index ee79494eb075..33d72fa91732 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApiProductsApiServiceImpl.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApiProductsApiServiceImpl.java @@ -27,10 +27,13 @@ import org.apache.cxf.jaxrs.ext.multipart.Attachment; import org.apache.cxf.phase.PhaseInterceptorChain; import org.wso2.carbon.apimgt.api.APIManagementException; +import org.wso2.carbon.apimgt.api.APIMgtResourceAlreadyExistsException; import org.wso2.carbon.apimgt.api.APIProvider; import org.wso2.carbon.apimgt.api.FaultGatewaysException; import org.wso2.carbon.apimgt.api.APIMgtResourceNotFoundException; import org.wso2.carbon.apimgt.api.ExceptionCodes; +import org.wso2.carbon.apimgt.api.model.API; +import org.wso2.carbon.apimgt.api.model.APIIdentifier; import org.wso2.carbon.apimgt.api.model.APIProduct; import org.wso2.carbon.apimgt.api.model.APIProductIdentifier; import org.wso2.carbon.apimgt.api.model.APIStateChangeResponse; @@ -38,11 +41,15 @@ import org.wso2.carbon.apimgt.api.model.ApiTypeWrapper; import org.wso2.carbon.apimgt.api.model.Documentation; import org.wso2.carbon.apimgt.api.model.DocumentationContent; +import org.wso2.carbon.apimgt.api.model.DuplicateAPIException; import org.wso2.carbon.apimgt.api.model.Environment; import org.wso2.carbon.apimgt.api.model.ResourceFile; import org.wso2.carbon.apimgt.api.model.APIRevision; import org.wso2.carbon.apimgt.api.model.APIRevisionDeployment; +import org.wso2.carbon.apimgt.api.model.ServiceEntry; import org.wso2.carbon.apimgt.api.model.SubscribedAPI; +import org.wso2.carbon.apimgt.impl.APIConstants; +import org.wso2.carbon.apimgt.impl.ServiceCatalogImpl; import org.wso2.carbon.apimgt.impl.dao.ApiMgtDAO; import org.wso2.carbon.apimgt.impl.importexport.APIImportExportException; import org.wso2.carbon.apimgt.impl.importexport.ExportFormat; @@ -57,6 +64,7 @@ import org.wso2.carbon.apimgt.rest.api.publisher.v1.common.mappings.APIMappingUtil; import org.wso2.carbon.apimgt.rest.api.publisher.v1.common.mappings.DocumentationMappingUtil; import org.wso2.carbon.apimgt.rest.api.publisher.v1.common.mappings.PublisherCommonUtils; +import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.APIDTO; import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.APIProductDTO; import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.APIProductListDTO; import org.wso2.carbon.apimgt.rest.api.publisher.v1.dto.DocumentDTO; @@ -90,6 +98,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import static org.wso2.carbon.apimgt.api.ExceptionCodes.API_VERSION_ALREADY_EXISTS; import static org.wso2.carbon.apimgt.impl.APIConstants.DOCUMENTATION_INLINE_CONTENT_TYPE; import static org.wso2.carbon.apimgt.impl.APIConstants.UN_AUTHORIZED_ERROR_MESSAGE; @@ -503,7 +512,8 @@ public Response updateAPIProduct(String apiProductId, APIProductDTO body, String if (retrievedProduct == null) { RestApiUtil.handleResourceNotFoundError(RestApiConstants.RESOURCE_API_PRODUCT, apiProductId, log); } - APIProduct updatedProduct = PublisherCommonUtils.updateApiProduct(retrievedProduct, body, apiProvider, username, tenantDomain); + APIProduct updatedProduct = PublisherCommonUtils.updateApiProduct(retrievedProduct, body, + apiProvider, username, tenantDomain); APIProductDTO updatedProductDTO = getAPIProductByID(apiProductId, apiProvider); return Response.ok().entity(updatedProductDTO).build(); } catch (APIManagementException | FaultGatewaysException e) { @@ -1134,4 +1144,50 @@ public Response deleteAPIProductLifecycleStatePendingTasks(String apiProductId, } return null; } + + @Override + public Response createNewAPIProductVersion(String newVersion, String apiProductId, Boolean defaultVersion, + MessageContext messageContext) { + + URI newVersionedApiProductUri; + APIProductDTO newVersionedApiProduct; + + try { + APIProductIdentifier productIdentifier = APIUtil.getAPIProductIdentifierFromUUID(apiProductId); + if (productIdentifier == null) { + throw new APIMgtResourceNotFoundException( + "Couldn't retrieve existing API Product with API Product Id: " + apiProductId, + ExceptionCodes.from(ExceptionCodes.API_NOT_FOUND, apiProductId)); + } + APIProvider apiProvider = RestApiCommonUtil.getLoggedInUserProvider(); + String tenantDomain = RestApiCommonUtil.getLoggedInUserTenantDomain(); + APIProduct versionedAPIProduct = apiProvider.createNewAPIProductVersion(apiProductId, newVersion, + defaultVersion, tenantDomain); + newVersionedApiProduct = APIMappingUtil.fromAPIProducttoDTO(versionedAPIProduct); + newVersionedApiProductUri = new URI( + RestApiConstants.RESOURCE_PATH_API_PRODUCTS + "/" + versionedAPIProduct.getUuid()); + return Response.created(newVersionedApiProductUri).entity(newVersionedApiProduct).build(); + + } catch (APIManagementException e) { + if (RestApiUtil.isDueToResourceAlreadyExists(e)) { + String errorMessage = "Requested new version " + newVersion + " of API Product " + apiProductId + + " already exists"; + RestApiUtil.handleResourceAlreadyExistsError(errorMessage, e, log); + } else if (RestApiUtil.isDueToResourceNotFound(e) || RestApiUtil.isDueToAuthorizationFailure(e)) { + //Auth failure occurs when cross tenant accessing APIs. Sends 404, since we don't need to expose + // the existence of the resource + RestApiUtil.handleResourceNotFoundError(RestApiConstants.RESOURCE_API_PRODUCT, apiProductId, e, log); + } else if (isAuthorizationFailure(e)) { + RestApiUtil.handleAuthorizationFailure("Authorization failure while copying API Product : " + + apiProductId, e, log); + } else { + String errorMessage = "Error while copying API Product : " + apiProductId; + RestApiUtil.handleInternalServerError(errorMessage, e, log); + } + } catch (URISyntaxException e) { + String errorMessage = "Error while retrieving API location of " + apiProductId; + RestApiUtil.handleInternalServerError(errorMessage, e, log); + } + return null; + } } diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml index 3cda113b3788..0e933ccedf02 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml @@ -6574,6 +6574,60 @@ paths: source: 'curl -k -X POST -H "Authorization: Bearer ae4eae22-3f65-387b-a171-d37eaa366fa8" "https://127.0.0.1:9443/api/am/publisher/v4/api-products/890a4f4d-09eb-4877-a323-57f6ce2ed79b/restore-revision?revisionId=e0824883-3e86-403a-aec1-22bbc454eb7c"' + /api-products/copy-api-products: + #-------------------------------------------------------- + # Create new product version from the existing API Product + #-------------------------------------------------------- + post: + tags: + - API Products + summary: Create a New API Product Version + description: | + This operation can be used to create a new version of an existing API Products. The new version is specified as `newVersion` query parameter. New API Product will be in `CREATED` state. + parameters: + - name: newVersion + in: query + description: Version of the new API Product. + required: true + schema: + maxLength: 30 + type: string + - name: defaultVersion + in: query + description: Specifies whether new API Product should be added as default version. + schema: + type: boolean + default: false + - $ref: '#/components/parameters/apiProductId-Q' + responses: + 201: + description: | + Created. + Successful response with the newly created API Product as entity in the body. Location header contains URL of newly created API Product. + headers: + Location: + description: | + The URL of the newly created API Product. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/APIProduct' + 400: + $ref: '#/components/responses/BadRequest' + 404: + $ref: '#/components/responses/NotFound' + security: + - OAuth2Security: + - apim:api_publish + - apim:api_manage + x-code-samples: + - lang: Curl + source: 'curl -k -X POST -H "Authorization: Bearer ae4eae22-3f65-387b-a171-d37eaa366fa8" + "https://127.0.0.1:9443/api/am/publisher/v4/api-products/copy-api-products?newVersion=2.0&defaultVersion=false&apiproductId=2fd14eb8-b828-4013-b448-0739d2e76bf7"' + operationId: createNewAPIProductVersion + /api-products/export: get: tags: @@ -9280,6 +9334,9 @@ components: description: | If the provider value is not given, the user invoking the API will be used as the provider. example: admin + version: + type: string + example: 1.0.0 hasThumbnail: type: boolean example: true @@ -9337,6 +9394,12 @@ components: minLength: 1 type: string example: pizzaproduct + version: + maxLength: 30 + minLength: 1 + type: string + pattern: '^[^~!@#;:%^*()+={}|\\<>"'',&/$\[\]\s+\/]+$' + example: 1.0.0 description: type: string description: A brief description about the API @@ -9359,6 +9422,9 @@ components: enableSchemaValidation: type: boolean example: false + isDefaultVersion: + type: boolean + example: false isRevision: type: boolean example: false diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.store.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/store/v1/mappings/APIMappingUtil.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.store.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/store/v1/mappings/APIMappingUtil.java index fc8967c19c7b..4ae598be28f7 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.store.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/store/v1/mappings/APIMappingUtil.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.store.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/store/v1/mappings/APIMappingUtil.java @@ -288,6 +288,7 @@ public static APIDTO fromAPItoDTO(APIProduct model, String organization) throws dto.setProvider(APIUtil.replaceEmailDomainBack(providerName)); dto.setId(model.getUuid()); dto.setContext(model.getContext()); + dto.setIsDefaultVersion(model.isPublishedDefaultVersion()); dto.setDescription(model.getDescription()); dto.setLifeCycleStatus(model.getState()); dto.setType(model.getType());