From cf5b30f9b216d837be059282471157b1ff8cda3f Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Wed, 8 Nov 2023 15:01:14 -0600 Subject: [PATCH] Fix uploads; add exception classes; other fixes * Do not send empty layout ("") for new pages * Use application/x-www-form-urlencoded for template create requests * Do not handle tag_list on files as it's not part of the API anymore * Add an Exception hierarchy * Refer to the API using its official name Developer Portal API instead of Content Management API * Remove distinction between ErrorHash and plain Error objects in OpenAPI doc Resolves FwMotion#99 Resolves FwMotion#16 --- README.adoc | 31 +- .../threescale/cms/cli/DeleteCommand.java | 3 + .../threescale/cms/cli/UploadCommand.java | 79 ++-- .../cli/config/ReflectionConfiguration.java | 3 +- .../cms/cli/support/LocalFileVisitor.java | 6 +- .../cms/cli/support/PathRecursionSupport.java | 24 +- docs/cli-usage.adoc | 2 +- docs/concepts.adoc | 2 +- rest-client/pom.xml | 2 +- rest-client/src/main/dokka/module.md | 2 +- rest-client/src/main/dokka/packages.md | 2 +- .../threescale/cms/ThreescaleCmsClient.java | 75 ++-- .../cms/ThreescaleCmsClientFactory.java | 10 +- .../cms/ThreescaleCmsClientImpl.java | 362 +++++++++++------- .../exception/ThreescaleCmsApiException.java | 85 ++++ ...escaleCmsCannotCreateBuiltinException.java | 14 + ...escaleCmsCannotDeleteBuiltinException.java | 28 ++ .../cms/exception/ThreescaleCmsException.java | 16 + .../ThreescaleCmsNonApiException.java | 15 + ...scaleCmsUnexpectedPaginationException.java | 23 ++ .../threescale/cms/mappers/CmsFileMapper.java | 2 - .../threescale/cms/model/CmsBuiltinPage.java | 8 +- .../cms/model/CmsBuiltinPartial.java | 8 +- .../cms/model/CmsBuiltinTemplate.java | 10 + .../threescale/cms/model/CmsFile.java | 15 +- .../threescale/cms/model/CmsLayout.java | 2 + .../threescale/cms/model/CmsObject.java | 9 + .../threescale/cms/model/CmsPage.java | 6 +- .../threescale/cms/model/CmsPartial.java | 6 +- .../threescale/cms/model/CmsSection.java | 4 + .../threescale/cms/model/CmsTemplate.java | 3 + .../AbstractPagedRestApiSpliterator.java | 51 ++- .../cms/support/ApiClientBuilder.java | 6 +- .../cms/support/PagedFilesSpliterator.java | 18 +- .../cms/support/PagedSectionsSpliterator.java | 17 +- .../support/PagedTemplatesSpliterator.java | 21 +- .../main/resources/api-spec/3scale-cms.yaml | 361 ++++++++--------- .../cms/ThreescaleCmsClientImplUnitTest.java | 29 +- .../cms/matchers/CmsFileMatcher.java | 3 +- .../cms/testsupport/FilesApiTestSupport.java | 1 - samples/tekton/task-3scale-cms-copy.yaml | 4 +- samples/tekton/task-3scale-cms.yaml | 4 +- 42 files changed, 845 insertions(+), 527 deletions(-) create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsApiException.java create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotCreateBuiltinException.java create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotDeleteBuiltinException.java create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsException.java create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsNonApiException.java create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsUnexpectedPaginationException.java create mode 100644 rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinTemplate.java diff --git a/README.adoc b/README.adoc index 5414c5b..36aa704 100644 --- a/README.adoc +++ b/README.adoc @@ -1,8 +1,9 @@ = 3scale CMS Tools - +:sectnums: :toc: == Overview + The 3scale CMS tools project provides the ability to interact with 3scale's Content Management System programmatically. The project also demonstrates API-first code generation. @@ -24,21 +25,24 @@ This repository provides the following artifacts: === Command-Line Interface -The 3scale CMS tools project provides a CLI tool that provides a convenient -mechanism for interacting with the 3scale Content Management API. It is -implemented using link:https://quarkus.io[Quarkus] and +The 3scale CMS tools project includes a Command-Line Interface (CLI) tool that +provides a convenient mechanism for interacting with the 3scale Developer Portal +API. It is implemented using link:https://quarkus.io[Quarkus] and link:https://picocli.info[picocli]. +More information on CLI Usage may be found in the +link:docs/cli-usage.adoc[Command-Line Interface Usage] document. + === Container Images Images are available for running the command-line interface in container environments. Images are available in the GitHub Packages repository at -link:https://github.com/orgs/FwMotion/packages?repo_name=3scale-cms[ghcr.io/fwmotion/3scale-cms]. +link:https://github.com/FwMotion/3scale-cms/pkgs/container/3scale-cms[ghcr.io/fwmotion/3scale-cms]. The following image tags are used: -* **VERSION** _(eg, 1.0.0)_: Execution of the CLI using the JVM -* **VERSION-native** _(eg, 1.0.0-native)_: Execution of a native-built CLI +* **VERSION** _(eg, v2.0.1)_: Execution of the CLI using the JVM +* **VERSION-native** _(eg, v2.0.1-native)_: Execution of a native-built CLI * **latest**: The latest version available using JVM-mode builds * **latest-native**: The latest version available using native-mode builds @@ -83,12 +87,13 @@ the available endpoints for managing CMS objects, along with the "provider" endpoint of the Account Management API. This provider endpoint is used for lookup of the Developer Portal base URL and the Developer Portal access code. -=== REST Client JAR +=== REST Client JARs -The 3scale CMS tools project provides a standalone REST client JAR. The JAR is -available in the GitHub Packages maven repository. +The 3scale CMS tools project provides JARs to handle interaction with 3scale's +Developer Portal API. These JARs are available in the GitHub Packages maven +repository. -To use the JAR in a Maven project, first include the following repository +To use a JAR in a Maven project, first include the following repository definition: [source,xml] @@ -110,8 +115,8 @@ definition: ---- -To include the client JAR as a dependency, use the following dependency -definition: +As an example, to include the REST client JAR as a dependency, use the following +dependency definition: [source,xml] ---- diff --git a/cli/src/main/java/com/fwmotion/threescale/cms/cli/DeleteCommand.java b/cli/src/main/java/com/fwmotion/threescale/cms/cli/DeleteCommand.java index bf6d854..5f1ac1f 100644 --- a/cli/src/main/java/com/fwmotion/threescale/cms/cli/DeleteCommand.java +++ b/cli/src/main/java/com/fwmotion/threescale/cms/cli/DeleteCommand.java @@ -4,6 +4,7 @@ import com.fwmotion.threescale.cms.cli.support.CmsObjectPathKeyGenerator; import com.fwmotion.threescale.cms.cli.support.CmsSectionToTopComparator; import com.fwmotion.threescale.cms.cli.support.PathRecursionSupport; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsCannotDeleteBuiltinException; import com.fwmotion.threescale.cms.model.CmsObject; import io.quarkus.logging.Log; import jakarta.annotation.Nonnull; @@ -115,6 +116,8 @@ static void deleteObjects(ThreescaleCmsClient client, CmsObjectPathKeyGenerator Log.info("Deleting " + objectName); try { client.delete(object); + } catch (ThreescaleCmsCannotDeleteBuiltinException e) { + Log.info("Could not delete built-in " + objectName); } catch (Exception e) { // TODO: Handle exceptions properly, and provide better logs // indicating the reason for failure diff --git a/cli/src/main/java/com/fwmotion/threescale/cms/cli/UploadCommand.java b/cli/src/main/java/com/fwmotion/threescale/cms/cli/UploadCommand.java index decf195..770c014 100644 --- a/cli/src/main/java/com/fwmotion/threescale/cms/cli/UploadCommand.java +++ b/cli/src/main/java/com/fwmotion/threescale/cms/cli/UploadCommand.java @@ -3,9 +3,9 @@ import com.fwmotion.threescale.cms.ThreescaleCmsClient; import com.fwmotion.threescale.cms.cli.support.*; import com.fwmotion.threescale.cms.model.*; -import com.redhat.threescale.rest.cms.ApiException; import io.quarkus.logging.Log; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import jakarta.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -108,16 +108,16 @@ public Integer call() throws Exception { if (layoutFilename == null) { newPageLayoutSystemName = treeDetails.getDefaultLayout() .map(CmsLayout::getSystemName) - .orElse(""); + .orElse(null); } else if (StringUtils.isBlank(layoutFilename)) { - newPageLayoutSystemName = ""; + newPageLayoutSystemName = null; } else { CmsObject layout = Optional.ofNullable(localObjectsByPath.get(layoutFilename)) .map(Pair::getLeft) .orElseGet(() -> remoteObjectsByPath.get(layoutFilename)); - if (layout instanceof CmsLayout) { - newPageLayoutSystemName = ((CmsLayout) layout).getSystemName(); + if (layout instanceof CmsLayout cmsLayout) { + newPageLayoutSystemName = cmsLayout.getSystemName(); } else { throw new IllegalArgumentException("Specified layout for new pages is not a layout!"); } @@ -139,11 +139,11 @@ public Integer call() throws Exception { uploadSortComparator = Comparator.comparing(Pair::getLeft, sectionToTopComparator .thenComparing((o1, o2) -> { - if (o1 instanceof CmsLayout && StringUtils.equals(newPageLayoutSystemName, ((CmsLayout) o1).getSystemName())) { + if (o1 instanceof CmsLayout layout1 && StringUtils.equals(newPageLayoutSystemName, layout1.getSystemName())) { return -1; } - if (o2 instanceof CmsLayout && StringUtils.equals(newPageLayoutSystemName, ((CmsLayout) o2).getSystemName())) { + if (o2 instanceof CmsLayout layout2 && StringUtils.equals(newPageLayoutSystemName, layout2.getSystemName())) { return 1; } @@ -168,7 +168,7 @@ public Integer call() throws Exception { return localObjectPair; }) .sorted(uploadSortComparator) - .collect(Collectors.toList()); + .toList(); if (noop) { for (CmsObject object : deleteObjects) { @@ -203,9 +203,9 @@ public Integer call() throws Exception { for (Pair pair : localObjectsToUpload) { CmsObject object = pair.getLeft(); - if (object instanceof CmsTemplate) { + if (object instanceof CmsTemplate cmsTemplate) { Log.info("Publishing " + object.getType() + " " + pathKeyGenerator.generatePathKeyForObject(object) + "..."); - client.publish((CmsTemplate) object); + client.publish(cmsTemplate); } } } @@ -218,35 +218,33 @@ private void performUpload(@Nonnull ThreescaleCmsClient client, @Nonnull CmsObject object, @Nonnull File file, @Nonnull Map> localFilesByPathKey, - @Nonnull Map remoteObjectsByPathKey) throws ApiException { + @Nonnull Map remoteObjectsByPathKey) { String pathKey = pathKeyGenerator.generatePathKeyForObject(object); Log.info("Uploading " + object.getType() + " " + pathKey + "..."); - if (object instanceof CmsSection) { - CmsSection section = (CmsSection) object; + if (object instanceof CmsSection section) { if (section.getParentId() == null && !"/".equals(pathKey)) { section.setParentId(findParentId(pathKey, localFilesByPathKey, remoteObjectsByPathKey)); } client.save(section); - } else if (object instanceof CmsFile) { - CmsFile cmsFile = (CmsFile) object; + } else if (object instanceof CmsFile cmsFile) { if (cmsFile.getSectionId() == null) { cmsFile.setSectionId(findParentId(pathKey, localFilesByPathKey, remoteObjectsByPathKey)); } client.save(cmsFile, file); - } else if (object instanceof CmsTemplate) { - if (object instanceof CmsPage && ((CmsPage) object).getSectionId() == null) { - ((CmsPage) object).setSectionId(findParentId(pathKey, localFilesByPathKey, remoteObjectsByPathKey)); + } else if (object instanceof CmsTemplate template) { + if (template instanceof CmsPage page && page.getSectionId() == null) { + page.setSectionId(findParentId(pathKey, localFilesByPathKey, remoteObjectsByPathKey)); } - client.save((CmsTemplate) object, file); + client.save(template, file); } else { throw new UnsupportedOperationException("Unknown object type " + object.getClass()); } } private Long findParentId(@Nonnull String pathKey, - @Nonnull Map> localFilesByPathKey, - @Nonnull Map remoteObjectsByPathKey) { + @Nonnull Map> localFilesByPathKey, + @Nonnull Map remoteObjectsByPathKey) { String parentPathKey; if (pathKey.endsWith("/")) { parentPathKey = pathKey.substring(0, pathKey.lastIndexOf('/', pathKey.length() - 2) + 1); @@ -268,21 +266,18 @@ private Long findParentId(@Nonnull String pathKey, } parentPathKey = parentPathKey.substring(0, pathKey.lastIndexOf('/', parentPathKey.length() - 2) + 1); - } while (!"".equals(parentPathKey)); + } while (!parentPathKey.isEmpty()); throw new IllegalStateException("Couldn't find any parent section ID... not even root"); } private void setRequiredCreationProperties(@Nonnull CmsObject localObject, - @Nonnull String newPageLayoutSystemName) { - if (localObject instanceof CmsPage) { - CmsPage localPage = (CmsPage) localObject; - - if ("text/html".equals(localPage.getContentType())) { - localPage.setLayout(newPageLayoutSystemName); - } else { - localPage.setLayout(""); - } + @Nullable String newPageLayoutSystemName) { + if (newPageLayoutSystemName != null + && localObject instanceof CmsPage localPage + && "text/html".equals(localPage.getContentType()) + ) { + localPage.setLayout(newPageLayoutSystemName); } } @@ -297,28 +292,26 @@ private void updateObjectId(@Nonnull CmsObject target, // Only set ID... Leave the rest for 3scale to dictate what's updatable // and not on each type of object. - if (target instanceof CmsSection) { - CmsSection targetSection = (CmsSection) target; + if (target instanceof CmsSection targetSection) { CmsSection sourceSection = (CmsSection) source; targetSection.setId(source.getId()); targetSection.setParentId(sourceSection.getParentId()); - } else if (target instanceof CmsFile) { - CmsFile targetFile = (CmsFile) target; + } else if (target instanceof CmsFile targetFile) { CmsFile sourceFile = (CmsFile) source; + targetFile.setId(sourceFile.getId()); targetFile.setSectionId(sourceFile.getSectionId()); - } else if (target instanceof CmsLayout) { - ((CmsLayout) target).setId(source.getId()); - } else if (target instanceof CmsPage) { - CmsPage targetPage = (CmsPage) target; + } else if (target instanceof CmsLayout targetLayout) { + targetLayout.setId(source.getId()); + } else if (target instanceof CmsPage targetPage) { targetPage.setId(source.getId()); - if (source instanceof CmsPage) { - targetPage.setSectionId(((CmsPage) source).getSectionId()); + if (source instanceof CmsPage sourcePage) { + targetPage.setSectionId((sourcePage.getSectionId())); } - } else if (target instanceof CmsPartial) { - ((CmsPartial) target).setId(source.getId()); + } else if (target instanceof CmsPartial targetPartial) { + targetPartial.setId(source.getId()); } else { // Unknown... built-in pages/partials shouldn't come from local // files anyway diff --git a/cli/src/main/java/com/fwmotion/threescale/cms/cli/config/ReflectionConfiguration.java b/cli/src/main/java/com/fwmotion/threescale/cms/cli/config/ReflectionConfiguration.java index 73698da..f4bc82c 100644 --- a/cli/src/main/java/com/fwmotion/threescale/cms/cli/config/ReflectionConfiguration.java +++ b/cli/src/main/java/com/fwmotion/threescale/cms/cli/config/ReflectionConfiguration.java @@ -1,6 +1,7 @@ package com.fwmotion.threescale.cms.cli.config; import com.fwmotion.threescale.cms.mixins.EnumHandlerMixIn; +import com.redhat.threescale.rest.cms.model.Error; import com.redhat.threescale.rest.cms.model.*; import io.quarkus.runtime.annotations.RegisterForReflection; @@ -12,7 +13,7 @@ BuiltinPartial.class, EnumHandler.class, EnumTemplateType.class, - ErrorHash.class, + Error.class, FileCreationRequest.class, FileList.class, FileUpdatableFields.class, diff --git a/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/LocalFileVisitor.java b/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/LocalFileVisitor.java index 4de2779..99129c7 100644 --- a/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/LocalFileVisitor.java +++ b/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/LocalFileVisitor.java @@ -62,7 +62,8 @@ public FileVisitResult preVisitDirectory(@Nonnull Path dir, // If directory is not ignored, try loading .cmsignore Path cmsIgnorePath = dir.resolve(CMSIGNORE_FILENAME); if (cmsIgnorePath.toFile().exists() - && cmsIgnorePath.toFile().canRead()) { + && cmsIgnorePath.toFile().canRead() + ) { readCmsIgnoreFile(cmsIgnorePath); } else { ignoreRules.addLast(Collections.emptyList()); @@ -136,11 +137,10 @@ private boolean testPathIsIgnored(@Nonnull Path path) { Path normalizedPath = path.normalize(); List matchingRules = ignoreRules.stream() - .sequential() .flatMap(Collection::stream) .filter(matcherEntry -> matcherEntry.getLeft().matches(normalizedPath)) .map(Pair::getRight) - .collect(Collectors.toList()); + .toList(); if (matchingRules.isEmpty()) { // Nothing matched, so not ignored diff --git a/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/PathRecursionSupport.java b/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/PathRecursionSupport.java index b3894ee..b8bcfd2 100644 --- a/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/PathRecursionSupport.java +++ b/cli/src/main/java/com/fwmotion/threescale/cms/cli/support/PathRecursionSupport.java @@ -45,7 +45,7 @@ private static void addChildObjectsToList(LinkedList> re Long childParentId = GET_PARENT_ID_FUNCTIONS.getOrDefault(childObject.getClass(), o -> Long.MIN_VALUE) .apply(childObject); - return parentId.equals(childParentId); + return Objects.equals(parentId, childParentId); }) .peek(e -> treeWalker.add(Pair.of(e))) .count()); @@ -72,24 +72,19 @@ public Set calculateSpecifiedPaths(@Nonnull Collection specified validatePaths(specifiedPaths, objectsByPath); - switch (recurseBy) { - case NONE: - return new HashSet<>(specifiedPaths); - - case PATH_PREFIX: - return specifiedPaths.stream() + return switch (recurseBy) { + case NONE -> new HashSet<>(specifiedPaths); + case PATH_PREFIX -> specifiedPaths.stream() .flatMap(pathKey -> { if (objectsByPath.get(pathKey).getType() == ThreescaleObjectType.SECTION) { return objectsByPath.keySet().stream() - .filter(subKey -> StringUtils.startsWith(subKey, pathKey)); + .filter(subKey -> StringUtils.startsWith(subKey, pathKey)); } return Stream.of(pathKey); }) .collect(Collectors.toSet()); - - case PARENT_ID: - return specifiedPaths.stream() + case PARENT_ID -> specifiedPaths.stream() .flatMap(pathKey -> { CmsObject parentObject = objectsByPath.get(pathKey); @@ -99,13 +94,10 @@ public Set calculateSpecifiedPaths(@Nonnull Collection specified addChildObjectsToList(recursingList, objectsByPath); return recursingList.stream() - .map(Pair::getKey); + .map(Pair::getKey); }) .collect(Collectors.toSet()); - - default: - throw new UnsupportedOperationException("Unknown recursion style: " + recurseBy); - } + }; } public enum RecursionOption { diff --git a/docs/cli-usage.adoc b/docs/cli-usage.adoc index 25d3945..7d23e96 100644 --- a/docs/cli-usage.adoc +++ b/docs/cli-usage.adoc @@ -11,7 +11,7 @@ To use the `3scale-cms` command you need to provide a few parameters: - An **ACCESS_TOKEN**, which can be used instead of a PROVIDER_KEY. The access token must be granted permissions to both the Account Management API and the -Content Management API. +Developer Portal API. - The **PROVIDER_KEY**, which can be found in the Account tab of your admin portal (only visible to the users with "admin" role). The PROVIDER_KEY will be ignored if an ACCESS_TOKEN is specified. diff --git a/docs/concepts.adoc b/docs/concepts.adoc index 61d0ce2..37ff633 100644 --- a/docs/concepts.adoc +++ b/docs/concepts.adoc @@ -3,7 +3,7 @@ :sectnums: :toc: -== 3scale Content Management API Concepts +== 3scale Developer Portal API Concepts The 3scale Developer Portal consists of 3 primary types of objects: diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 7afd9d3..f874e9e 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -278,7 +278,7 @@ or docs --> true - false + true false true diff --git a/rest-client/src/main/dokka/module.md b/rest-client/src/main/dokka/module.md index 73e8929..6710836 100644 --- a/rest-client/src/main/dokka/module.md +++ b/rest-client/src/main/dokka/module.md @@ -1,3 +1,3 @@ # Module 3scale-cms-rest-client -A Java library for interacting with the 3scale Content Management API +A Java library for interacting with the 3scale Developer Portal API diff --git a/rest-client/src/main/dokka/packages.md b/rest-client/src/main/dokka/packages.md index c77af8b..e2a4c94 100644 --- a/rest-client/src/main/dokka/packages.md +++ b/rest-client/src/main/dokka/packages.md @@ -11,7 +11,7 @@ developer-friendly model objects and auto-generated REST client objects # Package com.fwmotion.threescale.cms.mixins Support files to instruct Jackson on handling of JSON data when interacting -with the 3scale Content Management API +with the 3scale Developer Portal API # Package com.fwmotion.threescale.cms.model diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClient.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClient.java index 4dad8b4..de68fe6 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClient.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClient.java @@ -1,22 +1,22 @@ package com.fwmotion.threescale.cms; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsCannotDeleteBuiltinException; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsException; import com.fwmotion.threescale.cms.model.*; -import com.redhat.threescale.rest.cms.ApiException; import jakarta.annotation.Nonnull; import java.io.File; import java.io.InputStream; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; public interface ThreescaleCmsClient { - // TODO: Replace all "throws ApiException" with better exception handling - @Nonnull - default Stream streamAllCmsObjects() { + default Stream streamAllCmsObjects() throws ThreescaleCmsException { return Stream.of( streamSections(), streamFiles(), @@ -25,7 +25,7 @@ default Stream streamAllCmsObjects() { } @Nonnull - default List listAllCmsObjects() { + default List listAllCmsObjects() throws ThreescaleCmsException { return streamAllCmsObjects() .collect(Collectors.toList()); } @@ -34,7 +34,7 @@ default List listAllCmsObjects() { Stream streamSections(); @Nonnull - default List listSections() { + default List listSections() throws ThreescaleCmsException { return streamSections() .collect(Collectors.toList()); } @@ -43,86 +43,69 @@ default List listSections() { Stream streamFiles(); @Nonnull - default List listFiles() { + default List listFiles() throws ThreescaleCmsException { return streamFiles() .collect(Collectors.toList()); } @Nonnull - Optional getFileContent(long fileId) throws ApiException; + Optional getFileContent(long fileId) throws ThreescaleCmsException; @Nonnull - default Optional getFileContent(@Nonnull CmsFile file) { + default Optional getFileContent(@Nonnull CmsFile file) throws ThreescaleCmsException { return Optional.of(file) .map(CmsFile::getId) - .flatMap(fileId -> { - try { - return getFileContent(fileId); - } catch (ApiException e) { - // TODO: Create ThreescaleCmsException and throw it instead of RuntimeException - throw new RuntimeException(e); - } - }); + .flatMap(this::getFileContent); } @Nonnull Stream streamTemplates(boolean includeContent); @Nonnull - default List listTemplates(boolean includeContent) { + default List listTemplates(boolean includeContent) throws ThreescaleCmsException { return streamTemplates(includeContent) .collect(Collectors.toList()); } @Nonnull - Optional getTemplateDraft(long templateId) throws ApiException; + Optional getTemplateDraft(long templateId) throws ThreescaleCmsException; @Nonnull - default Optional getTemplateDraft(@Nonnull CmsTemplate template) { + default Optional getTemplateDraft(@Nonnull CmsTemplate template) throws ThreescaleCmsException { return Optional.of(template) .map(CmsTemplate::getId) - .flatMap(templateId -> { - try { - return getTemplateDraft(templateId); - } catch (ApiException e) { - // TODO: Create ThreescaleCmsException and throw it instead of RuntimeException - throw new RuntimeException(e); - } - }); + .flatMap(this::getTemplateDraft); } @Nonnull - Optional getTemplatePublished(long templateId) throws ApiException; + Optional getTemplatePublished(long templateId) throws ThreescaleCmsException; @Nonnull - default Optional getTemplatePublished(@Nonnull CmsTemplate template) { + default Optional getTemplatePublished(@Nonnull CmsTemplate template) throws ThreescaleCmsException { return Optional.of(template) .map(CmsTemplate::getId) - .flatMap(templateId -> { - try { - return getTemplatePublished(templateId); - } catch (ApiException e) { - // TODO: Create ThreescaleCmsException and throw it instead of RuntimeException - throw new RuntimeException(e); - } - }); + .flatMap(this::getTemplatePublished); } - void save(@Nonnull CmsSection section) throws ApiException; + void save(@Nonnull CmsSection section) throws ThreescaleCmsException; - void save(@Nonnull CmsFile file, @Nonnull File fileContent) throws ApiException; + void save(@Nonnull CmsFile file, @Nonnull File fileContent) throws ThreescaleCmsException; - void save(@Nonnull CmsTemplate template, @Nonnull File draft) throws ApiException; + void save(@Nonnull CmsTemplate template, @Nonnull File draft) throws ThreescaleCmsException; - void publish(long templateId) throws ApiException; + void publish(long templateId) throws ThreescaleCmsException; - default void publish(@Nonnull CmsTemplate template) throws ApiException { - publish(template.getId()); + default void publish(@Nonnull CmsTemplate template) throws ThreescaleCmsException { + publish(Objects.requireNonNull(template.getId())); } - void delete(@Nonnull ThreescaleObjectType type, long id) throws ApiException; + void delete(@Nonnull ThreescaleObjectType type, long id) throws ThreescaleCmsException; + + default void delete(@Nonnull CmsObject object) throws ThreescaleCmsException { + if (object.isBuiltin()) { + throw new ThreescaleCmsCannotDeleteBuiltinException(); + } - default void delete(@Nonnull CmsObject object) throws ApiException { if (object.getId() != null) { delete(object.getType(), object.getId()); } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientFactory.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientFactory.java index 42b5c84..a191ff4 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientFactory.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientFactory.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsNonApiException; import com.fwmotion.threescale.cms.support.ApiClientBuilder; import com.redhat.threescale.rest.cms.ApiClient; import com.redhat.threescale.rest.cms.auth.ApiKeyAuth; @@ -34,8 +35,7 @@ private void tryCloseHttpClient() { try { httpClient.close(); } catch (IOException e) { - // TODO: Create ThreescaleCmsException and throw it instead of IllegalStateException - throw new IllegalStateException("Couldn't close old HTTP client", e); + throw new ThreescaleCmsNonApiException("Couldn't close old HTTP client", e); } } httpClient = null; @@ -58,8 +58,7 @@ private CloseableHttpClient getHttpClient() { .build(); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { - // TODO: Create ThreescaleCmsException and throw it instead of IllegalStateException - throw new IllegalStateException("Unable to create insecure HttpClient", e); + throw new ThreescaleCmsNonApiException("Unable to create insecure HttpClient", e); } } else { httpClient = HttpClients.createDefault(); @@ -81,8 +80,7 @@ private ApiClient newApiClient() { Authentication accessTokenAuth = apiClient.getAuthentication("access_token"); ((ApiKeyAuth) accessTokenAuth).setApiKey(apiClient.escapeString(accessToken)); } else { - // TODO: Create ThreescaleCmsException and throw it instead of IllegalStateException - throw new IllegalStateException("Authentication not set for 3scale CMS client; must provide one of: providerKey, accessToken"); + throw new ThreescaleCmsNonApiException("Authentication not set for 3scale CMS client; must provide one of: providerKey, accessToken"); } return apiClient; diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImpl.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImpl.java index 7c54f1a..5fb52bb 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImpl.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImpl.java @@ -1,5 +1,8 @@ package com.fwmotion.threescale.cms; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fwmotion.threescale.cms.exception.*; import com.fwmotion.threescale.cms.mappers.CmsFileMapper; import com.fwmotion.threescale.cms.mappers.CmsSectionMapper; import com.fwmotion.threescale.cms.mappers.CmsTemplateMapper; @@ -12,6 +15,7 @@ import com.redhat.threescale.rest.cms.api.FilesApi; import com.redhat.threescale.rest.cms.api.SectionsApi; import com.redhat.threescale.rest.cms.api.TemplatesApi; +import com.redhat.threescale.rest.cms.model.Error; import com.redhat.threescale.rest.cms.model.*; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -42,156 +46,224 @@ public class ThreescaleCmsClientImpl implements ThreescaleCmsClient { private final FilesApi filesApi; private final SectionsApi sectionsApi; private final TemplatesApi templatesApi; + private final ObjectMapper objectMapper; public ThreescaleCmsClientImpl(@Nonnull FilesApi filesApi, @Nonnull SectionsApi sectionsApi, - @Nonnull TemplatesApi templatesApi) { + @Nonnull TemplatesApi templatesApi, + @Nonnull ObjectMapper objectMapper) { this.filesApi = filesApi; this.sectionsApi = sectionsApi; this.templatesApi = templatesApi; + this.objectMapper = objectMapper; } public ThreescaleCmsClientImpl(@Nonnull ApiClient apiClient) { this(new FilesApi(apiClient), new SectionsApi(apiClient), - new TemplatesApi(apiClient)); + new TemplatesApi(apiClient), + apiClient.getObjectMapper()); + } + + private T handleApiErrors( + @Nonnull ApiBlock apiBlock, + @Nullable ApiExceptionTransformer exceptionTransformer + ) throws ThreescaleCmsApiException { + try { + return apiBlock.callApi(); + } catch (ApiException e) { + throw handleApiException(exceptionTransformer, e); + } + } + + private T handleApiErrors(@Nonnull ApiBlock apiBlock) throws ThreescaleCmsException { + return handleApiErrors(apiBlock, null); + } + + private void handleApiErrors( + @Nonnull VoidApiBlock apiBlock, + @Nullable ApiExceptionTransformer exceptionTransformer + ) throws ThreescaleCmsException { + try { + apiBlock.callApi(); + } catch (ApiException e) { + throw handleApiException(exceptionTransformer, e); + } + } + + private void handleApiErrors(@Nonnull VoidApiBlock apiBlock) throws ThreescaleCmsException { + handleApiErrors(apiBlock, null); + } + + @Nonnull + private ThreescaleCmsApiException handleApiException(ApiExceptionTransformer exceptionTransformer, ApiException e) { + int httpStatus = e.getCode(); + + // Try to deserialize a REST-modeled Error object type + Error responseError; + + try { + responseError = objectMapper.readValue(e.getResponseBody(), Error.class); + } catch (JsonProcessingException jsonProcessingException) { + // If the response body is not parseable into an Error, throw + // the response body as-is. + return new ThreescaleCmsApiException(httpStatus, "Unknown ApiException", e); + } + + ThreescaleCmsApiException apiException = new ThreescaleCmsApiException( + httpStatus, + responseError, + e); + + if (exceptionTransformer == null) { + return apiException; + } + + return exceptionTransformer.transformException(apiException); } @Nonnull @Override public Stream streamSections() { - return StreamSupport.stream(new PagedSectionsSpliterator(sectionsApi), true); + return StreamSupport.stream(new PagedSectionsSpliterator(sectionsApi, objectMapper), true); } @Nonnull @Override public Stream streamFiles() { - return StreamSupport.stream(new PagedFilesSpliterator(filesApi), true); + return StreamSupport.stream(new PagedFilesSpliterator(filesApi, objectMapper), true); } @Nonnull @Override - public Optional getFileContent(long fileId) throws ApiException { - CloseableHttpClient httpClient = filesApi.getApiClient().getHttpClient(); - ModelFile file = filesApi.getFile(fileId); - ProviderAccount account = filesApi.readProviderSettings().getAccount(); - - HttpGet request = new HttpGet(account.getBaseUrl() + file.getPath()); - request.setHeader(HttpHeaders.ACCEPT, "*/*"); - if (StringUtils.isNotEmpty(account.getSiteAccessCode())) { - request.addHeader("Cookie", "access_code=" + account.getSiteAccessCode()); - } - - try { - return httpClient.execute(request, response -> { - if (response == null) { - return Optional.empty(); - } - - // TODO: Validate response headers, status code, etc - - HttpEntity entity = response.getEntity(); - if (entity == null) { - return Optional.empty(); - } + public Optional getFileContent(long fileId) { + return handleApiErrors(() -> { + CloseableHttpClient httpClient = filesApi.getApiClient().getHttpClient(); + ModelFile file = filesApi.getFile(fileId); + ProviderAccount account = filesApi.readProviderSettings().getAccount(); + + HttpGet request = new HttpGet(account.getBaseUrl() + file.getPath()); + request.setHeader(HttpHeaders.ACCEPT, "*/*"); + if (StringUtils.isNotEmpty(account.getSiteAccessCode())) { + request.addHeader("Cookie", "access_code=" + account.getSiteAccessCode()); + } - return Optional.of( - new ByteArrayInputStream(entity.getContent().readAllBytes()) - ); - }); - } catch (IOException e) { - // TODO: Create ThreescaleCmsException and throw it instead of ApiException - throw new ApiException(e); - } + try { + return httpClient.execute(request, response -> { + if (response == null) { + return Optional.empty(); + } + + // TODO: Validate response headers, status code, etc + + HttpEntity entity = response.getEntity(); + if (entity == null) { + return Optional.empty(); + } + + return Optional.of( + new ByteArrayInputStream(entity.getContent().readAllBytes()) + ); + }); + } catch (IOException e) { + throw new ThreescaleCmsNonApiException("IOException while retrieving file content", e); + } + }); } @Nonnull @Override public Stream streamTemplates(boolean includeContent) { - return StreamSupport.stream(new PagedTemplatesSpliterator(templatesApi, includeContent), true); + return StreamSupport.stream(new PagedTemplatesSpliterator(templatesApi, objectMapper, includeContent), true); } @Nonnull @Override - public Optional getTemplateDraft(long templateId) throws ApiException { - Template template = templatesApi.getTemplate(templateId); + public Optional getTemplateDraft(long templateId) { + return handleApiErrors(() -> { + Template template = templatesApi.getTemplate(templateId); - Optional result = Optional.ofNullable(template.getDraft()) - .map(StringUtils::trimToNull) - .map(input -> IOUtils.toInputStream(input, Charset.defaultCharset())); - - // When there's no draft content, the "draft" should be the same as - // the "published" content - if (result.isEmpty()) { - result = Optional.ofNullable(template.getPublished()) + Optional result = Optional.ofNullable(template.getDraft()) .map(StringUtils::trimToNull) .map(input -> IOUtils.toInputStream(input, Charset.defaultCharset())); - } - return result; + // When there's no draft content, the "draft" should be the same as + // the "published" content + if (result.isEmpty()) { + result = Optional.ofNullable(template.getPublished()) + .map(StringUtils::trimToNull) + .map(input -> IOUtils.toInputStream(input, Charset.defaultCharset())); + } + + return result; + }); } @Nonnull @Override - public Optional getTemplatePublished(long templateId) throws ApiException { - Template template = templatesApi.getTemplate(templateId); + public Optional getTemplatePublished(long templateId) { + return handleApiErrors(() -> { + Template template = templatesApi.getTemplate(templateId); - return Optional.ofNullable(template.getPublished()) - .map(input -> IOUtils.toInputStream(input, Charset.defaultCharset())); + return Optional.ofNullable(template.getPublished()) + .map(input -> IOUtils.toInputStream(input, Charset.defaultCharset())); + }); } @Override - public void save(@Nonnull CmsSection section) throws ApiException { - Section restSection = SECTION_MAPPER.toRest(section); - if (section.getId() == null) { - if (StringUtils.isBlank(restSection.getTitle()) - && StringUtils.isNotBlank(restSection.getSystemName())) { - restSection.setTitle(restSection.getSystemName()); - } - - Section response = sectionsApi.createSection( - restSection.getPublic(), - restSection.getTitle(), - restSection.getParentId(), - restSection.getPartialPath(), - restSection.getSystemName()); + public void save(@Nonnull CmsSection section) { + handleApiErrors(() -> { + Section restSection = SECTION_MAPPER.toRest(section); + if (section.getId() == null) { + if (StringUtils.isBlank(restSection.getTitle()) + && StringUtils.isNotBlank(restSection.getSystemName())) { + restSection.setTitle(restSection.getSystemName()); + } - section.setId(response.getId()); - } else { - sectionsApi.updateSection(restSection.getId(), - restSection.getPublic(), - restSection.getTitle(), - restSection.getParentId()); - } + Section response = sectionsApi.createSection( + restSection.getPublic(), + restSection.getTitle(), + restSection.getParentId(), + restSection.getPartialPath(), + restSection.getSystemName()); + + section.setId(response.getId()); + } else { + sectionsApi.updateSection(restSection.getId(), + restSection.getPublic(), + restSection.getTitle(), + restSection.getParentId()); + } + }); } @Override - public void save(@Nonnull CmsFile file, @Nullable File fileContent) throws ApiException { - ModelFile restFile = FILE_MAPPER.toRest(file); - - if (file.getId() == null) { - ModelFile response = filesApi.createFile( - restFile.getSectionId(), - restFile.getPath(), - fileContent, - restFile.getTagList(), - restFile.getDownloadable(), - restFile.getContentType()); - - file.setId(response.getId()); - } else { - filesApi.updateFile(file.getId(), - restFile.getSectionId(), - restFile.getPath(), - restFile.getTagList(), - restFile.getDownloadable(), - fileContent, - restFile.getContentType()); - } + public void save(@Nonnull CmsFile file, @Nullable File fileContent) { + handleApiErrors(() -> { + ModelFile restFile = FILE_MAPPER.toRest(file); + + if (file.getId() == null) { + ModelFile response = filesApi.createFile( + restFile.getSectionId(), + restFile.getPath(), + fileContent, + restFile.getDownloadable(), + restFile.getContentType()); + + file.setId(response.getId()); + } else { + filesApi.updateFile(file.getId(), + restFile.getSectionId(), + restFile.getPath(), + restFile.getDownloadable(), + fileContent, + restFile.getContentType()); + } + }); } @Override - public void save(@Nonnull CmsTemplate template, @Nullable File templateDraft) throws ApiException { + public void save(@Nonnull CmsTemplate template, @Nullable File templateDraft) { /* When upgraded to JDK21: switch (template) { case CmsBuiltinPage cmsBuiltinPage -> saveBuiltinPage(cmsBuiltinPage, templateDraft); @@ -217,9 +289,9 @@ public void save(@Nonnull CmsTemplate template, @Nullable File templateDraft) th } } - private void saveBuiltinPage(@Nonnull CmsBuiltinPage page, @Nullable File templateDraft) throws ApiException { + private void saveBuiltinPage(@Nonnull CmsBuiltinPage page, @Nullable File templateDraft) { if (page.getId() == null) { - throw new IllegalArgumentException("Built-in pages cannot be created."); + throw new ThreescaleCmsCannotCreateBuiltinException("Built-in pages can't be created."); } saveUpdatedTemplate(page.getId(), @@ -227,9 +299,9 @@ private void saveBuiltinPage(@Nonnull CmsBuiltinPage page, @Nullable File templa templateDraft); } - private void saveBuiltinPartial(@Nonnull CmsBuiltinPartial partial, @Nullable File templateDraft) throws ApiException { + private void saveBuiltinPartial(@Nonnull CmsBuiltinPartial partial, @Nullable File templateDraft) { if (partial.getId() == null) { - throw new IllegalArgumentException("Built-in partials cannot be created."); + throw new ThreescaleCmsCannotCreateBuiltinException("Built-in partials cannot be created."); } saveUpdatedTemplate(partial.getId(), @@ -237,7 +309,7 @@ private void saveBuiltinPartial(@Nonnull CmsBuiltinPartial partial, @Nullable Fi templateDraft); } - private void saveLayout(@Nonnull CmsLayout layout, @Nullable File templateDraft) throws ApiException { + private void saveLayout(@Nonnull CmsLayout layout, @Nullable File templateDraft) { if (layout.getId() == null) { Template response = saveNewTemplate( TEMPLATE_MAPPER.toRestLayoutCreation(layout), @@ -251,7 +323,7 @@ private void saveLayout(@Nonnull CmsLayout layout, @Nullable File templateDraft) } } - private void savePage(@Nonnull CmsPage page, @Nullable File templateDraft) throws ApiException { + private void savePage(@Nonnull CmsPage page, @Nullable File templateDraft) { if (page.getId() == null) { Template response = saveNewTemplate( TEMPLATE_MAPPER.toRestPageCreation(page), templateDraft); @@ -264,7 +336,7 @@ private void savePage(@Nonnull CmsPage page, @Nullable File templateDraft) throw } } - private void savePartial(@Nonnull CmsPartial partial, @Nullable File templateDraft) throws ApiException { + private void savePartial(@Nonnull CmsPartial partial, @Nullable File templateDraft) { if (partial.getId() == null) { Template response = saveNewTemplate( TEMPLATE_MAPPER.toRestPartialCreation(partial), @@ -278,7 +350,7 @@ private void savePartial(@Nonnull CmsPartial partial, @Nullable File templateDra } } - private Template saveNewTemplate(@Nonnull TemplateCreationRequest template, @Nullable File templateDraft) throws ApiException { + private Template saveNewTemplate(@Nonnull TemplateCreationRequest template, @Nullable File templateDraft) { if (templateDraft == null) { throw new IllegalArgumentException("New template must have draft content"); } @@ -287,11 +359,10 @@ private Template saveNewTemplate(@Nonnull TemplateCreationRequest template, @Nul try { draft = FileUtils.readFileToString(templateDraft, Charset.defaultCharset()); } catch (IOException e) { - // TODO: Create ThreescaleCmsException and throw it instead of RuntimeException - throw new RuntimeException(e); + throw new ThreescaleCmsNonApiException("Exception while reading file content for template draft", e); } - return templatesApi.createTemplate(template.getType(), + return handleApiErrors(() -> templatesApi.createTemplate(template.getType(), template.getSystemName(), template.getTitle(), template.getPath(), @@ -301,13 +372,11 @@ private Template saveNewTemplate(@Nonnull TemplateCreationRequest template, @Nul template.getLayoutId(), template.getLiquidEnabled(), template.getHandler(), - template.getContentType()); - + template.getContentType())); } @SuppressWarnings("UnusedReturnValue") - private Template saveUpdatedTemplate(long id, @Nonnull TemplateUpdatableFields template, @Nullable File templateDraft) throws ApiException { - + private Template saveUpdatedTemplate(long id, @Nonnull TemplateUpdatableFields template, @Nullable File templateDraft) { String draft; if (templateDraft == null) { draft = null; @@ -315,12 +384,11 @@ private Template saveUpdatedTemplate(long id, @Nonnull TemplateUpdatableFields t try { draft = FileUtils.readFileToString(templateDraft, Charset.defaultCharset()); } catch (IOException e) { - // TODO: Create ThreescaleCmsException and throw it instead of RuntimeException - throw new RuntimeException(e); + throw new ThreescaleCmsNonApiException("Exception while reading file content for template draft", e); } } - return templatesApi.updateTemplate(id, + return handleApiErrors(() -> templatesApi.updateTemplate(id, template.getSystemName(), template.getTitle(), template.getPath(), @@ -330,29 +398,61 @@ private Template saveUpdatedTemplate(long id, @Nonnull TemplateUpdatableFields t template.getLayoutId(), template.getLiquidEnabled(), template.getHandler(), - template.getContentType()); + template.getContentType())); } @Override - public void publish(long templateId) throws ApiException { - templatesApi.publishTemplate(templateId); + public void publish(long templateId) throws ThreescaleCmsApiException { + handleApiErrors(() -> templatesApi.publishTemplate(templateId)); } @Override - public void delete(@Nonnull ThreescaleObjectType type, long id) throws ApiException { - switch (type) { - case SECTION: - sectionsApi.deleteSection(id); - break; - case FILE: - filesApi.deleteFile(id); - break; - case TEMPLATE: - templatesApi.deleteTemplate(id); - break; - default: - throw new UnsupportedOperationException("Unknown type: " + type); - } + public void delete(@Nonnull ThreescaleObjectType type, long id) throws ThreescaleCmsApiException { + handleApiErrors( + () -> { + switch (type) { + case SECTION: + sectionsApi.deleteSection(id); + break; + case FILE: + filesApi.deleteFile(id); + break; + case TEMPLATE: + templatesApi.deleteTemplate(id); + break; + default: + throw new UnsupportedOperationException("Unknown type: " + type); + } + }, + apiException -> { + if (apiException.getHttpStatus() == ThreescaleCmsCannotDeleteBuiltinException.ERROR_HTTP_CODE + && apiException.getApiError() + .filter(apiError -> ThreescaleCmsCannotDeleteBuiltinException.ERROR_MESSAGE.equals(apiError.getError())) + .isPresent() + ) { + return new ThreescaleCmsCannotDeleteBuiltinException( + apiException.getApiError().get() + ); + } + + return apiException; + } + ); + } + + @FunctionalInterface + private interface VoidApiBlock { + void callApi() throws ApiException; + } + + @FunctionalInterface + private interface ApiBlock { + T callApi() throws ApiException; + } + + @FunctionalInterface + private interface ApiExceptionTransformer { + T transformException(ThreescaleCmsApiException e); } } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsApiException.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsApiException.java new file mode 100644 index 0000000..ea3dc7f --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsApiException.java @@ -0,0 +1,85 @@ +package com.fwmotion.threescale.cms.exception; + +import com.redhat.threescale.rest.cms.model.Error; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import java.util.Optional; + +/** + * Common base exception type for all 3scale CMS-related API exceptions + */ +public class ThreescaleCmsApiException extends ThreescaleCmsException { + + private final int httpStatus; + private final Error apiError; + + public ThreescaleCmsApiException( + int httpStatus, + @Nullable Error apiError, + @Nullable String message + ) { + super(message); + this.httpStatus = httpStatus; + this.apiError = apiError; + } + + public ThreescaleCmsApiException( + int httpStatus, + @Nullable String message + ) { + this(httpStatus, null, message); + } + + public ThreescaleCmsApiException( + int httpStatus, + @Nullable Error apiError + ) { + this(httpStatus, + apiError, + Optional.ofNullable(apiError) + .map(Error::getError) + .orElse(null)); + } + + public ThreescaleCmsApiException( + int httpStatus, + @Nullable Error apiError, + @Nullable String message, + @Nonnull Throwable cause + ) { + super(message, cause); + this.httpStatus = httpStatus; + this.apiError = apiError; + } + + public ThreescaleCmsApiException( + int httpStatus, + @Nullable String message, + @Nonnull Throwable cause + ) { + this(httpStatus, null, message, cause); + } + + public ThreescaleCmsApiException( + int httpStatus, + @Nullable Error apiError, + @Nonnull Throwable cause + ) { + this(httpStatus, + apiError, + Optional.ofNullable(apiError) + .map(Error::getError) + .orElse(null), + cause); + } + + public int getHttpStatus() { + return httpStatus; + } + + @Nonnull + public Optional getApiError() { + return Optional.ofNullable(apiError); + } +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotCreateBuiltinException.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotCreateBuiltinException.java new file mode 100644 index 0000000..29dd2bf --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotCreateBuiltinException.java @@ -0,0 +1,14 @@ +package com.fwmotion.threescale.cms.exception; + +import jakarta.annotation.Nonnull; +import org.apache.hc.core5.http.HttpStatus; + +public class ThreescaleCmsCannotCreateBuiltinException extends ThreescaleCmsApiException { + + public static final int ERROR_HTTP_STATUS = HttpStatus.SC_BAD_REQUEST; + + public ThreescaleCmsCannotCreateBuiltinException(@Nonnull String message) { + super(ERROR_HTTP_STATUS, message); + } + +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotDeleteBuiltinException.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotDeleteBuiltinException.java new file mode 100644 index 0000000..607b8fd --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsCannotDeleteBuiltinException.java @@ -0,0 +1,28 @@ +package com.fwmotion.threescale.cms.exception; + +import com.redhat.threescale.rest.cms.model.Error; +import jakarta.annotation.Nonnull; +import org.apache.hc.core5.http.HttpStatus; + +public class ThreescaleCmsCannotDeleteBuiltinException extends ThreescaleCmsApiException { + + /** + * The HTTP status code sent by 3scale when this error occurs + */ + public static final int ERROR_HTTP_CODE = HttpStatus.SC_UNPROCESSABLE_ENTITY; + + /** + * The error message sent by 3scale to indicate this type of error + */ + public static final String ERROR_MESSAGE = "Built-in resources can't be deleted"; + + public ThreescaleCmsCannotDeleteBuiltinException() { + super(ERROR_HTTP_CODE, + new Error() + .error(ERROR_MESSAGE)); + } + public ThreescaleCmsCannotDeleteBuiltinException(@Nonnull Error apiError) { + super(HttpStatus.SC_UNPROCESSABLE_ENTITY, + apiError); + } +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsException.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsException.java new file mode 100644 index 0000000..364b412 --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsException.java @@ -0,0 +1,16 @@ +package com.fwmotion.threescale.cms.exception; + +/** + * Common base exception type for all 3scale CMS-related exceptions + */ +public class ThreescaleCmsException extends RuntimeException { + + protected ThreescaleCmsException(String message) { + super(message); + } + + protected ThreescaleCmsException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsNonApiException.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsNonApiException.java new file mode 100644 index 0000000..dddb532 --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsNonApiException.java @@ -0,0 +1,15 @@ +package com.fwmotion.threescale.cms.exception; + +/** + * Base class for exceptions not directly related to the CMS API, such as + * IO Exceptions + */ +public class ThreescaleCmsNonApiException extends ThreescaleCmsException { + public ThreescaleCmsNonApiException(String message) { + super(message); + } + + public ThreescaleCmsNonApiException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsUnexpectedPaginationException.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsUnexpectedPaginationException.java new file mode 100644 index 0000000..f00794f --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/exception/ThreescaleCmsUnexpectedPaginationException.java @@ -0,0 +1,23 @@ +package com.fwmotion.threescale.cms.exception; + +import jakarta.annotation.Nonnull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import org.apache.hc.core5.http.HttpStatus; + +public class ThreescaleCmsUnexpectedPaginationException extends ThreescaleCmsApiException { + + public ThreescaleCmsUnexpectedPaginationException( + @Nonnull String type, + @PositiveOrZero int pageNumber, + @Positive int requestedPageSize, + @Positive int expectedPageSize, + @Positive int actualPageSize + ) { + super(HttpStatus.SC_OK, + "Unexpected page size for " + type + " list page " + pageNumber + + " (with page size of " + requestedPageSize + + "); parsed page size is " + actualPageSize + + " but expected size of " + expectedPageSize); + } +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/mappers/CmsFileMapper.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/mappers/CmsFileMapper.java index 5308a9c..121914e 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/mappers/CmsFileMapper.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/mappers/CmsFileMapper.java @@ -15,10 +15,8 @@ @Mapper public interface CmsFileMapper { - @Mapping(target = "tags", source = "tagList") CmsFile fromRest(ModelFile file); - @Mapping(target = "tagList", source = "tags") // title is only the filename without path... not useful @Mapping(target = "title", ignore = true) // url is the S3 or local filesystem location for 3scale... not useful diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPage.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPage.java index 358d25f..47d0028 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPage.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPage.java @@ -1,12 +1,13 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import java.time.OffsetDateTime; -public class CmsBuiltinPage implements CmsTemplate { +public class CmsBuiltinPage implements CmsBuiltinTemplate { private OffsetDateTime createdAt; private OffsetDateTime updatedAt; @@ -38,6 +39,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; @@ -115,9 +117,7 @@ public void setTitle(String title) { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof CmsBuiltinPage)) return false; - - CmsBuiltinPage that = (CmsBuiltinPage) o; + if (!(o instanceof CmsBuiltinPage that)) return false; return new EqualsBuilder().append(getId(), that.getId()).isEquals(); } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPartial.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPartial.java index 72f74a0..fee14db 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPartial.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinPartial.java @@ -1,12 +1,13 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import java.time.OffsetDateTime; -public class CmsBuiltinPartial implements CmsTemplate { +public class CmsBuiltinPartial implements CmsBuiltinTemplate { private OffsetDateTime createdAt; private OffsetDateTime updatedAt; @@ -34,6 +35,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; @@ -79,9 +81,7 @@ public void setLiquidEnabled(Boolean liquidEnabled) { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof CmsBuiltinPartial)) return false; - - CmsBuiltinPartial that = (CmsBuiltinPartial) o; + if (!(o instanceof CmsBuiltinPartial that)) return false; return new EqualsBuilder().append(getId(), that.getId()).isEquals(); } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinTemplate.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinTemplate.java new file mode 100644 index 0000000..59eb0de --- /dev/null +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsBuiltinTemplate.java @@ -0,0 +1,10 @@ +package com.fwmotion.threescale.cms.model; + +public interface CmsBuiltinTemplate extends CmsTemplate { + + @Override + default boolean isBuiltin() { + return true; + } + +} diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsFile.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsFile.java index f8d495f..ca01829 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsFile.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsFile.java @@ -1,11 +1,12 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import java.time.OffsetDateTime; -import java.util.Set; public class CmsFile implements CmsObject { @@ -14,10 +15,10 @@ public class CmsFile implements CmsObject { private Long id; private Long sectionId; private String path; - private Set tags; private Boolean downloadable; private String contentType; + @Nonnull @Override public ThreescaleObjectType getType() { return ThreescaleObjectType.FILE; @@ -41,6 +42,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; @@ -66,14 +68,6 @@ public void setPath(String path) { this.path = path; } - public Set getTags() { - return tags; - } - - public void setTags(Set tags) { - this.tags = tags; - } - public Boolean getDownloadable() { return downloadable; } @@ -112,7 +106,6 @@ public String toString() { .append("id", id) .append("sectionId", sectionId) .append("path", path) - .append("tags", tags) .append("downloadable", downloadable) .toString(); } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsLayout.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsLayout.java index 00e0def..deaa17c 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsLayout.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsLayout.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -37,6 +38,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsObject.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsObject.java index d061e59..96b8bbd 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsObject.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsObject.java @@ -1,11 +1,20 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + import java.time.OffsetDateTime; public interface CmsObject { + default boolean isBuiltin() { + return false; + } + + @Nonnull ThreescaleObjectType getType(); + @Nullable Long getId(); OffsetDateTime getCreatedAt(); diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPage.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPage.java index a6b380e..83f16e1 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPage.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPage.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -38,6 +39,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; @@ -115,9 +117,7 @@ public void setTitle(String title) { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof CmsPage)) return false; - - CmsPage cmsPage = (CmsPage) o; + if (!(o instanceof CmsPage cmsPage)) return false; return new EqualsBuilder().append(getId(), cmsPage.getId()).isEquals(); } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPartial.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPartial.java index dae815a..495335c 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPartial.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsPartial.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -34,6 +35,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; @@ -79,9 +81,7 @@ public void setLiquidEnabled(Boolean liquidEnabled) { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof CmsPartial)) return false; - - CmsPartial that = (CmsPartial) o; + if (!(o instanceof CmsPartial that)) return false; return new EqualsBuilder().append(getId(), that.getId()).isEquals(); } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsSection.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsSection.java index 7177e8c..d04da4d 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsSection.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsSection.java @@ -1,5 +1,7 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -17,6 +19,7 @@ public class CmsSection implements CmsObject { private Boolean _public; private String path; + @Nonnull @Override public ThreescaleObjectType getType() { return ThreescaleObjectType.SECTION; @@ -40,6 +43,7 @@ public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } + @Nullable @Override public Long getId() { return id; diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsTemplate.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsTemplate.java index 1534500..64c0984 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsTemplate.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/model/CmsTemplate.java @@ -1,7 +1,10 @@ package com.fwmotion.threescale.cms.model; +import jakarta.annotation.Nonnull; + public interface CmsTemplate extends CmsObject { + @Nonnull @Override default ThreescaleObjectType getType() { return ThreescaleObjectType.TEMPLATE; diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/AbstractPagedRestApiSpliterator.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/AbstractPagedRestApiSpliterator.java index 71bb6ac..83a309f 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/AbstractPagedRestApiSpliterator.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/AbstractPagedRestApiSpliterator.java @@ -1,5 +1,12 @@ package com.fwmotion.threescale.cms.support; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsApiException; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsException; +import com.fwmotion.threescale.cms.exception.ThreescaleCmsUnexpectedPaginationException; +import com.redhat.threescale.rest.cms.ApiException; +import com.redhat.threescale.rest.cms.model.Error; import com.redhat.threescale.rest.cms.model.ListPaginationMetadata; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -14,23 +21,32 @@ public abstract class AbstractPagedRestApiSpliterator implements Spliterator< protected static final int DEFAULT_REQUESTED_PAGE_SIZE = 20; private final int requestedPageSize; + private final ObjectMapper objectMapper; private Iterator currentPageIterator; private int currentPageSize; private int currentPageNumber; private boolean didSplit = false; protected AbstractPagedRestApiSpliterator(@Positive int requestedPageSize, + @Nonnull ObjectMapper objectMapper, @Nonnull Collection currentPage, @PositiveOrZero int currentPageNumber) { this.requestedPageSize = requestedPageSize; + this.objectMapper = objectMapper; this.currentPageIterator = currentPage.iterator(); this.currentPageSize = currentPage.size(); this.currentPageNumber = currentPageNumber; } protected AbstractPagedRestApiSpliterator(@Nonnull Collection currentPage, + @Nonnull ObjectMapper objectMapper, @PositiveOrZero int currentPageNumber) { - this(DEFAULT_REQUESTED_PAGE_SIZE, currentPage, currentPageNumber); + this(DEFAULT_REQUESTED_PAGE_SIZE, objectMapper, currentPage, currentPageNumber); + } + + @Nonnull + ObjectMapper getObjectMapper() { + return objectMapper; } @Nullable @@ -134,11 +150,34 @@ protected void validateResultPageSize(@Nonnull String type, return; } - // TODO: Create ThreescaleCMSException and throw it instead of IllegalStateException - throw new IllegalStateException("Unexpected page size for " + type + " list page " + pageNumber - + " (with page size of " + pageSize - + "); parsed page size is " + resultPage.size() - + " but expected size of " + expectedPageSize); + throw new ThreescaleCmsUnexpectedPaginationException(type, + pageNumber, + pageSize, + resultPage.size(), + expectedPageSize); + } + + protected ThreescaleCmsException handleApiException( + @Nonnull ApiException e, + @Nonnull String type, + @PositiveOrZero int pageNumber, + @Positive int pageSize + ) { + String errorMessage = "Unexpected exception while iterating CMS " + type + + " page " + pageNumber + + " (with page size of " + pageSize + ")"; + + Error responseError; + try { + responseError = objectMapper.readValue(e.getResponseBody(), Error.class); + } catch (JsonProcessingException ex) { + return new ThreescaleCmsApiException(e.getCode(), errorMessage, e); + } + + return new ThreescaleCmsApiException(e.getCode(), + responseError, + errorMessage, + e); } } diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/ApiClientBuilder.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/ApiClientBuilder.java index 8fa66db..2112308 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/ApiClientBuilder.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/ApiClientBuilder.java @@ -12,11 +12,11 @@ private ApiClientBuilder() { } public static ApiClient buildApiClient(CloseableHttpClient httpClient) { - ApiClient xmlApiClient = new ApiClient(httpClient); + ApiClient apiClient = new ApiClient(httpClient); - applyMixIns(xmlApiClient.getObjectMapper()); + applyMixIns(apiClient.getObjectMapper()); - return xmlApiClient; + return apiClient; } /** diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedFilesSpliterator.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedFilesSpliterator.java index d37b80a..ce52197 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedFilesSpliterator.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedFilesSpliterator.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms.support; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fwmotion.threescale.cms.mappers.CmsFileMapper; import com.fwmotion.threescale.cms.model.CmsFile; import com.redhat.threescale.rest.cms.ApiException; @@ -21,22 +22,25 @@ public class PagedFilesSpliterator extends AbstractPagedRestApiSpliterator currentPage, @PositiveOrZero int currentPageNumber) { - super(requestedPageSize, currentPage, currentPageNumber); + super(requestedPageSize, objectMapper, currentPage, currentPageNumber); this.filesApi = filesApi; } @@ -62,9 +66,7 @@ protected Collection getPage(@PositiveOrZero int pageNumber, return resultPage; } catch (ApiException e) { - // TODO: Create ThreescaleCmsException and throw it instead of IllegalStateException - throw new IllegalStateException("Unexpected exception while iterating CMS file page " + pageNumber - + " (with page size of " + pageSize + ")", e); + throw handleApiException(e, "file", pageNumber, pageSize); } } @@ -74,7 +76,7 @@ protected AbstractPagedRestApiSpliterator doSplit( @Positive int requestedPageSize, @Nonnull Collection currentPage, @PositiveOrZero int currentPageNumber) { - return new PagedFilesSpliterator(filesApi, requestedPageSize, currentPage, currentPageNumber); + return new PagedFilesSpliterator(filesApi, getObjectMapper(), requestedPageSize, currentPage, currentPageNumber); } @Override diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedSectionsSpliterator.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedSectionsSpliterator.java index b96d7a2..a6f59fa 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedSectionsSpliterator.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedSectionsSpliterator.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms.support; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fwmotion.threescale.cms.mappers.CmsSectionMapper; import com.fwmotion.threescale.cms.model.CmsSection; import com.redhat.threescale.rest.cms.ApiException; @@ -21,22 +22,24 @@ public class PagedSectionsSpliterator extends AbstractPagedRestApiSpliterator currentPage, @PositiveOrZero int currentPageNumber) { - super(requestedPageSize, currentPage, currentPageNumber); + super(requestedPageSize, objectMapper, currentPage, currentPageNumber); this.sectionsApi = sectionsApi; } @@ -62,9 +65,7 @@ protected Collection getPage(@PositiveOrZero int pageNumber, return resultPage; } catch (ApiException e) { - // TODO: Create ThreescaleCmsException and throw it instead of IllegalStateException - throw new IllegalStateException("Unexpected exception while iterating CMS section list page " + pageNumber - + " (with page size of " + pageSize + ")", e); + throw handleApiException(e, "section", pageNumber, pageSize); } } @@ -74,7 +75,7 @@ protected AbstractPagedRestApiSpliterator doSplit( @Positive int requestedPageSize, @Nonnull Collection currentPage, @PositiveOrZero int currentPageNumber) { - return new PagedSectionsSpliterator(sectionsApi, requestedPageSize, currentPage, currentPageNumber); + return new PagedSectionsSpliterator(sectionsApi, getObjectMapper(), requestedPageSize, currentPage, currentPageNumber); } @Override diff --git a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedTemplatesSpliterator.java b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedTemplatesSpliterator.java index dfbb404..2f5e21a 100644 --- a/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedTemplatesSpliterator.java +++ b/rest-client/src/main/java/com/fwmotion/threescale/cms/support/PagedTemplatesSpliterator.java @@ -1,5 +1,6 @@ package com.fwmotion.threescale.cms.support; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fwmotion.threescale.cms.mappers.CmsTemplateMapper; import com.fwmotion.threescale.cms.model.CmsTemplate; import com.redhat.threescale.rest.cms.ApiException; @@ -23,26 +24,29 @@ public class PagedTemplatesSpliterator extends AbstractPagedRestApiSpliterator currentPage, @PositiveOrZero int currentPageNumber) { - super(requestedPageSize, currentPage, currentPageNumber); + super(requestedPageSize, objectMapper, currentPage, currentPageNumber); this.templatesApi = templatesApi; this.includeContent = includeContent; } @@ -74,9 +78,7 @@ protected Collection getPage(@PositiveOrZero int pageNumber, return resultPage; } catch (ApiException e) { - // TODO: Create ThreescaleCmsException and throw it instead of IllegalStateException - throw new IllegalStateException("Unexpected exception while iterating CMS template list page " + pageNumber - + " (with page size of " + pageSize + ")", e); + throw handleApiException(e, "template", pageNumber, pageSize); } } @@ -85,10 +87,13 @@ protected Collection getPage(@PositiveOrZero int pageNumber, protected AbstractPagedRestApiSpliterator doSplit( @Positive int requestedPageSize, @Nonnull Collection currentPage, - @PositiveOrZero int currentPageNumber) { + @PositiveOrZero int currentPageNumber + ) { return new PagedTemplatesSpliterator( templatesApi, - includeContent, requestedPageSize, + getObjectMapper(), + includeContent, + requestedPageSize, currentPage, currentPageNumber ); diff --git a/rest-client/src/main/resources/api-spec/3scale-cms.yaml b/rest-client/src/main/resources/api-spec/3scale-cms.yaml index 39824d3..fe84c76 100644 --- a/rest-client/src/main/resources/api-spec/3scale-cms.yaml +++ b/rest-client/src/main/resources/api-spec/3scale-cms.yaml @@ -16,6 +16,9 @@ servers: paths: /admin/api/cms/files.json: get: + operationId: list-files + summary: List Files + description: List files held within the 3scale CMS tags: - Files parameters: @@ -23,99 +26,99 @@ paths: description: | The number for the page of results to retrieve, starting from page 1; defaults to 1 + in: query + required: false schema: type: integer minimum: 1 default: 1 - in: query - required: false - name: per_page description: | The number of items to retrieve per page of results; defaults to 20 + in: query + required: false schema: type: integer minimum: 1 maximum: 100 default: 20 - required: false - in: query - name: section_id description: The section to query for files (hidden option in CMS API) + in: query + required: false schema: $ref: '#/components/schemas/SectionId' - required: false - in: query responses: '200': $ref: '#/components/responses/FileListResponse' - operationId: list-files - summary: List Files - description: List files held within the 3scale CMS post: + operationId: create-file + summary: Create File + description: Create file within the 3scale CMS + tags: + - Files requestBody: + required: true content: multipart/form-data: schema: $ref: '#/components/schemas/FileCreationRequest' - required: true - tags: - - Files responses: '201': $ref: '#/components/responses/FileResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' - operationId: create-file - summary: Create File - description: Create file within the 3scale CMS + $ref: '#/components/responses/ErrorResponse' '/admin/api/cms/files/{file_id}.json': + parameters: + - name: file_id + in: path + required: true + schema: + $ref: '#/components/schemas/FileId' get: + operationId: get-file + summary: Get File + description: Get a file descriptor tags: - Files responses: '200': $ref: '#/components/responses/FileResponse' - operationId: get-file - summary: Get File - description: Get a file descriptor put: + operationId: update-file + summary: Update File + description: Update an existing file + tags: + - Files requestBody: + required: true content: multipart/form-data: schema: $ref: '#/components/schemas/FileUpdatableFields' - required: true - tags: - - Files responses: '200': $ref: '#/components/responses/FileResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' - operationId: update-file - summary: Update File - description: Update an existing file + $ref: '#/components/responses/ErrorResponse' delete: + operationId: delete-file + summary: Delete File + description: Delete a file from 3scale CMS tags: - Files responses: '200': $ref: '#/components/responses/EmptyResponse' - operationId: delete-file - summary: Delete File - description: Delete a file from 3scale CMS - parameters: - - name: file_id - schema: - $ref: '#/components/schemas/FileId' - in: path - required: true /admin/api/cms/sections.json: get: + operationId: list-sections + summary: List Sections + description: List sections held within the 3scale CMS tags: - Sections parameters: @@ -123,28 +126,25 @@ paths: description: | The number for the page of results to retrieve, starting from page 1; defaults to 1 + in: query + required: false schema: type: integer minimum: 1 default: 1 - in: query - required: false - name: per_page description: | The number of items to retrieve per page of results; defaults to 20 + in: query + required: false schema: type: integer minimum: 1 maximum: 100 default: 20 - required: false - in: query responses: '200': $ref: '#/components/responses/SectionListResponse' - operationId: list-sections - summary: List Sections - description: List sections held within the 3scale CMS post: requestBody: content: @@ -158,42 +158,51 @@ paths: '201': $ref: '#/components/responses/SectionResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' + $ref: '#/components/responses/ErrorResponse' operationId: create-section summary: Create Section description: Create section within the 3scale CMS '/admin/api/cms/sections/{section_id}.json': + parameters: + - name: section_id + in: path + required: true + schema: + $ref: '#/components/schemas/SectionId' get: + operationId: get-section + summary: Get Section + description: Get section descriptor tags: - Sections responses: '200': $ref: '#/components/responses/SectionResponse' - operationId: get-section - summary: Get Section - description: Get section descriptor put: + operationId: update-section + summary: Update Section + description: Update section descriptor + tags: + - Sections requestBody: + required: true content: multipart/form-data: schema: $ref: '#/components/schemas/SectionUpdatableFields' - required: true - tags: - - Sections responses: '200': $ref: '#/components/responses/SectionResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' - operationId: update-section - summary: Update Section - description: Update section descriptor + $ref: '#/components/responses/ErrorResponse' delete: + operationId: delete-section + summary: Delete Section + description: Delete a section from 3scale CMS tags: - Sections responses: @@ -201,61 +210,55 @@ paths: $ref: '#/components/responses/EmptyResponse' '423': $ref: '#/components/responses/EmptyResponse' - operationId: delete-section - summary: Delete Section - description: Delete a section from 3scale CMS + '/admin/api/cms/sections/{system_name}.json': parameters: - - name: section_id - schema: - $ref: '#/components/schemas/SectionId' + - name: system_name in: path required: true - '/admin/api/cms/sections/{system_name}.json': + schema: + $ref: '#/components/schemas/SystemName' get: + operationId: get-section-by-system-name + summary: Get Section by System Name + description: Get section descriptor tags: - Sections responses: '200': $ref: '#/components/responses/SectionResponse' - operationId: get-section-by-system-name - summary: Get Section by System Name - description: Get section descriptor put: + operationId: update-section-by-system-name + summary: Update Section by System Name + description: Update section descriptor + tags: + - Sections requestBody: content: multipart/form-data: schema: $ref: '#/components/schemas/SectionUpdatableFields' required: true - tags: - - Sections responses: '200': $ref: '#/components/responses/SectionResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' - operationId: update-section-by-system-name - summary: Update Section by System Name - description: Update section descriptor + $ref: '#/components/responses/ErrorResponse' delete: + operationId: delete-section-by-system-name + summary: Delete Section by System Name + description: Delete a section from 3scale CMS tags: - Sections responses: '200': $ref: '#/components/responses/EmptyResponse' - operationId: delete-section-by-system-name - summary: Delete Section by System Name - description: Delete a section from 3scale CMS - parameters: - - name: system_name - schema: - $ref: '#/components/schemas/SystemName' - in: path - required: true /admin/api/cms/templates.json: get: + operationId: list-templates + summary: List Templates + description: List all templates contained in the 3scale CMS tags: - Templates parameters: @@ -263,116 +266,117 @@ paths: description: | The number for the page of results to retrieve, starting from page 1; defaults to 1 + in: query + required: false schema: type: integer minimum: 1 default: 1 - in: query - required: false - name: per_page description: | The number of items to retrieve per page of results; defaults to 20 + in: query + required: false schema: type: integer minimum: 1 maximum: 100 default: 20 - required: false - in: query - name: content description: | Whether to include the draft and published content in listed templates + in: query + required: false schema: type: boolean - required: false - in: query responses: '200': $ref: '#/components/responses/TemplateListResponse' - operationId: list-templates - summary: List Templates - description: List all templates contained in the 3scale CMS post: + operationId: create-template + summary: Create Template + description: Create template with the 3scale CMS + tags: + - Templates requestBody: + required: true content: - multipart/form-data: + # 3scale gives an Internal Server Error when + # multipart/form-data is used for this + application/x-www-form-urlencoded: schema: $ref: '#/components/schemas/TemplateCreationRequest' - required: true - tags: - - Templates responses: '201': $ref: '#/components/responses/TemplateResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' - operationId: create-template - summary: Create Template - description: Create template with the 3scale CMS + $ref: '#/components/responses/ErrorResponse' '/admin/api/cms/templates/{template_id}.json': + parameters: + - name: template_id + in: path + required: true + schema: + $ref: '#/components/schemas/TemplateId' get: + operationId: get-template + summary: Get Template + description: |- + Retrieve a template of any kind from 3scale (TODO: double-check response) tags: - Templates responses: '200': $ref: '#/components/responses/TemplateResponse' - operationId: get-template - summary: Get Template - description: |- - Retrieve a template of any kind from 3scale (TODO: double-check response) put: + operationId: update-template + summary: Update Template + description: 'Update a template draft (TODO: double-check response)' + tags: + - Templates requestBody: + required: true content: multipart/form-data: schema: $ref: '#/components/schemas/TemplateUpdatableFields' - required: true - tags: - - Templates responses: '200': $ref: '#/components/responses/TemplateResponse' '422': - $ref: '#/components/responses/ErrorListResponse' + $ref: '#/components/responses/ErrorResponse' '500': - $ref: '#/components/responses/ErrorHashResponse' - operationId: update-template - summary: Update Template - description: 'Update a template draft (TODO: double-check response)' + $ref: '#/components/responses/ErrorResponse' delete: + operationId: delete-template + summary: Delete Template + description: Delete a template from 3scale CMS tags: - Templates responses: '200': $ref: '#/components/responses/EmptyResponse' - operationId: delete-template - summary: Delete Template - description: Delete a template from 3scale CMS - parameters: - - name: template_id - schema: - $ref: '#/components/schemas/TemplateId' - in: path - required: true + '422': + $ref: '#/components/responses/ErrorResponse' '/admin/api/cms/templates/{template_id}/publish': put: + operationId: publish-template + summary: Publish Template + description: Move a template draft to be "published" tags: - Templates responses: '200': $ref: '#/components/responses/TemplateResponse' - operationId: publish-template - summary: Publish Template - description: Move a template draft to be "published" parameters: - name: template_id - schema: - $ref: '#/components/schemas/TemplateId' in: path required: true + schema: + $ref: '#/components/schemas/TemplateId' /admin/api/provider.json: get: operationId: read-provider-settings @@ -381,8 +385,8 @@ paths: Read 3scale Provider (Tenant) settings; particularly related to the developer portal. - *Note:* This is technically part of the Account Management API, but is - used to determine the location and access-token for retrieving file + *Note:* This is technically part of 3scale's Account Management API, but + is used to determine the location and access-token for retrieving file content, so it is tagged with `Files`. tags: - Files @@ -394,7 +398,8 @@ components: BuiltinPage: allOf: - $ref: '#/components/schemas/Template' - - properties: + - type: object + properties: path: type: string maxLength: 255 @@ -411,7 +416,8 @@ components: BuiltinPartial: allOf: - $ref: '#/components/schemas/Template' - - properties: + - type: object + properties: system_name: $ref: '#/components/schemas/SystemName' example: @@ -437,38 +443,36 @@ components: - page - layout - partial - ErrorHash: - description: Unhandled server error on 3scale + Error: + description: Error message from the server + type: object + required: + - error properties: status: + description: Error code type: integer - toXml: - type: string error: + description: Error message type: string - ErrorList: - description: List of errors that occurred in processing a request - type: array - items: - description: | - Description of an individual error that occurred while processing - an event. - type: string + example: + error: Built-in resources can't be deleted File: description: File as held in the 3scale CMS allOf: - $ref: '#/components/schemas/FileUpdatableFields' - - properties: + - type: object + properties: id: $ref: '#/components/schemas/FileId' created_at: - format: date-time description: The instant at which the file was first created in the 3scale CMS type: string - updated_at: format: date-time + updated_at: description: The instant at which the file was last updated type: string + format: date-time url: description: The location at which the file is stored by 3scale; for 3scale-internal use type: string @@ -516,7 +520,6 @@ components: section_id: 33 path: /images/desk.jpg url: http://s3.openshift-storage.svc/bucket--99173774-11c4-4a63-9d1a-2df7b6a0b5bd/provider-name/2022/03/05/desk-b2a6c318d66334d2.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=VLXdr0R4TkaRNkgubbLx%2F20221005%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221005T214121Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=aa05d76d599d07c6e8593a92edbe2d261aa6990326864d39465430b25761275e - tag_list: '' title: desk.jpg - id: 10 created_at: 2022-03-05T04:48:28Z @@ -524,10 +527,10 @@ components: section_id: 33 path: /images/mouse.jpg url: http://s3.openshift-storage.svc/bucket--99173774-11c4-4a63-9d1a-2df7b6a0b5bd/provider-name/2022/03/05/mouse-54d77f30bdfb31a4.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=VLXdr0R4TkaRNkgubbLx%2F20221005%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221005T214121Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=186eab31325093ec6b542ff9805621e6c1fa9a26bdfce24f7f9e446f22e635ca - tag_list: '' title: mouse.jpg FileUpdatableFields: description: Fields that may be modified after file creation + type: object properties: section_id: $ref: '#/components/schemas/SectionId' @@ -537,9 +540,6 @@ components: Portal. Must be unique across all CMS objects type: string maxLength: 255 - tag_list: - description: Comma-separated list of tags - type: string downloadable: description: | Flag indicating whether the file will be returned from the Developer @@ -556,7 +556,8 @@ components: Layout: allOf: - $ref: '#/components/schemas/Template' - - properties: + - type: object + properties: system_name: $ref: '#/components/schemas/SystemName' title: @@ -589,7 +590,8 @@ components: Page: allOf: - $ref: '#/components/schemas/Template' - - properties: + - type: object + properties: path: description: | The path to which this template will be retrieved when browsing @@ -620,7 +622,8 @@ components: Partial: allOf: - $ref: '#/components/schemas/Template' - - properties: + - type: object + properties: system_name: $ref: '#/components/schemas/SystemName' example: @@ -734,7 +737,8 @@ components: description: Section within 3scale CMS allOf: - $ref: '#/components/schemas/SectionUpdatableFields' - - properties: + - type: object + properties: id: $ref: '#/components/schemas/SectionId' created_at: @@ -761,7 +765,8 @@ components: description: Request fields for creation of Sections allOf: - $ref: '#/components/schemas/SectionUpdatableFields' - - properties: + - type: object + properties: partial_path: type: string maxLength: 255 @@ -828,6 +833,7 @@ components: partial_path: /password SectionUpdatableFields: description: Fields that may be modified after section creation + type: object properties: public: description: Whether the section is viewable by users not logged in @@ -845,6 +851,7 @@ components: maxLength: 255 Template: description: Base type for all template types + type: object discriminator: propertyName: type mapping: @@ -857,11 +864,11 @@ components: id: $ref: '#/components/schemas/TemplateId' created_at: - format: date-time type: string - updated_at: format: date-time + updated_at: type: string + format: date-time content_type: description: | How the template should be described to web browsers with the @@ -886,6 +893,7 @@ components: type: string maxLength: 16777215 TemplateCreationRequest: + type: object required: - type allOf: @@ -946,6 +954,7 @@ components: total_pages: 4 current_page: 1 TemplateUpdatableFields: + type: object properties: system_name: $ref: '#/components/schemas/SystemName' @@ -1018,36 +1027,40 @@ components: responses: EmptyResponse: description: Response with no body; eg, to indicate successful deletion - ErrorHashResponse: - content: - application/xml: - schema: - $ref: '#/components/schemas/ErrorHash' - description: Response containing an `ErrorHash` - ErrorListResponse: + ErrorResponse: + description: Response containing an `Error` content: application/json: schema: - $ref: '#/components/schemas/ErrorList' - description: Response containing an `ErrorList` + $ref: '#/components/schemas/Error' + examples: + server_error: + description: Internal Server Error + value: + status: 500 + error: Internal Server Error + builtin_delete: + description: Built-in resource deletion error + value: + error: Built-in resources can't be deleted FileResponse: + description: Response containing a single file declaration content: application/json: schema: $ref: '#/components/schemas/File' - description: Response containing a single file declaration FileListResponse: + description: Response containing a list of `File`s content: application/json: schema: $ref: '#/components/schemas/FileList' - description: Response containing a list of `File`s ProviderAccountResponse: + description: Response containing the `WrappedProviderAccount` configuration content: application/json: schema: $ref: '#/components/schemas/WrappedProviderAccount' - description: Response containing the `WrappedProviderAccount` configuration SectionListResponse: content: application/json: @@ -1074,20 +1087,20 @@ components: description: Response containing a single `Section` securitySchemes: provider_key: - type: apiKey description: >- Provider API Key from the `Account Settings > Overview` page of a 3scale tenant. To view this key, a user must be logged in with `admin` privileges. - name: provider_key + type: apiKey in: query + name: provider_key access_token: - type: apiKey description: >- Access Token from the `Account Settings > Personal > Tokens` page of a 3scale tenant. The token must have access to both the `Account Management API` and the `Developer Portal API`. - name: access_token + type: apiKey in: query + name: access_token security: - provider_key: [ ] - access_token: [ ] diff --git a/rest-client/src/test/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImplUnitTest.java b/rest-client/src/test/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImplUnitTest.java index c1ef913..ed31f5a 100644 --- a/rest-client/src/test/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImplUnitTest.java +++ b/rest-client/src/test/java/com/fwmotion/threescale/cms/ThreescaleCmsClientImplUnitTest.java @@ -1,5 +1,7 @@ package com.fwmotion.threescale.cms; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fwmotion.threescale.cms.mixins.EnumHandlerMixIn; import com.fwmotion.threescale.cms.model.*; import com.fwmotion.threescale.cms.testsupport.FilesApiTestSupport; import com.fwmotion.threescale.cms.testsupport.SectionsApiTestSupport; @@ -8,10 +10,7 @@ import com.redhat.threescale.rest.cms.api.FilesApi; import com.redhat.threescale.rest.cms.api.SectionsApi; import com.redhat.threescale.rest.cms.api.TemplatesApi; -import com.redhat.threescale.rest.cms.model.ModelFile; -import com.redhat.threescale.rest.cms.model.ProviderAccount; -import com.redhat.threescale.rest.cms.model.Section; -import com.redhat.threescale.rest.cms.model.WrappedProviderAccount; +import com.redhat.threescale.rest.cms.model.*; import org.apache.commons.io.IOUtils; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -34,7 +33,6 @@ import java.time.OffsetDateTime; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; import static com.fwmotion.threescale.cms.matchers.HeaderMatcher.header; @@ -60,6 +58,8 @@ class ThreescaleCmsClientImplUnitTest { SectionsApi sectionsApi; @Mock TemplatesApi templatesApi; + @Spy + ObjectMapper objectMapper = new ObjectMapper(); SectionsApiTestSupport sectionsApiTestSupport; FilesApiTestSupport filesApiTestSupport; @@ -78,6 +78,8 @@ class ThreescaleCmsClientImplUnitTest { @BeforeEach void setUp() { + objectMapper.addMixIn(EnumHandler.class, EnumHandlerMixIn.class); + sectionsApiTestSupport = new SectionsApiTestSupport(sectionsApi); filesApiTestSupport = new FilesApiTestSupport(filesApi); templatesApiTestSupport = new TemplatesApiTestSupport(templatesApi); @@ -694,14 +696,8 @@ void save_NewFile() throws Exception { newFile.setId(null); newFile.setPath("/file.jpg"); newFile.setSectionId(30L); - newFile.setTags(Set.of("a", "b", "c")); newFile.setDownloadable(true); - String expectedTagString = newFile.getTags() - .stream() - .sorted() - .collect(Collectors.joining(",")); - // And a File File newFileContent = new File("/tmp/file.jpg"); @@ -710,7 +706,6 @@ void save_NewFile() throws Exception { eq(newFile.getSectionId()), eq(newFile.getPath()), same(newFileContent), - eq(expectedTagString), eq(newFile.getDownloadable()), nullable(String.class))) .willReturn(new ModelFile() @@ -725,7 +720,6 @@ void save_NewFile() throws Exception { eq(newFile.getSectionId()), eq(newFile.getPath()), same(newFileContent), - eq(expectedTagString), eq(newFile.getDownloadable()), nullable(String.class)); then(sectionsApi).shouldHaveNoInteractions(); @@ -742,14 +736,8 @@ void save_UpdatedFile() throws Exception { updateFile.setId(16L); updateFile.setPath("/file.jpg"); updateFile.setSectionId(30L); - updateFile.setTags(Set.of("a", "b", "c")); updateFile.setDownloadable(true); - String expectedTagString = updateFile.getTags() - .stream() - .sorted() - .collect(Collectors.joining(",")); - // And a File File newFileContent = new File("/tmp/file.jpg"); @@ -758,7 +746,6 @@ void save_UpdatedFile() throws Exception { eq(updateFile.getId()), eq(updateFile.getSectionId()), eq(updateFile.getPath()), - eq(expectedTagString), eq(updateFile.getDownloadable()), same(newFileContent), nullable(String.class))) @@ -774,7 +761,6 @@ void save_UpdatedFile() throws Exception { eq(updateFile.getId()), eq(updateFile.getSectionId()), eq(updateFile.getPath()), - eq(expectedTagString), eq(updateFile.getDownloadable()), same(newFileContent), nullable(String.class)); @@ -805,7 +791,6 @@ void delete_File() throws Exception { newFile.setId(16L); newFile.setPath("/file.jpg"); newFile.setSectionId(30L); - newFile.setTags(Set.of("a", "b", "c")); newFile.setDownloadable(true); // When the interface code is called diff --git a/rest-client/src/test/java/com/fwmotion/threescale/cms/matchers/CmsFileMatcher.java b/rest-client/src/test/java/com/fwmotion/threescale/cms/matchers/CmsFileMatcher.java index ccb2e5f..c571d87 100644 --- a/rest-client/src/test/java/com/fwmotion/threescale/cms/matchers/CmsFileMatcher.java +++ b/rest-client/src/test/java/com/fwmotion/threescale/cms/matchers/CmsFileMatcher.java @@ -27,8 +27,7 @@ public boolean matchesSafely(@Nonnull CmsObject actual) { return super.matchesSafely(actual) && actualMatchesExpected(expected.getSectionId(), actualFile.getSectionId()) - && actualMatchesExpected(expected.getPath(), actualFile.getPath()) - && actualMatchesExpected(expected.getTagList(), String.join(",", actualFile.getTags())); + && actualMatchesExpected(expected.getPath(), actualFile.getPath()); } @Override diff --git a/rest-client/src/test/java/com/fwmotion/threescale/cms/testsupport/FilesApiTestSupport.java b/rest-client/src/test/java/com/fwmotion/threescale/cms/testsupport/FilesApiTestSupport.java index 15e356c..65f4f21 100644 --- a/rest-client/src/test/java/com/fwmotion/threescale/cms/testsupport/FilesApiTestSupport.java +++ b/rest-client/src/test/java/com/fwmotion/threescale/cms/testsupport/FilesApiTestSupport.java @@ -25,7 +25,6 @@ public class FilesApiTestSupport { .sectionId(2675715L) .path("/favicon.ico") .url("http://s3.openshift-storage.svc/bucket--99173774-11c4-4a63-9d1a-2df7b6a0b5bd/provider-name/2022/03/05/favicon-e94f1a378c59231c.ico?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=VLXdr0R4TkaRNkgubbLx%2F20221005%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221005T214121Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=deb0c4a3741a76adf739c3865c978aa0a062410685efa0b16e2827c977ea3143") - .tagList("") .title("favicon.ico") .createdAt(OffsetDateTime.of(2022, 3, 5, 4, 48, 29, 0, ZoneOffset.UTC)) .updatedAt(OffsetDateTime.of(2022, 3, 5, 4, 48, 29, 0, ZoneOffset.UTC)); diff --git a/samples/tekton/task-3scale-cms-copy.yaml b/samples/tekton/task-3scale-cms-copy.yaml index 2a3bac8..2f9fc20 100644 --- a/samples/tekton/task-3scale-cms-copy.yaml +++ b/samples/tekton/task-3scale-cms-copy.yaml @@ -104,7 +104,7 @@ spec: for downloading CMS content. The information should include data such as the Admin Portal base URL and either a Provider Key or a Personal Access Token. (Note that when a Personal Access Token is used, it must have - permission to the Content Management API. Binding a Secret to this + permission to the Developer Portal API. Binding a Secret to this Workspace is strongly recommended over other volume types. - name: target-provider-details description: >- @@ -112,5 +112,5 @@ spec: for uploading CMS content. The information should include data such as the Admin Portal base URL and either a Provider Key or a Personal Access Token. (Note that when a Personal Access Token is used, it must have - permission to the Content Management API. Binding a Secret to this + permission to the Developer Portal API. Binding a Secret to this Workspace is strongly recommended over other volume types. diff --git a/samples/tekton/task-3scale-cms.yaml b/samples/tekton/task-3scale-cms.yaml index 96630ae..576513f 100644 --- a/samples/tekton/task-3scale-cms.yaml +++ b/samples/tekton/task-3scale-cms.yaml @@ -4,7 +4,7 @@ metadata: name: 3scale-cms spec: description: >- - 3scale-cms task to interact with the 3scale Content Management API + 3scale-cms task to interact with the 3scale Developer Portal API params: - name: 3scale-cms-image type: string @@ -83,6 +83,6 @@ spec: A Workspace containing the information required to interact with 3scale, such as the Admin Portal base URL and either a Provider Key or a Personal Access Token. (Note that when a Personal Access Token is used, - it must have permission to the Content Management API. Binding a Secret + it must have permission to the Developer Portal API. Binding a Secret to this Workspace is strongly recommended over other volume types.