diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e289cdd2a8..d0634d9a9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### 4.10-SNAPSHOT #### Bugs +* Fix #2066: Separated Kubernetes and OpenShift Handler interfaces to avoid collission in uberjar #### Improvements diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java index 19f6a9b9cd2..d800090dcb2 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java @@ -256,7 +256,7 @@ public HasMetadata waitUntilCondition(Predicate condition, long amo } - private static HasMetadata acceptVisitors(HasMetadata item, List visitors) { + protected HasMetadata acceptVisitors(HasMetadata item, List visitors) { ResourceHandler h = handlerOf(item); VisitableBuilder builder = h.edit(item); @@ -267,7 +267,7 @@ private static HasMetadata acceptVisitors(HasMetadata item, List visito return builder.build(); } - private static List acceptVisitors(List list, List visitors) { + protected List acceptVisitors(List list, List visitors) { List result = new ArrayList<>(); for (HasMetadata item : list) { ResourceHandler h = handlerOf(item); @@ -282,7 +282,7 @@ private static List acceptVisitors(List list, List HasMetadata asHasMetadata(T item) { + protected HasMetadata asHasMetadata(T item) { if (item instanceof HasMetadata) { return (HasMetadata) item; } else if (item instanceof String) { @@ -295,7 +295,7 @@ private static HasMetadata asHasMetadata(T item) { throw new IllegalArgumentException("Item needs to be an instance of HasMetadata or String."); } - private static ResourceHandler handlerOf(T item) { + protected ResourceHandler handlerOf(T item) { if (item instanceof HasMetadata) { return Handlers.get(((HasMetadata) item).getKind(), ((HasMetadata) item).getApiVersion()); } else if (item instanceof KubernetesList) { diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java index e652fbba76a..21afae31b5c 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java @@ -325,7 +325,7 @@ public List get() { } } - private static List acceptVisitors(List list, List visitors) { + public List acceptVisitors(List list, List visitors) { List result = new ArrayList<>(); for (HasMetadata item : list) { ResourceHandler h = handlerOf(item); @@ -410,7 +410,7 @@ private static List asHasMetadata(T item, Boolean enableProcces return result; } - private static ResourceHandler handlerOf(T item) { + public ResourceHandler handlerOf(T item) { if (item instanceof HasMetadata) { return Handlers.get(((HasMetadata) item).getKind(), ((HasMetadata) item).getApiVersion()); } else if (item instanceof KubernetesList) { diff --git a/openshift-client/pom.xml b/openshift-client/pom.xml index 6f8da9fcd4a..4806a20fd34 100644 --- a/openshift-client/pom.xml +++ b/openshift-client/pom.xml @@ -190,7 +190,7 @@ ${osgi.activator} ${osgi.export.service} - /META-INF/services/io.fabric8.kubernetes.client.ResourceHandler=target/classes/META-INF/services/io.fabric8.kubernetes.client.ResourceHandler + /META-INF/services/io.fabric8.openshift.client.OpenShiftResourceHandler=target/classes/META-INF/services/io.fabric8.openshift.client.OpenShiftResourceHandler bundle diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/CodgeGen.java b/openshift-client/src/main/java/io/fabric8/openshift/client/CodgeGen.java index f905645bfab..c64f015ad13 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/CodgeGen.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/CodgeGen.java @@ -23,7 +23,7 @@ value = { @VelocityTransformation("/resource-operation.vm"), @VelocityTransformation("/resource-handler.vm"), - @VelocityTransformation(value = "/resource-handler-services.vm", gather = true, outputPath = "META-INF/services/io.fabric8.kubernetes.client.ResourceHandler") + @VelocityTransformation(value = "/resource-handler-services.vm", gather = true, outputPath = "META-INF/services/io.fabric8.openshift.client.OpenShiftResourceHandler") }, resources = { @ResourceSelector("openshift.properties") diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java b/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java index 9b2e1ac4f78..9b0a8c80c0b 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java @@ -15,11 +15,13 @@ */ package io.fabric8.openshift.client; +import io.fabric8.kubernetes.api.builder.Visitor; import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.DoneableBinding; import io.fabric8.kubernetes.api.model.DoneableComponentStatus; import io.fabric8.kubernetes.api.model.DoneableConfigMap; import io.fabric8.kubernetes.api.model.DoneableEndpoints; +import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.events.DoneableEvent; import io.fabric8.kubernetes.api.model.DoneableLimitRange; import io.fabric8.kubernetes.api.model.DoneableNamespace; @@ -129,6 +131,7 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.Objects; import java.util.Collection; import java.util.HashMap; @@ -138,7 +141,7 @@ import java.util.concurrent.ForkJoinPool; /** - * Class for Default Openshift Client implementing KubernetesClient interface. + * Class for Default OpenShift Client implementing KubernetesClient interface. * It is thread safe. */ public class DefaultOpenShiftClient extends BaseClient implements NamespacedOpenShiftClient { @@ -235,37 +238,42 @@ public MixedOperation load(InputStream is) { - return delegate.load(is); + return new OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl(httpClient, getConfiguration(), getNamespace(), null, false, false, new ArrayList<>(), is, null, true, DeletionPropagation.BACKGROUND) { + }; } @Override public NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicable resource(HasMetadata item) { - return delegate.resource(item); + return new OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl(httpClient, getConfiguration(), getNamespace(), null, false, false, new ArrayList(), item, -1, DeletionPropagation.BACKGROUND, true) { + }; } @Override public NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicable resource(String s) { - return delegate.resource(s); + return new OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl(httpClient, getConfiguration(), getNamespace(), null, false, false, new ArrayList(), s, -1, DeletionPropagation.BACKGROUND, true) { + }; } @Override - public NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable resourceList(KubernetesResourceList is) { - return delegate.resourceList(is); + public NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable resourceList(KubernetesResourceList item) { + return new OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl(httpClient, getConfiguration(), getNamespace(), null, false, false, new ArrayList(), item, null, null, -1, DeletionPropagation.BACKGROUND, true) { + }; } @Override public NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable resourceList(HasMetadata... items) { - return delegate.resourceList(items); + return resourceList(new KubernetesListBuilder().withItems(items).build()); } @Override public NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable resourceList(Collection items) { - return delegate.resourceList(items); + return resourceList(new KubernetesListBuilder().withItems(new ArrayList(items)).build()); } @Override public ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable resourceList(String s) { - return delegate.resourceList(s); + return new OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl(httpClient, getConfiguration(), getNamespace(), null, false, false, new ArrayList(), s, null, null, -1, DeletionPropagation.BACKGROUND, true) { + }; } @Override diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftHandlers.java b/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftHandlers.java new file mode 100644 index 00000000000..8d087857262 --- /dev/null +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftHandlers.java @@ -0,0 +1,94 @@ +package io.fabric8.openshift.client; + +import io.fabric8.kubernetes.api.builder.VisitableBuilder; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.Handlers; +import io.fabric8.kubernetes.client.ResourceHandler; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.function.Predicate; + +public final class OpenShiftHandlers { + + private static final Set CLASS_LOADERS = new HashSet<>(); + private static final Map RESOURCE_HANDLER_MAP = new HashMap<>(); + static { + // Register Kubernetes Handlers + discoverHandlers(ResourceHandler.class.getClassLoader()); + + // Register OpenShift Handlers + discoverHandlers(OpenShiftResourceHandler.class.getClassLoader()); + } + + public static > void register(ResourceHandler handler) { + RESOURCE_HANDLER_MAP.put(new ResourceHandler.Key(handler.getKind().toLowerCase(Locale.ROOT), handler.getApiVersion()), handler); + } + + public static > void unregister(ResourceHandler handler) { + RESOURCE_HANDLER_MAP.remove(new ResourceHandler.Key(handler.getKind().toLowerCase(Locale.ROOT), handler.getApiVersion())); + } + + public static > ResourceHandler get(String kind, String apiVersion) { + return get(kind, apiVersion, Thread.currentThread().getContextClassLoader()); + } + + public static > ResourceHandler get(String kind, String apiVersion, ClassLoader classLoader) { + return get(new ResourceHandler.Key(kind.toLowerCase(Locale.ROOT), apiVersion), classLoader); + } + + public static > ResourceHandler get(ResourceHandler.Key key) { + return get(key, Thread.currentThread().getContextClassLoader()); + } + + public static > ResourceHandler get(ResourceHandler.Key key, ClassLoader classLoader) { + if (RESOURCE_HANDLER_MAP.containsKey(key)) { + return RESOURCE_HANDLER_MAP.get(key); + } else { + // Check for Kubernetes + ResourceHandler resourceHandler = getResourceHandlerForClass(key, ResourceHandler.class, classLoader); + if (resourceHandler != null) { + return resourceHandler; + } + + // Check for OpenShift + resourceHandler = getResourceHandlerForClass(key, OpenShiftResourceHandler.class, classLoader); + return resourceHandler; + } + } + + private static > ResourceHandler fetchResourceHandlerFromServiceLoader(Class handlerClass, ClassLoader classLoader, Predicate> condition) { + for (ResourceHandler handler : ServiceLoader.load(handlerClass, classLoader)) { + if (condition.test(handler)) { + return handler; + } + } + return null; + } + + private static > ResourceHandler getResourceHandlerForClass(ResourceHandler.Key key, Class className, ClassLoader classLoader) { + //1st pass: match kind and apiVersion + ResourceHandler handler = fetchResourceHandlerFromServiceLoader(className, classLoader, h -> h.getKind().toLowerCase(Locale.ROOT).equals(key.getKind()) && h.getApiVersion().equals(key.getApiVersion())); + if (handler != null) { + return handler; + } + + + //2nd pass: match kind. + handler = fetchResourceHandlerFromServiceLoader(className, classLoader, h -> h.getKind().toLowerCase(Locale.ROOT).equals(key.getKind())); + return handler; + } + + protected static void discoverHandlers(ClassLoader classLoader) { + if (classLoader != null && CLASS_LOADERS.add(classLoader)) { + for (ResourceHandler handler : ServiceLoader.load(ResourceHandler.class, classLoader)) { + register(handler); + } + } + } + +} diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftResourceHandler.java b/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftResourceHandler.java new file mode 100644 index 00000000000..c096857a576 --- /dev/null +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftResourceHandler.java @@ -0,0 +1,7 @@ +package io.fabric8.openshift.client; + +import io.fabric8.kubernetes.api.builder.VisitableBuilder; +import io.fabric8.kubernetes.client.ResourceHandler; + +public interface OpenShiftResourceHandler> extends ResourceHandler { +} diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java new file mode 100644 index 00000000000..273dda24dc0 --- /dev/null +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java @@ -0,0 +1,34 @@ +package io.fabric8.openshift.client.dsl.internal; + +import io.fabric8.kubernetes.api.builder.Visitor; +import io.fabric8.kubernetes.api.model.DeletionPropagation; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.HasMetadataVisitiableBuilder; +import io.fabric8.kubernetes.client.ResourceHandler; +import io.fabric8.kubernetes.client.dsl.internal.NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl; +import io.fabric8.kubernetes.client.handlers.KubernetesListHandler; +import io.fabric8.openshift.client.OpenShiftHandlers; +import okhttp3.OkHttpClient; + +import java.util.List; + +public class OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl extends NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl { + + public OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl(OkHttpClient client, Config config, String namespace, String explicitNamespace, Boolean fromServer, Boolean deletingExisting, List visitors, Object item, long gracePeriodSeconds, DeletionPropagation propagationPolicy, Boolean cascading) { + super(client, config, namespace, explicitNamespace, fromServer, deletingExisting, visitors, item, gracePeriodSeconds, propagationPolicy, cascading); + } + + @Override + public ResourceHandler handlerOf(T item) { + if (item instanceof HasMetadata) { + return OpenShiftHandlers.get(((HasMetadata) item).getKind(), ((HasMetadata) item).getApiVersion()); + } else if (item instanceof KubernetesList) { + return new KubernetesListHandler(); + } else { + throw new IllegalArgumentException("Could not find a registered handler for item: [" + item + "]."); + } + } + +} diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java new file mode 100644 index 00000000000..ede11cd5ada --- /dev/null +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java @@ -0,0 +1,38 @@ +package io.fabric8.openshift.client.dsl.internal; + +import io.fabric8.kubernetes.api.builder.Visitor; +import io.fabric8.kubernetes.api.model.DeletionPropagation; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.HasMetadataVisitiableBuilder; +import io.fabric8.kubernetes.client.ResourceHandler; +import io.fabric8.kubernetes.client.dsl.internal.NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl; +import io.fabric8.kubernetes.client.handlers.KubernetesListHandler; +import io.fabric8.openshift.client.OpenShiftHandlers; +import okhttp3.OkHttpClient; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +public class OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl extends NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl { + public OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl(OkHttpClient client, Config config, String namespace, String explicitNamespace, Boolean fromServer, Boolean deletingExisting, List visitors, InputStream is, Map parameters, Boolean cascading, DeletionPropagation propagationPolicy) { + super(client, config, namespace, explicitNamespace, fromServer, deletingExisting, visitors, is, parameters, cascading, propagationPolicy); + } + + public OpenShiftNamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl(OkHttpClient client, Config config, String namespace, String explicitNamespace, Boolean fromServer, Boolean deletingExisting, List visitors, Object item, InputStream inputStream, Map parameters, long gracePeriodSeconds, DeletionPropagation propagationPolicy, Boolean cascading) { + super(client, config, namespace, explicitNamespace, fromServer, deletingExisting, visitors, item, inputStream, parameters, gracePeriodSeconds, propagationPolicy, cascading); + } + + @Override + public ResourceHandler handlerOf(T item) { + if (item instanceof HasMetadata) { + return OpenShiftHandlers.get(((HasMetadata) item).getKind(), ((HasMetadata) item).getApiVersion()); + } else if (item instanceof KubernetesList) { + return new KubernetesListHandler(); + } else { + throw new IllegalArgumentException("Could not find a registered handler for item: [" + item + "]."); + } + } +} diff --git a/openshift-client/src/main/resources/resource-handler.vm b/openshift-client/src/main/resources/resource-handler.vm index e1a3a4e82e9..d16ab5a96c6 100644 --- a/openshift-client/src/main/resources/resource-handler.vm +++ b/openshift-client/src/main/resources/resource-handler.vm @@ -47,7 +47,7 @@ import java.util.function.Predicate; import io.fabric8.kubernetes.client.Config; import io.fabric8.openshift.client.OpenShiftConfig; -import io.fabric8.kubernetes.client.ResourceHandler; +import io.fabric8.openshift.client.OpenShiftResourceHandler; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.openshift.client.dsl.internal.${model.name}OperationsImpl; @@ -64,7 +64,7 @@ import ${model.fullyQualifiedName}Builder; import java.util.TreeMap; import java.util.concurrent.TimeUnit; -public class ${model.name}Handler implements ResourceHandler<${model.name}, ${model.name}Builder> { +public class ${model.name}Handler implements OpenShiftResourceHandler<${model.name}, ${model.name}Builder> { @Override public String getKind() { diff --git a/openshift-client/src/test/java/io/fabric8/openshift/client/HandlersTest.java b/openshift-client/src/test/java/io/fabric8/openshift/client/HandlersTest.java index d3d7059769b..0fa93917429 100644 --- a/openshift-client/src/test/java/io/fabric8/openshift/client/HandlersTest.java +++ b/openshift-client/src/test/java/io/fabric8/openshift/client/HandlersTest.java @@ -16,6 +16,7 @@ package io.fabric8.openshift.client; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.Handlers; import io.fabric8.kubernetes.client.ResourceHandler; import io.fabric8.openshift.api.model.Build; import io.fabric8.openshift.api.model.BuildConfig; @@ -52,6 +53,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import java.util.Locale; @@ -77,6 +79,40 @@ public void checkHandlers() { checkHandler(new User(), new UserHandler()); } + @Test + public void shouldLoadKubernetesResourceFromResourceHandler() { + assertNotNull(Handlers.get("Deployment", "apps/v1")); + assertNotNull(Handlers.get("StatefulSet", "apps/v1")); + assertNotNull(Handlers.get("DaemonSet", "apps/v1")); + assertNotNull(Handlers.get("ReplicaSet", "apps/v1")); + assertNotNull(Handlers.get("Service", "v1")); + assertNotNull(Handlers.get("Pod", "v1")); + } + + @Test + public void shouldLoadOpenShiftResourceFromOpenShiftResourceHandler() { + assertNotNull(OpenShiftHandlers.get("DeploymentConfig", "v1")); + assertNotNull(OpenShiftHandlers.get("Build", "v1")); + assertNotNull(OpenShiftHandlers.get("BuildConfig", "v1")); + assertNotNull(OpenShiftHandlers.get("Group", "v1")); + assertNotNull(OpenShiftHandlers.get("ImageStream", "v1")); + assertNotNull(OpenShiftHandlers.get("ImageStreamTag", "v1")); + assertNotNull(OpenShiftHandlers.get("Project", "v1")); + assertNotNull(OpenShiftHandlers.get("Route", "v1")); + assertNotNull(OpenShiftHandlers.get("SecurityContextConstraints", "v1")); + assertNotNull(OpenShiftHandlers.get("User", "v1")); + } + + @Test + public void shouldLoadKubernetesResourceFromOpenShiftResourceHandler() { + assertNotNull(OpenShiftHandlers.get("Deployment", "apps/v1")); + assertNotNull(OpenShiftHandlers.get("StatefulSet", "apps/v1")); + assertNotNull(OpenShiftHandlers.get("DaemonSet", "apps/v1")); + assertNotNull(OpenShiftHandlers.get("ReplicaSet", "apps/v1")); + assertNotNull(OpenShiftHandlers.get("Service", "v1")); + assertNotNull(OpenShiftHandlers.get("Pod", "v1")); + } + private void checkHandler(HasMetadata hasMetadata, ResourceHandler handler) { assertEquals(hasMetadata.getKind().toLowerCase(Locale.ROOT), handler.getKind().toLowerCase(Locale.ROOT)); assertEquals(hasMetadata.getApiVersion().toLowerCase(Locale.ROOT), handler.getApiVersion().toLowerCase(Locale.ROOT));