From 85acf6777b5a72f9fcb7677187f7dcf5b6c7cc2e Mon Sep 17 00:00:00 2001 From: WillardHu Date: Tue, 14 Sep 2021 10:23:09 +0800 Subject: [PATCH] fix: replace String.format() with newly created class OpenApiPathBuilder Signed-off-by: WillardHu --- .../exception/ApolloOpenApiException.java | 3 +- .../service/AbstractOpenApiService.java | 44 ++-- .../client/service/AppOpenApiService.java | 20 +- .../client/service/ClusterOpenApiService.java | 16 +- .../client/service/ItemOpenApiService.java | 56 +++-- .../service/NamespaceOpenApiService.java | 35 ++- .../client/service/ReleaseOpenApiService.java | 30 ++- .../client/url/OpenApiPathBuilder.java | 152 ++++++++++++ .../client/url/OpenApiPathBuilderTest.java | 234 ++++++++++++++++++ 9 files changed, 508 insertions(+), 82 deletions(-) create mode 100644 apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilder.java create mode 100644 apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilderTest.java diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/exception/ApolloOpenApiException.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/exception/ApolloOpenApiException.java index 0f89855175a..a5f343fea15 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/exception/ApolloOpenApiException.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/exception/ApolloOpenApiException.java @@ -17,8 +17,7 @@ package com.ctrip.framework.apollo.openapi.client.exception; public class ApolloOpenApiException extends RuntimeException { - - private int status; + private final int status; public ApolloOpenApiException(int status, String reason, String message) { super(String.format("Request to apollo open api failed, status code: %d, reason: %s, message: %s", status, reason, diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AbstractOpenApiService.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AbstractOpenApiService.java index 889a23f3e8e..cd122249211 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AbstractOpenApiService.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AbstractOpenApiService.java @@ -17,30 +17,21 @@ package com.ctrip.framework.apollo.openapi.client.service; import com.ctrip.framework.apollo.openapi.client.exception.ApolloOpenApiException; +import com.ctrip.framework.apollo.openapi.client.url.OpenApiPathBuilder; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import com.google.common.escape.Escaper; -import com.google.common.net.UrlEscapers; import com.google.gson.Gson; -import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.*; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -abstract class AbstractOpenApiService { - private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper(); - private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper(); +import java.io.IOException; +abstract class AbstractOpenApiService { private final String baseUrl; protected final CloseableHttpClient client; @@ -52,38 +43,30 @@ abstract class AbstractOpenApiService { this.gson = gson; } - protected CloseableHttpResponse get(String path) throws IOException { - HttpGet get = new HttpGet(String.format("%s/%s", baseUrl, path)); + protected CloseableHttpResponse get(OpenApiPathBuilder path) throws IOException { + HttpGet get = new HttpGet(path.buildPath(baseUrl)); return execute(get); } - protected CloseableHttpResponse post(String path, Object entity) throws IOException { - HttpPost post = new HttpPost(String.format("%s/%s", baseUrl, path)); + protected CloseableHttpResponse post(OpenApiPathBuilder path, Object entity) throws IOException { + HttpPost post = new HttpPost(path.buildPath(baseUrl)); return execute(post, entity); } - protected CloseableHttpResponse put(String path, Object entity) throws IOException { - HttpPut put = new HttpPut(String.format("%s/%s", baseUrl, path)); + protected CloseableHttpResponse put(OpenApiPathBuilder path, Object entity) throws IOException { + HttpPut put = new HttpPut(path.buildPath(baseUrl)); return execute(put, entity); } - protected CloseableHttpResponse delete(String path) throws IOException { - HttpDelete delete = new HttpDelete(String.format("%s/%s", baseUrl, path)); + protected CloseableHttpResponse delete(OpenApiPathBuilder path) throws IOException { + HttpDelete delete = new HttpDelete(path.buildPath(baseUrl)); return execute(delete); } - protected String escapePath(String path) { - return pathEscaper.escape(path); - } - - protected String escapeParam(String param) { - return queryParamEscaper.escape(param); - } - private CloseableHttpResponse execute(HttpEntityEnclosingRequestBase requestBase, Object entity) throws IOException { requestBase.setEntity(new StringEntity(gson.toJson(entity), ContentType.APPLICATION_JSON)); @@ -98,7 +81,6 @@ private CloseableHttpResponse execute(HttpUriRequest request) throws IOException return response; } - private void checkHttpResponseStatus(HttpResponse response) { if (response.getStatusLine().getStatusCode() == 200) { return; @@ -118,5 +100,7 @@ private void checkHttpResponseStatus(HttpResponse response) { protected void checkNotEmpty(String value, String name) { Preconditions.checkArgument(!Strings.isNullOrEmpty(value), name + " should not be null or empty"); } + + } diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AppOpenApiService.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AppOpenApiService.java index ae0848ce799..34756f98d8b 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AppOpenApiService.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/AppOpenApiService.java @@ -16,6 +16,7 @@ */ package com.ctrip.framework.apollo.openapi.client.service; +import com.ctrip.framework.apollo.openapi.client.url.OpenApiPathBuilder; import com.ctrip.framework.apollo.openapi.dto.OpenAppDTO; import com.ctrip.framework.apollo.openapi.dto.OpenEnvClusterDTO; import com.google.common.base.Joiner; @@ -40,9 +41,11 @@ public AppOpenApiService(CloseableHttpClient client, String baseUrl, Gson gson) public List getEnvClusterInfo(String appId) { checkNotEmpty(appId, "App id"); - String path = String.format("apps/%s/envclusters", escapePath(appId)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .appsPathVal(appId) + .customResource("envclusters"); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OPEN_ENV_CLUSTER_DTO_LIST_TYPE); } catch (Throwable ex) { throw new RuntimeException(String.format("Load env cluster information for appId: %s failed", appId), ex); @@ -50,14 +53,15 @@ public List getEnvClusterInfo(String appId) { } public List getAppsInfo(List appIds) { - String path = "apps"; + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .customResource("apps"); if (appIds != null && !appIds.isEmpty()) { String param = Joiner.on(",").join(appIds); - path = String.format("apps?appIds=%s", escapeParam(param)); + pathBuilder.addParam("appIds", param); } - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OPEN_APP_DTO_LIST_TYPE); } catch (Throwable ex) { throw new RuntimeException(String.format("Load app information for appIds: %s failed", appIds), ex); @@ -65,8 +69,10 @@ public List getAppsInfo(List appIds) { } public List getAuthorizedApps() { - String path = "apps/authorized"; - try(CloseableHttpResponse response = this.get(path)) { + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .customResource("apps/authorized"); + + try(CloseableHttpResponse response = this.get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OPEN_APP_DTO_LIST_TYPE); } catch (Throwable ex) { throw new RuntimeException("Load authorized apps failed", ex); diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ClusterOpenApiService.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ClusterOpenApiService.java index 02695f9345c..9c4cce585af 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ClusterOpenApiService.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ClusterOpenApiService.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.openapi.client.service; import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.openapi.client.url.OpenApiPathBuilder; import com.ctrip.framework.apollo.openapi.dto.OpenClusterDTO; import com.google.common.base.Strings; import com.google.gson.Gson; @@ -38,10 +39,12 @@ public OpenClusterDTO getCluster(String appId, String env, String clusterName) { clusterName = ConfigConsts.CLUSTER_NAME_DEFAULT; } - String path = String.format("envs/%s/apps/%s/clusters/%s", escapePath(env), escapePath(appId), - escapePath(clusterName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenClusterDTO.class); } catch (Throwable ex) { throw new RuntimeException(String @@ -55,9 +58,12 @@ public OpenClusterDTO createCluster(String env, OpenClusterDTO openClusterDTO) { checkNotEmpty(openClusterDTO.getName(), "Cluster name"); checkNotEmpty(openClusterDTO.getDataChangeCreatedBy(), "Created by"); - String path = String.format("envs/%s/apps/%s/clusters", escapePath(env), escapePath(openClusterDTO.getAppId())); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(openClusterDTO.getAppId()) + .customResource("clusters"); - try (CloseableHttpResponse response = post(path, openClusterDTO)) { + try (CloseableHttpResponse response = post(pathBuilder, openClusterDTO)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenClusterDTO.class); } catch (Throwable ex) { throw new RuntimeException(String diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ItemOpenApiService.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ItemOpenApiService.java index 37a378e11d4..1dd6c0ec751 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ItemOpenApiService.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ItemOpenApiService.java @@ -18,6 +18,7 @@ import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.openapi.client.exception.ApolloOpenApiException; +import com.ctrip.framework.apollo.openapi.client.url.OpenApiPathBuilder; import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO; import com.google.common.base.Strings; import com.google.gson.Gson; @@ -43,10 +44,14 @@ public OpenItemDTO getItem(String appId, String env, String clusterName, String checkNotEmpty(env, "Env"); checkNotEmpty(key, "Item key"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName), escapePath(key)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(key); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenItemDTO.class); } catch (Throwable ex) { // return null if item doesn't exist @@ -72,10 +77,14 @@ public OpenItemDTO createItem(String appId, String env, String clusterName, Stri checkNotEmpty(itemDTO.getKey(), "Item key"); checkNotEmpty(itemDTO.getDataChangeCreatedBy(), "Item created by"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .customResource("items"); - try (CloseableHttpResponse response = post(path, itemDTO)) { + try (CloseableHttpResponse response = post(pathBuilder, itemDTO)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenItemDTO.class); } catch (Throwable ex) { throw new RuntimeException(String @@ -97,11 +106,14 @@ public void updateItem(String appId, String env, String clusterName, String name checkNotEmpty(itemDTO.getKey(), "Item key"); checkNotEmpty(itemDTO.getDataChangeLastModifiedBy(), "Item modified by"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName), - escapePath(itemDTO.getKey())); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(itemDTO.getKey()); - try (CloseableHttpResponse ignored = put(path, itemDTO)) { + try (CloseableHttpResponse ignored = put(pathBuilder, itemDTO)) { } catch (Throwable ex) { throw new RuntimeException(String .format("Update item: %s for appId: %s, cluster: %s, namespace: %s in env: %s failed", itemDTO.getKey(), @@ -126,11 +138,15 @@ public void createOrUpdateItem(String appId, String env, String clusterName, Str itemDTO.setDataChangeLastModifiedBy(itemDTO.getDataChangeCreatedBy()); } - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s?createIfNotExists=true", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName), - escapePath(itemDTO.getKey())); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(itemDTO.getKey()) + .addParam("createIfNotExists", "true"); - try (CloseableHttpResponse ignored = put(path, itemDTO)) { + try (CloseableHttpResponse ignored = put(pathBuilder, itemDTO)) { } catch (Throwable ex) { throw new RuntimeException(String .format("CreateOrUpdate item: %s for appId: %s, cluster: %s, namespace: %s in env: %s failed", itemDTO.getKey(), @@ -151,11 +167,15 @@ public void removeItem(String appId, String env, String clusterName, String name checkNotEmpty(key, "Item key"); checkNotEmpty(operator, "Operator"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s?operator=%s", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName), escapePath(key), - escapeParam(operator)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(key) + .addParam("operator", operator); - try (CloseableHttpResponse ignored = delete(path)) { + try (CloseableHttpResponse ignored = delete(pathBuilder)) { } catch (Throwable ex) { throw new RuntimeException(String .format("Remove item: %s for appId: %s, cluster: %s, namespace: %s in env: %s failed", key, appId, diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/NamespaceOpenApiService.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/NamespaceOpenApiService.java index e823f065089..e5d8145d893 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/NamespaceOpenApiService.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/NamespaceOpenApiService.java @@ -18,6 +18,7 @@ import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; +import com.ctrip.framework.apollo.openapi.client.url.OpenApiPathBuilder; import com.ctrip.framework.apollo.openapi.dto.OpenAppNamespaceDTO; import com.ctrip.framework.apollo.openapi.dto.OpenNamespaceDTO; import com.ctrip.framework.apollo.openapi.dto.OpenNamespaceLockDTO; @@ -49,10 +50,13 @@ public OpenNamespaceDTO getNamespace(String appId, String env, String clusterNam checkNotEmpty(appId, "App id"); checkNotEmpty(env, "Env"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s", escapePath(env), escapePath(appId), - escapePath(clusterName), escapePath(namespaceName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenNamespaceDTO.class); } catch (Throwable ex) { throw new RuntimeException(String @@ -69,10 +73,13 @@ public List getNamespaces(String appId, String env, String clu checkNotEmpty(appId, "App id"); checkNotEmpty(env, "Env"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces", escapePath(env), escapePath(appId), - escapePath(clusterName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .customResource("namespaces"); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OPEN_NAMESPACE_DTO_LIST_TYPE); } catch (Throwable ex) { throw new RuntimeException(String @@ -89,9 +96,11 @@ public OpenAppNamespaceDTO createAppNamespace(OpenAppNamespaceDTO appNamespaceDT appNamespaceDTO.setFormat(ConfigFileFormat.Properties.getValue()); } - String path = String.format("apps/%s/appnamespaces", escapePath(appNamespaceDTO.getAppId())); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .appsPathVal(appNamespaceDTO.getAppId()) + .customResource("appnamespaces"); - try (CloseableHttpResponse response = post(path, appNamespaceDTO)) { + try (CloseableHttpResponse response = post(pathBuilder, appNamespaceDTO)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenAppNamespaceDTO.class); } catch (Throwable ex) { throw new RuntimeException(String @@ -111,10 +120,14 @@ public OpenNamespaceLockDTO getNamespaceLock(String appId, String env, String cl checkNotEmpty(appId, "App id"); checkNotEmpty(env, "Env"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/lock", escapePath(env), escapePath(appId), - escapePath(clusterName), escapePath(namespaceName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .customResource("lock"); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenNamespaceLockDTO.class); } catch (Throwable ex) { throw new RuntimeException(String diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ReleaseOpenApiService.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ReleaseOpenApiService.java index 5b24321a468..75c63b92efb 100644 --- a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ReleaseOpenApiService.java +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/service/ReleaseOpenApiService.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.openapi.client.service; import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.openapi.client.url.OpenApiPathBuilder; import com.ctrip.framework.apollo.openapi.dto.NamespaceReleaseDTO; import com.ctrip.framework.apollo.openapi.dto.OpenReleaseDTO; import com.google.common.base.Strings; @@ -45,10 +46,14 @@ public OpenReleaseDTO publishNamespace(String appId, String env, String clusterN checkNotEmpty(releaseDTO.getReleaseTitle(), "Release title"); checkNotEmpty(releaseDTO.getReleasedBy(), "Released by"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/releases", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .customResource("releases"); - try (CloseableHttpResponse response = post(path, releaseDTO)) { + try (CloseableHttpResponse response = post(pathBuilder, releaseDTO)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenReleaseDTO.class); } catch (Throwable ex) { throw new RuntimeException(String @@ -68,10 +73,14 @@ public OpenReleaseDTO getLatestActiveRelease(String appId, String env, String cl checkNotEmpty(appId, "App id"); checkNotEmpty(env, "Env"); - String path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/releases/latest", - escapePath(env), escapePath(appId), escapePath(clusterName), escapePath(namespaceName)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .releasesPathVal("latest"); - try (CloseableHttpResponse response = get(path)) { + try (CloseableHttpResponse response = get(pathBuilder)) { return gson.fromJson(EntityUtils.toString(response.getEntity()), OpenReleaseDTO.class); } catch (Throwable ex) { throw new RuntimeException(String @@ -84,10 +93,13 @@ public void rollbackRelease(String env, long releaseId, String operator) { checkNotEmpty(env, "Env"); checkNotEmpty(operator, "Operator"); - String path = String.format("envs/%s/releases/%s/rollback?operator=%s", escapePath(env), releaseId, - escapeParam(operator)); + OpenApiPathBuilder pathBuilder = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .releasesPathVal(String.valueOf(releaseId)) + .customResource("rollback") + .addParam("operator", operator); - try (CloseableHttpResponse ignored = put(path, null)) { + try (CloseableHttpResponse ignored = put(pathBuilder, null)) { } catch (Throwable ex) { throw new RuntimeException(String.format("Rollback release: %s in env: %s failed", releaseId, env), ex); } diff --git a/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilder.java b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilder.java new file mode 100644 index 00000000000..4df4d98a6e4 --- /dev/null +++ b/apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilder.java @@ -0,0 +1,152 @@ +/* + * Copyright 2021 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.openapi.client.url; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.escape.Escaper; +import com.google.common.net.UrlEscapers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OpenApiPathBuilder { + private static final String ENV_PATH = "env"; + private static final String ENVS_PATH = "envs"; + private static final String APPS_PATH = "apps"; + private static final String CLUSTERS_PATH = "clusters"; + private static final String NAMESPACES_PATH = "namespaces"; + private static final String ITEMS_PATH = "items"; + private static final String RELEASE_PATH = "releases"; + + private final static List SORTED_PATH_KEYS = Arrays.asList(ENVS_PATH, ENV_PATH, APPS_PATH, CLUSTERS_PATH, + NAMESPACES_PATH, ITEMS_PATH, RELEASE_PATH); + + private static final Escaper PATH_ESCAPER = UrlEscapers.urlPathSegmentEscaper(); + private static final Escaper QUERY_PARAM_ESCAPER = UrlEscapers.urlFormParameterEscaper(); + private static final Joiner PATH_JOIN = Joiner.on("/"); + + private final Map pathVariable; + private final Map params; + + private String customResource; + + public static OpenApiPathBuilder newBuilder() { + return new OpenApiPathBuilder(); + } + + private OpenApiPathBuilder() { + this.pathVariable = new HashMap<>(); + this.params = new HashMap<>(); + } + + public OpenApiPathBuilder envPathVal(String env) { + pathVariable.put(ENV_PATH, escapePath(env)); + return this; + } + + public OpenApiPathBuilder envsPathVal(String envs) { + pathVariable.put(ENVS_PATH, escapePath(envs)); + return this; + } + + public OpenApiPathBuilder appsPathVal(String apps) { + pathVariable.put(APPS_PATH, escapePath(apps)); + return this; + } + + public OpenApiPathBuilder clustersPathVal(String clusters) { + pathVariable.put(CLUSTERS_PATH, escapePath(clusters)); + return this; + } + + public OpenApiPathBuilder namespacesPathVal(String namespaces) { + pathVariable.put(NAMESPACES_PATH, escapePath(namespaces)); + return this; + } + + public OpenApiPathBuilder itemsPathVal(String items) { + pathVariable.put(ITEMS_PATH, escapePath(items)); + return this; + } + + public OpenApiPathBuilder releasesPathVal(String releases) { + pathVariable.put(RELEASE_PATH, escapePath(releases)); + return this; + } + + public OpenApiPathBuilder customResource(String customResource) { + this.customResource = customResource; + return this; + } + + public OpenApiPathBuilder addParam(String key, Object value) { + if (Strings.isNullOrEmpty(key)) { + throw new IllegalArgumentException("Param key should not be null or empty"); + } + this.params.put(key, escapeParam(String.valueOf(value))); + return this; + } + + public String buildPath(String baseUrl) { + if (Strings.isNullOrEmpty(baseUrl)) { + throw new IllegalArgumentException("Base url should not be null or empty"); + } + List parts = new ArrayList<>(); + parts.add(baseUrl); + + for (String k : SORTED_PATH_KEYS) { + if (pathVariable.containsKey(k)) { + parts.add(k); + String v = pathVariable.get(k); + if (!Strings.isNullOrEmpty(v)) { + parts.add(v); + } + } + } + + if (!Strings.isNullOrEmpty(this.customResource)) { + parts.add(this.customResource); + } + + String path = PATH_JOIN.join(parts); + + if (!params.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry kv : params.entrySet()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(kv.getKey()).append("=").append(kv.getValue()); + } + path += "?" + sb; + } + return path; + } + + protected String escapePath(String path) { + return PATH_ESCAPER.escape(path); + } + + protected String escapeParam(String param) { + return QUERY_PARAM_ESCAPER.escape(param); + } + +} diff --git a/apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilderTest.java b/apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilderTest.java new file mode 100644 index 00000000000..e22ec76d650 --- /dev/null +++ b/apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilderTest.java @@ -0,0 +1,234 @@ +/* + * Copyright 2021 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.openapi.client.url; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class OpenApiPathBuilderTest { + + @Test + public void testBuildPath() { + String baseURL = "http://localhost"; + OpenApiPathBuilder tools = OpenApiPathBuilder.newBuilder(); + String path, expected, actual; + String env = "test"; + String appId = "appid-1001"; + String clusterName = "cluster-1001"; + String namespaceName = "application.yml"; + String key = "spring.profile"; + String operator = "junit"; + long releaseId = 1L; + + // AppOpenApiService path check + + path = String.format("apps/%s/envclusters", tools.escapePath(appId)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .appsPathVal(appId) + .customResource("envclusters") + .buildPath(baseURL); + assertEquals(expected, actual); + + String param = "1,2,3"; + path = String.format("apps?appIds=%s", tools.escapeParam(param)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .customResource("apps") + .addParam("appIds", param) + .buildPath(baseURL); + assertEquals(expected, actual); + + path = "apps/authorized"; + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .customResource("apps/authorized") + .buildPath(baseURL); + assertEquals(expected, actual); + + // ClusterOpenApiService path check + + path = String.format("envs/%s/apps/%s/clusters/%s", tools.escapePath(env), tools.escapePath(appId), + tools.escapePath(clusterName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters", tools.escapePath(env), tools.escapePath(appId)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .customResource("clusters") + .buildPath(baseURL); + assertEquals(expected, actual); + + // ItemOpenApiService path check + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s", + tools.escapePath(env), tools.escapePath(appId), tools.escapePath(clusterName), + tools.escapePath(namespaceName), tools.escapePath(key)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(key) + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items", + tools.escapePath(env), tools.escapePath(appId), tools.escapePath(clusterName), + tools.escapePath(namespaceName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .customResource("items") + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s?createIfNotExists=true", + tools.escapePath(env), tools.escapePath(appId), tools.escapePath(clusterName), + tools.escapePath(namespaceName), tools.escapePath(key)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(key) + .addParam("createIfNotExists", "true") + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/items/%s?operator=%s", + tools.escapePath(env), tools.escapePath(appId), tools.escapePath(clusterName), + tools.escapePath(namespaceName), tools.escapePath(key), tools.escapeParam(operator)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .itemsPathVal(key) + .addParam("operator", operator) + .buildPath(baseURL); + assertEquals(expected, actual); + + // NamespaceOpenApiService path check + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s", tools.escapePath(env), + tools.escapePath(appId), tools.escapePath(clusterName), tools.escapePath(namespaceName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces", tools.escapePath(env), + tools.escapePath(appId), tools.escapePath(clusterName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .customResource("namespaces") + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("apps/%s/appnamespaces", tools.escapePath(appId)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .appsPathVal(appId) + .customResource("appnamespaces") + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/lock", tools.escapePath(env), + tools.escapePath(appId), tools.escapePath(clusterName), tools.escapePath(namespaceName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .customResource("lock") + .buildPath(baseURL); + assertEquals(expected, actual); + + // ReleaseOpenApiService path check + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/releases", + tools.escapePath(env), tools.escapePath(appId), tools.escapePath(clusterName), + tools.escapePath(namespaceName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .customResource("releases") + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/apps/%s/clusters/%s/namespaces/%s/releases/latest", + tools.escapePath(env), tools.escapePath(appId), tools.escapePath(clusterName), + tools.escapePath(namespaceName)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .appsPathVal(appId) + .clustersPathVal(clusterName) + .namespacesPathVal(namespaceName) + .releasesPathVal("latest") + .buildPath(baseURL); + assertEquals(expected, actual); + + path = String.format("envs/%s/releases/%s/rollback?operator=%s", tools.escapePath(env), releaseId, + tools.escapeParam(operator)); + expected = String.format("%s/%s", baseURL, path); + actual = OpenApiPathBuilder.newBuilder() + .envsPathVal(env) + .releasesPathVal(String.valueOf(releaseId)) + .customResource("rollback") + .addParam("operator", operator) + .buildPath(baseURL); + assertEquals(expected, actual); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddParamKeyEmpty() { + OpenApiPathBuilder.newBuilder().addParam("", ""); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildPathURLEmpty() { + OpenApiPathBuilder.newBuilder().buildPath(""); + } +} \ No newline at end of file