From 6e344c158af92f9e136572d2ecb8bb47b764c5c9 Mon Sep 17 00:00:00 2001 From: Chris Wiechmann Date: Fri, 14 Jan 2022 17:10:33 +0100 Subject: [PATCH] Fix: Cache update implemented #253 --- CHANGELOG.md | 7 ++- .../axway/apim/adapter/APIManagerAdapter.java | 18 ++++-- .../apis/APIManagerAPIAccessAdapter.java | 2 +- .../adapter/apis/APIManagerConfigAdapter.java | 2 +- .../adapter/clientApps/APIMgrAppsAdapter.java | 9 +-- .../axway/apim/lib/FilteredCacheManager.java | 58 ++++++++++++++++++- .../apim/apiimport/actions/CreateNewAPI.java | 7 ++- .../actions/RecreateToUpdateAPI.java | 5 ++ 8 files changed, 92 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5b231e8..4ad256120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Backslashes in user passwords are ignored (See issue [#244](https://github.com/Axway-API-Management-Plus/apim-cli/issues/244)) ### Added -- Added updateOnly toggle (See issue [#251](https://github.com/Axway-API-Management-Plus/apim-cli/issues/251)) -- Added support to delete applications from API-Manager -- Added support for API-Method level quotas +- Support to use a cache for import actions (See issue [#253](https://github.com/Axway-API-Management-Plus/apim-cli/issues/253)) +- UpdateOnly toggle (See issue [#251](https://github.com/Axway-API-Management-Plus/apim-cli/issues/251)) +- Support to delete applications from API-Manager +- Support for API-Method level quotas ## [1.6.1] 2021-12-20 diff --git a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/APIManagerAdapter.java b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/APIManagerAdapter.java index cbe1eda12..f37957916 100644 --- a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/APIManagerAdapter.java +++ b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/APIManagerAdapter.java @@ -104,7 +104,7 @@ public class APIManagerAdapter { private static CoreParameters cmd; - private static FilteredCacheManager cacheManager; + public static FilteredCacheManager cacheManager; public APIManagerConfigAdapter configAdapter; public APIManagerCustomPropertiesAdapter customPropertiesAdapter; @@ -126,10 +126,20 @@ public static enum CacheType { oauthClientProviderCache, applicationsCache, applicationsSubscriptionCache, - applicationsQuotaCache, + applicationsQuotaCache(true), applicationsCredentialCache, organizationCache, userCache; + + public boolean supportsImportActions = false; + + private CacheType() { + this.supportsImportActions = false; + } + + private CacheType(boolean supportsImportActions) { + this.supportsImportActions = supportsImportActions; + } } public static synchronized APIManagerAdapter getInstance() throws AppException { @@ -149,9 +159,9 @@ public static synchronized APIManagerAdapter getInstance() throws AppException { public static synchronized void deleteInstance() throws AppException { if(APIManagerAdapter.cacheManager!=null && APIManagerAdapter.cacheManager.getStatus()==Status.AVAILABLE) { - LOG.debug("Closing cache begin"); + LOG.debug("Closing cache ..."); APIManagerAdapter.cacheManager.close(); - LOG.debug("Closing cache end"); + LOG.trace("Cache Closed."); } if(APIManagerAdapter.instance!=null) { if(hasOrgAdmin()) APIManagerAdapter.instance.logoutFromAPIManager(false); // Logout potentially logged in OrgAdmin diff --git a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerAPIAccessAdapter.java b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerAPIAccessAdapter.java index cc9291069..ab990cad4 100644 --- a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerAPIAccessAdapter.java +++ b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerAPIAccessAdapter.java @@ -120,7 +120,7 @@ public List getAPIAccess(AbstractEntity entity, Type type, boolean in for(APIAccess apiAccess : allApiAccess) { API api = APIManagerAdapter.getInstance().apiAdapter.getAPI(new APIFilter.Builder().hasId(apiAccess.getApiId()).build(), false); if(api==null) { - throw new AppException("Unable to find API with ID: " + apiAccess.getApiId() + " referenced by "+type.niceName+": " + entity.getName(), ErrorCode.UNKNOWN_API); + throw new AppException("Unable to find API with ID: " + apiAccess.getApiId() + " referenced by "+type.niceName+": " + entity.getName() + ". You may try again with -clearCache", ErrorCode.UNKNOWN_API); } apiAccess.setApiName(api.getName()); apiAccess.setApiVersion(api.getVersion()); diff --git a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerConfigAdapter.java b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerConfigAdapter.java index 3a4376d91..336340376 100644 --- a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerConfigAdapter.java +++ b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/apis/APIManagerConfigAdapter.java @@ -90,7 +90,7 @@ private void readConfigFromAPIManager(boolean useAdmin) throws AppException { HttpResponse httpResponse = null; try { uri = new URIBuilder(cmd.getAPIManagerURL()).setPath(cmd.getApiBasepath() + "/config").build(); - LOG.debug("Load configuration API-Manager."); + LOG.debug("Load API-Manager configuration."); RestAPICall getRequest = new GETRequest(uri, useAdmin); httpResponse = getRequest.execute(); String response = EntityUtils.toString(httpResponse.getEntity()); diff --git a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/clientApps/APIMgrAppsAdapter.java b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/clientApps/APIMgrAppsAdapter.java index 44b8f8c08..55432769a 100644 --- a/modules/apim-adapter/src/main/java/com/axway/apim/adapter/clientApps/APIMgrAppsAdapter.java +++ b/modules/apim-adapter/src/main/java/com/axway/apim/adapter/clientApps/APIMgrAppsAdapter.java @@ -70,11 +70,14 @@ public class APIMgrAppsAdapter { Cache applicationsCache; Cache applicationsSubscriptionCache; Cache applicationsCredentialCache; + Cache applicationsQuotaCache; public APIMgrAppsAdapter() throws AppException { applicationsCache = APIManagerAdapter.getCache(CacheType.applicationsCache, String.class, String.class); applicationsSubscriptionCache = APIManagerAdapter.getCache(CacheType.applicationsSubscriptionCache, String.class, String.class); applicationsCredentialCache = APIManagerAdapter.getCache(CacheType.applicationsCredentialCache, String.class, String.class); + // Must be refactored to use Quota-Adapter instead of doing this in + applicationsQuotaCache = APIManagerAdapter.getCache(CacheType.applicationsQuotaCache, String.class, String.class); } /** @@ -352,10 +355,6 @@ void addImage(ClientApplication app, boolean addImage) throws Exception { app.setImage(image); } - void addQuota(ClientApplication app, boolean addQuota) { - - } - public ClientApplication updateApplication(ClientApplication desiredApp, ClientApplication actualApp) throws AppException { return createOrUpdateApplication(desiredApp, actualApp); } @@ -594,6 +593,8 @@ private void saveQuota(ClientApplication app, ClientApplication actualApp) throw LOG.error("Error creating/updating application quota. Response-Code: "+statusCode+". Got response: '"+EntityUtils.toString(httpResponse.getEntity())+"'"); throw new AppException("Error creating application' Response-Code: "+statusCode+"", ErrorCode.API_MANAGER_COMMUNICATION); } + // Force reload of this quota next time + applicationsQuotaCache.remove(app.getId()); } catch (Exception e) { throw new AppException("Error creating application quota. Error: " + e.getMessage(), ErrorCode.CANT_CREATE_API_PROXY, e); } finally { diff --git a/modules/apim-adapter/src/main/java/com/axway/apim/lib/FilteredCacheManager.java b/modules/apim-adapter/src/main/java/com/axway/apim/lib/FilteredCacheManager.java index 4fb87111e..a51888970 100644 --- a/modules/apim-adapter/src/main/java/com/axway/apim/lib/FilteredCacheManager.java +++ b/modules/apim-adapter/src/main/java/com/axway/apim/lib/FilteredCacheManager.java @@ -13,7 +13,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.axway.apim.adapter.APIManagerAdapter; import com.axway.apim.adapter.APIManagerAdapter.CacheType; +import com.axway.apim.lib.DoNothingCacheManager.DoNothingCache; +import com.axway.apim.lib.errorHandling.AppException; +import com.axway.apim.lib.errorHandling.ErrorCode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; public class FilteredCacheManager implements CacheManager { @@ -37,7 +46,11 @@ public void setEnabledCaches(List enabledCaches) { if(enabledCaches==null || cacheManager instanceof DoNothingCacheManager) return; this.enabledCaches = new ArrayList(); for(CacheType cacheType : enabledCaches) { - this.enabledCaches.add(cacheType.name()); + if(cacheType.supportsImportActions) { + this.enabledCaches.add(cacheType.name()); + } else { + LOG.error("The cache: " + cacheType.name() + " is currently not supported for import actions."); + } } LOG.info("Enabled caches: " + this.enabledCaches); } @@ -95,5 +108,46 @@ public void removeCache(String arg0) { cacheManager.removeCache(arg0); } - + /** + * There are a number of entities which have references to an API (e.g. QuotaRestrictions). + * These are stored/maintained with their own ID (quotaId) and cached in Ehcache. + * But, if the API-ID changes, the cached reference points to an API that no longer exists. + * This method is used to update all entities in the cache when the API ID of an API + * changes (e.g. with a Replace Action). + * @param oldApiId the ID currently used by the cached entities + * @param newApiId the new ID that must be replaced + * @throws AppException when the cache cannot be updated. + */ + public void flipApiId(String oldApiId, String newApiId) throws AppException { + ObjectMapper mapper = new ObjectMapper(); + Cache appQuotaCached = getCache(CacheType.applicationsQuotaCache.name(), String.class, String.class); + if(appQuotaCached instanceof DoNothingCache) return; + LOG.debug("Updating ApplicationQuotaCache: Flip API-ID: " + oldApiId + " --> " + newApiId); + try { + appQuotaCached.forEach(entry -> { + try { + String cachedValueString = entry.getValue(); + JsonNode cachedValue = mapper.readTree(cachedValueString); + // As System- and App-Default-Quotas are not cached, they can be ignored + if(APIManagerAdapter.APPLICATION_DEFAULT_QUOTA.equals(cachedValue.get("id").asText()) || + APIManagerAdapter.SYSTEM_API_QUOTA.equals(cachedValue.get("id").asText())) { + // Do nothing + } else { + ArrayNode restrictions = cachedValue.withArray("restrictions"); + for(JsonNode restriction : restrictions) { + if(oldApiId.equals(restriction.get("api").asText())) { + ((ObjectNode)restriction).replace("api", new TextNode(newApiId)); + appQuotaCached.replace(entry.getKey(), cachedValue.toString()); + } + } + } + } catch (Exception e) { + throw new RuntimeException("There was an error updating the cache.", e); + } + }); + } catch (Exception e) { + appQuotaCached.clear(); + throw new AppException("Error updating the cache. Cache has been cleared.", ErrorCode.UNXPECTED_ERROR, e); + } + } } diff --git a/modules/apis/src/main/java/com/axway/apim/apiimport/actions/CreateNewAPI.java b/modules/apis/src/main/java/com/axway/apim/apiimport/actions/CreateNewAPI.java index 1c41d4909..394a265bd 100644 --- a/modules/apis/src/main/java/com/axway/apim/apiimport/actions/CreateNewAPI.java +++ b/modules/apis/src/main/java/com/axway/apim/apiimport/actions/CreateNewAPI.java @@ -23,10 +23,11 @@ public class CreateNewAPI { static Logger LOG = LoggerFactory.getLogger(CreateNewAPI.class); + + private API createdAPI = null; public void execute(APIChangeState changes, boolean reCreation) throws AppException { - API createdAPI = null; API desiredAPI = changes.getDesiredAPI(); API actualAPI = changes.getActualAPI(); @@ -91,4 +92,8 @@ public void execute(APIChangeState changes, boolean reCreation) throws AppExcept } } } + + public API getCreatedAPI() { + return createdAPI; + } } diff --git a/modules/apis/src/main/java/com/axway/apim/apiimport/actions/RecreateToUpdateAPI.java b/modules/apis/src/main/java/com/axway/apim/apiimport/actions/RecreateToUpdateAPI.java index cdecbf564..1c58c79be 100644 --- a/modules/apis/src/main/java/com/axway/apim/apiimport/actions/RecreateToUpdateAPI.java +++ b/modules/apis/src/main/java/com/axway/apim/apiimport/actions/RecreateToUpdateAPI.java @@ -3,6 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.axway.apim.adapter.APIManagerAdapter; import com.axway.apim.adapter.APIStatusManager; import com.axway.apim.api.API; import com.axway.apim.apiimport.APIChangeState; @@ -37,6 +38,10 @@ public void execute(APIChangeState changes) throws AppException { LOG.info("New API successfuly created. Going to delete old API: '"+actualAPI.getName()+"' "+actualAPI.getVersion()+" (ID: "+actualAPI.getId()+")"); // Delete the existing old API! new APIStatusManager().update(actualAPI, API.STATE_DELETED, true); + + // Maintain the Ehcache + // All cached entities referencing this API must be updated with the correct API-ID + APIManagerAdapter.cacheManager.flipApiId(changes.getActualAPI().getId(), createNewAPI.getCreatedAPI().getId()); } }