From 2be9f7511da52c2143c76693989bd09ed1e05c56 Mon Sep 17 00:00:00 2001 From: Peter Darton Date: Wed, 10 Mar 2021 22:53:36 +0000 Subject: [PATCH 1/3] Add Delegating DockerClient code. --- pom.xml | 14 + .../client/DelegatingDockerClient.java | 437 ++++++++++++++++++ .../client/DelegatingDockerClientTest.java | 157 +++++++ 3 files changed, 608 insertions(+) create mode 100644 src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java create mode 100644 src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java diff --git a/pom.xml b/pom.xml index f0a41b0..5371602 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ -SNAPSHOT 1.609.1 8 + UTF-8 3.1 @@ -126,5 +127,18 @@ jackson2-api 2.6.4 + + + org.hamcrest + hamcrest-core + 2.2 + test + + + org.mockito + mockito-core + 2.10.0 + test + diff --git a/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java b/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java new file mode 100644 index 0000000..4eb5aa3 --- /dev/null +++ b/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java @@ -0,0 +1,437 @@ +// Copyright 2011 Peter Darton +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package io.jenkins.dockerjavaapi.client; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nonnull; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.exception.DockerException; +import com.github.dockerjava.api.model.*; + +// MAINTENANCE NOTE: +// The DockerClient API varies depending on the version of the docker-java +// library that this plugin provides. +// It may be necessary to add/remove methods from this class when changing +// the version of docker-java. +// ...but, by having this here, it allows all other Jenkins plugins to be much +// less affected by changes to the version of docker-java, meaning that this +// plugin can be upgraded without breaking every other docker-using plugin. + +/** + * Simple delegate class for the {@link DockerClient} interface. + *

+ * This makes it easy for other classes to override specific methods without + * having to implement all of them. + *

+ * If you are writing a Jenkins plugin that needs a class to implement/wrap + * {@link DockerClient}, you'd be best advised to extend this one, otherwise + * your code could fail whenever the version of this plugin changes. + */ +@SuppressWarnings("deprecation") +public class DelegatingDockerClient implements DockerClient { + + private final DockerClient delegate; + + /** + * Constructs a new instance that delegates all API calls to the specified + * {@link DockerClient}. + * + * @param delegate The {@link DockerClient} to delegate to. + */ + public DelegatingDockerClient(@Nonnull DockerClient delegate) { + this.delegate = delegate; + } + + /** + * Obtains the underlying {@link DockerClient} interface. Subclasses can + * override this if they need to hook into every call. + * + * @return the {@link DockerClient} to be delegated to. + */ + @Nonnull + protected DockerClient getDelegate() { + return delegate; + } + + @Override + public AttachContainerCmd attachContainerCmd(String arg0) { + return getDelegate().attachContainerCmd(arg0); + } + + @Override + public AuthCmd authCmd() { + return getDelegate().authCmd(); + } + + @Override + public AuthConfig authConfig() throws DockerException { + return getDelegate().authConfig(); + } + + @Override + public BuildImageCmd buildImageCmd() { + return getDelegate().buildImageCmd(); + } + + @Override + public BuildImageCmd buildImageCmd(File arg0) { + return getDelegate().buildImageCmd(arg0); + } + + @Override + public BuildImageCmd buildImageCmd(InputStream arg0) { + return getDelegate().buildImageCmd(arg0); + } + + @Override + public void close() throws IOException { + getDelegate().close(); + } + + @Override + public CommitCmd commitCmd(String arg0) { + return getDelegate().commitCmd(arg0); + } + + @Override + public ConnectToNetworkCmd connectToNetworkCmd() { + return getDelegate().connectToNetworkCmd(); + } + + @Override + public ContainerDiffCmd containerDiffCmd(String arg0) { + return getDelegate().containerDiffCmd(arg0); + } + + @Override + public CopyArchiveFromContainerCmd copyArchiveFromContainerCmd(String arg0, String arg1) { + return getDelegate().copyArchiveFromContainerCmd(arg0, arg1); + } + + @Override + public CopyArchiveToContainerCmd copyArchiveToContainerCmd(String arg0) { + return getDelegate().copyArchiveToContainerCmd(arg0); + } + + @Override + public CopyFileFromContainerCmd copyFileFromContainerCmd(String arg0, String arg1) { + return getDelegate().copyFileFromContainerCmd(arg0, arg1); + } + + @Override + public CreateContainerCmd createContainerCmd(String arg0) { + return getDelegate().createContainerCmd(arg0); + } + + @Override + public CreateImageCmd createImageCmd(String arg0, InputStream arg1) { + return getDelegate().createImageCmd(arg0, arg1); + } + + @Override + public CreateNetworkCmd createNetworkCmd() { + return getDelegate().createNetworkCmd(); + } + + @Override + public CreateVolumeCmd createVolumeCmd() { + return getDelegate().createVolumeCmd(); + } + + @Override + public DisconnectFromNetworkCmd disconnectFromNetworkCmd() { + return getDelegate().disconnectFromNetworkCmd(); + } + + @Override + public EventsCmd eventsCmd() { + return getDelegate().eventsCmd(); + } + + @Override + public ExecCreateCmd execCreateCmd(String arg0) { + return getDelegate().execCreateCmd(arg0); + } + + @Override + public ExecStartCmd execStartCmd(String arg0) { + return getDelegate().execStartCmd(arg0); + } + + @Override + public InfoCmd infoCmd() { + return getDelegate().infoCmd(); + } + + @Override + public InspectContainerCmd inspectContainerCmd(String arg0) { + return getDelegate().inspectContainerCmd(arg0); + } + + @Override + public InspectExecCmd inspectExecCmd(String arg0) { + return getDelegate().inspectExecCmd(arg0); + } + + @Override + public InspectImageCmd inspectImageCmd(String arg0) { + return getDelegate().inspectImageCmd(arg0); + } + + @Override + public InspectNetworkCmd inspectNetworkCmd() { + return getDelegate().inspectNetworkCmd(); + } + + @Override + public InspectVolumeCmd inspectVolumeCmd(String arg0) { + return getDelegate().inspectVolumeCmd(arg0); + } + + @Override + public KillContainerCmd killContainerCmd(String arg0) { + return getDelegate().killContainerCmd(arg0); + } + + @Override + public ListContainersCmd listContainersCmd() { + return getDelegate().listContainersCmd(); + } + + @Override + public ListImagesCmd listImagesCmd() { + return getDelegate().listImagesCmd(); + } + + @Override + public ListNetworksCmd listNetworksCmd() { + return getDelegate().listNetworksCmd(); + } + + @Override + public ListVolumesCmd listVolumesCmd() { + return getDelegate().listVolumesCmd(); + } + + @Override + public LoadImageCmd loadImageCmd(InputStream arg0) { + return getDelegate().loadImageCmd(arg0); + } + + @Override + public LogContainerCmd logContainerCmd(String arg0) { + return getDelegate().logContainerCmd(arg0); + } + + @Override + public PauseContainerCmd pauseContainerCmd(String arg0) { + return getDelegate().pauseContainerCmd(arg0); + } + + @Override + public PingCmd pingCmd() { + return getDelegate().pingCmd(); + } + + @Override + public PullImageCmd pullImageCmd(String arg0) { + return getDelegate().pullImageCmd(arg0); + } + + @Override + public PushImageCmd pushImageCmd(String arg0) { + return getDelegate().pushImageCmd(arg0); + } + + @Override + public PushImageCmd pushImageCmd(Identifier arg0) { + return getDelegate().pushImageCmd(arg0); + } + + @Override + public RemoveContainerCmd removeContainerCmd(String arg0) { + return getDelegate().removeContainerCmd(arg0); + } + + @Override + public RemoveImageCmd removeImageCmd(String arg0) { + return getDelegate().removeImageCmd(arg0); + } + + @Override + public RemoveNetworkCmd removeNetworkCmd(String arg0) { + return getDelegate().removeNetworkCmd(arg0); + } + + @Override + public RemoveVolumeCmd removeVolumeCmd(String arg0) { + return getDelegate().removeVolumeCmd(arg0); + } + + @Override + public RenameContainerCmd renameContainerCmd(String arg0) { + return getDelegate().renameContainerCmd(arg0); + } + + @Override + public RestartContainerCmd restartContainerCmd(String arg0) { + return getDelegate().restartContainerCmd(arg0); + } + + @Override + public SaveImageCmd saveImageCmd(String arg0) { + return getDelegate().saveImageCmd(arg0); + } + + @Override + public SearchImagesCmd searchImagesCmd(String arg0) { + return getDelegate().searchImagesCmd(arg0); + } + + @Override + public StartContainerCmd startContainerCmd(String arg0) { + return getDelegate().startContainerCmd(arg0); + } + + @Override + public StatsCmd statsCmd(String arg0) { + return getDelegate().statsCmd(arg0); + } + + @Override + public StopContainerCmd stopContainerCmd(String arg0) { + return getDelegate().stopContainerCmd(arg0); + } + + @Override + public TagImageCmd tagImageCmd(String arg0, String arg1, String arg2) { + return getDelegate().tagImageCmd(arg0, arg1, arg2); + } + + @Override + public TopContainerCmd topContainerCmd(String arg0) { + return getDelegate().topContainerCmd(arg0); + } + + @Override + public UnpauseContainerCmd unpauseContainerCmd(String arg0) { + return getDelegate().unpauseContainerCmd(arg0); + } + + @Override + public UpdateContainerCmd updateContainerCmd(String arg0) { + return getDelegate().updateContainerCmd(arg0); + } + + @Override + public VersionCmd versionCmd() { + return getDelegate().versionCmd(); + } + + @Override + public WaitContainerCmd waitContainerCmd(String arg0) { + return getDelegate().waitContainerCmd(arg0); + } + + @Override + public InitializeSwarmCmd initializeSwarmCmd(SwarmSpec swarmSpec) { + return getDelegate().initializeSwarmCmd(swarmSpec); + } + + @Override + public InspectSwarmCmd inspectSwarmCmd() { + return getDelegate().inspectSwarmCmd(); + } + + @Override + public JoinSwarmCmd joinSwarmCmd() { + return getDelegate().joinSwarmCmd(); + } + + @Override + public LeaveSwarmCmd leaveSwarmCmd() { + return getDelegate().leaveSwarmCmd(); + } + + @Override + public UpdateSwarmCmd updateSwarmCmd(SwarmSpec swarmSpec) { + return getDelegate().updateSwarmCmd(swarmSpec); + } + + @Override + public UpdateSwarmNodeCmd updateSwarmNodeCmd() { + return getDelegate().updateSwarmNodeCmd(); + } + + @Override + public ListSwarmNodesCmd listSwarmNodesCmd() { + return getDelegate().listSwarmNodesCmd(); + } + + @Override + public ListServicesCmd listServicesCmd() { + return getDelegate().listServicesCmd(); + } + + @Override + public CreateServiceCmd createServiceCmd(ServiceSpec serviceSpec) { + return getDelegate().createServiceCmd(serviceSpec); + } + + @Override + public InspectServiceCmd inspectServiceCmd(String serviceId) { + return getDelegate().inspectServiceCmd(serviceId); + } + + @Override + public UpdateServiceCmd updateServiceCmd(String serviceId, ServiceSpec serviceSpec) { + return getDelegate().updateServiceCmd(serviceId, serviceSpec); + } + + @Override + public RemoveServiceCmd removeServiceCmd(String serviceId) { + return getDelegate().removeServiceCmd(serviceId); + } + + @Override + public ListTasksCmd listTasksCmd() { + return getDelegate().listTasksCmd(); + } + + @Override + public LogSwarmObjectCmd logServiceCmd(String serviceId) { + return getDelegate().logServiceCmd(serviceId); + } + + @Override + public LogSwarmObjectCmd logTaskCmd(String taskId) { + return getDelegate().logTaskCmd(taskId); + } + + @Override + public PruneCmd pruneCmd(PruneType pruneType) { + return getDelegate().pruneCmd(pruneType); + } +} diff --git a/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java b/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java new file mode 100644 index 0000000..a9505d1 --- /dev/null +++ b/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java @@ -0,0 +1,157 @@ +// Copyright 2011 Peter Darton +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package io.jenkins.dockerjavaapi.client; + +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.exceptions.base.MockitoException; + +import com.github.dockerjava.api.DockerClient; + +/** + * Ensures that every method in DelegatingDockerClient delegates to the matching + * method in the instance it delegates to. Uses reflection/introspection to + * ensure that this class doesn't need updating if new methods are added. + */ +@RunWith(Parameterized.class) +public class DelegatingDockerClientTest { + + /** + * Defines the set of data that all test methods (in this test class) will be + * run with - in this case, it's all the methods of {@link DockerClient}. + *

+ * Each element in the returned {@link Iterable} is an Object[] whose contents + * matches the arguments taken by this class's constructor. + *

+ * The annotation name = "{0}" says that the name of each set of + * data should be first element of the array. + * + * @return {@link Iterable} of [ {@link String}, {@link Method} ]. + */ + @Parameterized.Parameters(name = "{0}") + public static Iterable data() { + final List data = new ArrayList<>(); + final Method[] declaredMethods = DockerClient.class.getDeclaredMethods(); + for (Method m : declaredMethods) { + final StringBuilder testCaseName = new StringBuilder(m.getName()); + testCaseName.append('('); + for (Class t : m.getParameterTypes()) { + final String tName = t.getSimpleName(); + if (testCaseName.charAt(testCaseName.length() - 1) != '(') { + testCaseName.append(','); + } + testCaseName.append(tName); + } + testCaseName.append(')'); + final Object[] testCase = new Object[] { testCaseName.toString(), m }; + data.add(testCase); + } + data.sort(new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + final String n1 = (String) o1[0]; + final String n2 = (String) o2[0]; + return n1.compareTo(n2); + } + }); + return data; + } + + private final String dockerClientMethodName; + private final Method dockerClientMethod; + + public DelegatingDockerClientTest(String methodName, Method dockerClientMethod) { + this.dockerClientMethodName = methodName; + this.dockerClientMethod = dockerClientMethod; + } + + @Test + public void methodIsDelegatedCorrectly() throws Exception { + // Given + final Parameter[] methodParameters = dockerClientMethod.getParameters(); + final Object[] mockParameters = createFakeArgumentValues(methodParameters); + final Class methodReturnType = dockerClientMethod.getReturnType(); + final Object mockReturnValue = methodReturnType.equals(Void.TYPE) ? null + : createFakeObject(methodReturnType, dockerClientMethodName + "ReturnedValue"); + final DockerClient mockDelegate = mock(DockerClient.class, "mockDelegate"); + if (mockReturnValue != null) { + when(dockerClientMethod.invoke(mockDelegate, mockParameters)).thenReturn(mockReturnValue); + } + final DelegatingDockerClient instanceUnderTest = new DelegatingDockerClient(mockDelegate); + + // When + final Object actualReturnValue = dockerClientMethod.invoke(instanceUnderTest, mockParameters); + + // Then + if (mockReturnValue != null) { + assertThat("Returned value is what delegate returned", actualReturnValue, sameInstance(mockReturnValue)); + } + dockerClientMethod.invoke(verify(mockDelegate, times(1)), mockParameters); + verifyNoMoreInteractions(mockDelegate); + } + + private static Object[] createFakeArgumentValues(final Parameter[] methodParameters) throws Exception { + final Object[] mockParameters = new Object[methodParameters.length]; + for (int i = 0; i < methodParameters.length; i++) { + final Class paramType = methodParameters[i].getType(); + final String paramName = "arg" + i; + mockParameters[i] = createFakeObject(paramType, paramName); + } + return mockParameters; + } + + private static Object createFakeObject(final Class paramType, final String paramName) throws Exception { + if (paramType.isEnum()) { + final Object[] values = (Object[]) paramType.getMethod("values").invoke(null); + return values[values.length / 2]; // pick a value in the middle + } + try { + return mock(paramType, paramName); + } catch (MockitoException ex) { + // Many arguments are of final classes that mockito can't mock, causing this + // exception. + // In such cases we just have to hope there's a constructor we + // can access and make a real instance instead of a mock. + // MAINTENANCE NOTE: + // So far we've gotten away with only looking for a default constructor. + // If, in future, this isn't enough then we could try picking another + // constructor and trying to fake any arguments it needs. + final Constructor defaultConstructor = paramType.getConstructor(); + return defaultConstructor.newInstance(); + } + } +} From a144fd589cae8daa1cdf3655fd041e7f1bc827f1 Mon Sep 17 00:00:00 2001 From: Peter Darton Date: Mon, 15 Mar 2021 00:38:56 +0000 Subject: [PATCH 2/3] Added intercepter methods to allow pre/post hooks. --- .../client/DelegatingDockerClient.java | 172 ++++++++++-------- .../client/DelegatingDockerClientTest.java | 44 ++++- 2 files changed, 138 insertions(+), 78 deletions(-) diff --git a/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java b/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java index 4eb5aa3..4168262 100644 --- a/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java +++ b/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java @@ -43,7 +43,16 @@ * Simple delegate class for the {@link DockerClient} interface. *

* This makes it easy for other classes to override specific methods without - * having to implement all of them. + * having to implement all of them. Every method here: + *

    + *
  • calls {@link #getDelegate()},
  • + *
  • calls the matching method on the delegate class,
  • + *
  • for methods that return void, calls {@link #interceptVoid()} and then + * returns,
  • + *
  • for other methods, calls {@link #interceptAnswer(Object)}, passing in the + * delegate method's answer and returns whatever + * {@link #interceptAnswer(Object)} returned.
  • + *
*

* If you are writing a Jenkins plugin that needs a class to implement/wrap * {@link DockerClient}, you'd be best advised to extend this one, otherwise @@ -75,363 +84,382 @@ protected DockerClient getDelegate() { return delegate; } + /** + * Called just before the result is returned. Allows a subclass to act just + * before the method returns and/or to alter the result. + * + * @param originalAnswer The result from the delegate. + * @return The result to be returned instead. + */ + protected T interceptAnswer(T originalAnswer) { + return originalAnswer; + } + + /** + * Called just before the method returns void. Allows a subclass to act just + * before the method returns. + */ + protected void interceptVoid() { + } + @Override public AttachContainerCmd attachContainerCmd(String arg0) { - return getDelegate().attachContainerCmd(arg0); + return interceptAnswer(getDelegate().attachContainerCmd(arg0)); } @Override public AuthCmd authCmd() { - return getDelegate().authCmd(); + return interceptAnswer(getDelegate().authCmd()); } @Override public AuthConfig authConfig() throws DockerException { - return getDelegate().authConfig(); + return interceptAnswer(getDelegate().authConfig()); } @Override public BuildImageCmd buildImageCmd() { - return getDelegate().buildImageCmd(); + return interceptAnswer(getDelegate().buildImageCmd()); } @Override public BuildImageCmd buildImageCmd(File arg0) { - return getDelegate().buildImageCmd(arg0); + return interceptAnswer(getDelegate().buildImageCmd(arg0)); } @Override public BuildImageCmd buildImageCmd(InputStream arg0) { - return getDelegate().buildImageCmd(arg0); + return interceptAnswer(getDelegate().buildImageCmd(arg0)); } @Override public void close() throws IOException { getDelegate().close(); + interceptVoid(); } @Override public CommitCmd commitCmd(String arg0) { - return getDelegate().commitCmd(arg0); + return interceptAnswer(getDelegate().commitCmd(arg0)); } @Override public ConnectToNetworkCmd connectToNetworkCmd() { - return getDelegate().connectToNetworkCmd(); + return interceptAnswer(getDelegate().connectToNetworkCmd()); } @Override public ContainerDiffCmd containerDiffCmd(String arg0) { - return getDelegate().containerDiffCmd(arg0); + return interceptAnswer(getDelegate().containerDiffCmd(arg0)); } @Override public CopyArchiveFromContainerCmd copyArchiveFromContainerCmd(String arg0, String arg1) { - return getDelegate().copyArchiveFromContainerCmd(arg0, arg1); + return interceptAnswer(getDelegate().copyArchiveFromContainerCmd(arg0, arg1)); } @Override public CopyArchiveToContainerCmd copyArchiveToContainerCmd(String arg0) { - return getDelegate().copyArchiveToContainerCmd(arg0); + return interceptAnswer(getDelegate().copyArchiveToContainerCmd(arg0)); } @Override public CopyFileFromContainerCmd copyFileFromContainerCmd(String arg0, String arg1) { - return getDelegate().copyFileFromContainerCmd(arg0, arg1); + return interceptAnswer(getDelegate().copyFileFromContainerCmd(arg0, arg1)); } @Override public CreateContainerCmd createContainerCmd(String arg0) { - return getDelegate().createContainerCmd(arg0); + return interceptAnswer(getDelegate().createContainerCmd(arg0)); } @Override public CreateImageCmd createImageCmd(String arg0, InputStream arg1) { - return getDelegate().createImageCmd(arg0, arg1); + return interceptAnswer(getDelegate().createImageCmd(arg0, arg1)); } @Override public CreateNetworkCmd createNetworkCmd() { - return getDelegate().createNetworkCmd(); + return interceptAnswer(getDelegate().createNetworkCmd()); } @Override public CreateVolumeCmd createVolumeCmd() { - return getDelegate().createVolumeCmd(); + return interceptAnswer(getDelegate().createVolumeCmd()); } @Override public DisconnectFromNetworkCmd disconnectFromNetworkCmd() { - return getDelegate().disconnectFromNetworkCmd(); + return interceptAnswer(getDelegate().disconnectFromNetworkCmd()); } @Override public EventsCmd eventsCmd() { - return getDelegate().eventsCmd(); + return interceptAnswer(getDelegate().eventsCmd()); } @Override public ExecCreateCmd execCreateCmd(String arg0) { - return getDelegate().execCreateCmd(arg0); + return interceptAnswer(getDelegate().execCreateCmd(arg0)); } @Override public ExecStartCmd execStartCmd(String arg0) { - return getDelegate().execStartCmd(arg0); + return interceptAnswer(getDelegate().execStartCmd(arg0)); } @Override public InfoCmd infoCmd() { - return getDelegate().infoCmd(); + return interceptAnswer(getDelegate().infoCmd()); } @Override public InspectContainerCmd inspectContainerCmd(String arg0) { - return getDelegate().inspectContainerCmd(arg0); + return interceptAnswer(getDelegate().inspectContainerCmd(arg0)); } @Override public InspectExecCmd inspectExecCmd(String arg0) { - return getDelegate().inspectExecCmd(arg0); + return interceptAnswer(getDelegate().inspectExecCmd(arg0)); } @Override public InspectImageCmd inspectImageCmd(String arg0) { - return getDelegate().inspectImageCmd(arg0); + return interceptAnswer(getDelegate().inspectImageCmd(arg0)); } @Override public InspectNetworkCmd inspectNetworkCmd() { - return getDelegate().inspectNetworkCmd(); + return interceptAnswer(getDelegate().inspectNetworkCmd()); } @Override public InspectVolumeCmd inspectVolumeCmd(String arg0) { - return getDelegate().inspectVolumeCmd(arg0); + return interceptAnswer(getDelegate().inspectVolumeCmd(arg0)); } @Override public KillContainerCmd killContainerCmd(String arg0) { - return getDelegate().killContainerCmd(arg0); + return interceptAnswer(getDelegate().killContainerCmd(arg0)); } @Override public ListContainersCmd listContainersCmd() { - return getDelegate().listContainersCmd(); + return interceptAnswer(getDelegate().listContainersCmd()); } @Override public ListImagesCmd listImagesCmd() { - return getDelegate().listImagesCmd(); + return interceptAnswer(getDelegate().listImagesCmd()); } @Override public ListNetworksCmd listNetworksCmd() { - return getDelegate().listNetworksCmd(); + return interceptAnswer(getDelegate().listNetworksCmd()); } @Override public ListVolumesCmd listVolumesCmd() { - return getDelegate().listVolumesCmd(); + return interceptAnswer(getDelegate().listVolumesCmd()); } @Override public LoadImageCmd loadImageCmd(InputStream arg0) { - return getDelegate().loadImageCmd(arg0); + return interceptAnswer(getDelegate().loadImageCmd(arg0)); } @Override public LogContainerCmd logContainerCmd(String arg0) { - return getDelegate().logContainerCmd(arg0); + return interceptAnswer(getDelegate().logContainerCmd(arg0)); } @Override public PauseContainerCmd pauseContainerCmd(String arg0) { - return getDelegate().pauseContainerCmd(arg0); + return interceptAnswer(getDelegate().pauseContainerCmd(arg0)); } @Override public PingCmd pingCmd() { - return getDelegate().pingCmd(); + return interceptAnswer(getDelegate().pingCmd()); } @Override public PullImageCmd pullImageCmd(String arg0) { - return getDelegate().pullImageCmd(arg0); + return interceptAnswer(getDelegate().pullImageCmd(arg0)); } @Override public PushImageCmd pushImageCmd(String arg0) { - return getDelegate().pushImageCmd(arg0); + return interceptAnswer(getDelegate().pushImageCmd(arg0)); } @Override public PushImageCmd pushImageCmd(Identifier arg0) { - return getDelegate().pushImageCmd(arg0); + return interceptAnswer(getDelegate().pushImageCmd(arg0)); } @Override public RemoveContainerCmd removeContainerCmd(String arg0) { - return getDelegate().removeContainerCmd(arg0); + return interceptAnswer(getDelegate().removeContainerCmd(arg0)); } @Override public RemoveImageCmd removeImageCmd(String arg0) { - return getDelegate().removeImageCmd(arg0); + return interceptAnswer(getDelegate().removeImageCmd(arg0)); } @Override public RemoveNetworkCmd removeNetworkCmd(String arg0) { - return getDelegate().removeNetworkCmd(arg0); + return interceptAnswer(getDelegate().removeNetworkCmd(arg0)); } @Override public RemoveVolumeCmd removeVolumeCmd(String arg0) { - return getDelegate().removeVolumeCmd(arg0); + return interceptAnswer(getDelegate().removeVolumeCmd(arg0)); } @Override public RenameContainerCmd renameContainerCmd(String arg0) { - return getDelegate().renameContainerCmd(arg0); + return interceptAnswer(getDelegate().renameContainerCmd(arg0)); } @Override public RestartContainerCmd restartContainerCmd(String arg0) { - return getDelegate().restartContainerCmd(arg0); + return interceptAnswer(getDelegate().restartContainerCmd(arg0)); } @Override public SaveImageCmd saveImageCmd(String arg0) { - return getDelegate().saveImageCmd(arg0); + return interceptAnswer(getDelegate().saveImageCmd(arg0)); } @Override public SearchImagesCmd searchImagesCmd(String arg0) { - return getDelegate().searchImagesCmd(arg0); + return interceptAnswer(getDelegate().searchImagesCmd(arg0)); } @Override public StartContainerCmd startContainerCmd(String arg0) { - return getDelegate().startContainerCmd(arg0); + return interceptAnswer(getDelegate().startContainerCmd(arg0)); } @Override public StatsCmd statsCmd(String arg0) { - return getDelegate().statsCmd(arg0); + return interceptAnswer(getDelegate().statsCmd(arg0)); } @Override public StopContainerCmd stopContainerCmd(String arg0) { - return getDelegate().stopContainerCmd(arg0); + return interceptAnswer(getDelegate().stopContainerCmd(arg0)); } @Override public TagImageCmd tagImageCmd(String arg0, String arg1, String arg2) { - return getDelegate().tagImageCmd(arg0, arg1, arg2); + return interceptAnswer(getDelegate().tagImageCmd(arg0, arg1, arg2)); } @Override public TopContainerCmd topContainerCmd(String arg0) { - return getDelegate().topContainerCmd(arg0); + return interceptAnswer(getDelegate().topContainerCmd(arg0)); } @Override public UnpauseContainerCmd unpauseContainerCmd(String arg0) { - return getDelegate().unpauseContainerCmd(arg0); + return interceptAnswer(getDelegate().unpauseContainerCmd(arg0)); } @Override public UpdateContainerCmd updateContainerCmd(String arg0) { - return getDelegate().updateContainerCmd(arg0); + return interceptAnswer(getDelegate().updateContainerCmd(arg0)); } @Override public VersionCmd versionCmd() { - return getDelegate().versionCmd(); + return interceptAnswer(getDelegate().versionCmd()); } @Override public WaitContainerCmd waitContainerCmd(String arg0) { - return getDelegate().waitContainerCmd(arg0); + return interceptAnswer(getDelegate().waitContainerCmd(arg0)); } @Override public InitializeSwarmCmd initializeSwarmCmd(SwarmSpec swarmSpec) { - return getDelegate().initializeSwarmCmd(swarmSpec); + return interceptAnswer(getDelegate().initializeSwarmCmd(swarmSpec)); } @Override public InspectSwarmCmd inspectSwarmCmd() { - return getDelegate().inspectSwarmCmd(); + return interceptAnswer(getDelegate().inspectSwarmCmd()); } @Override public JoinSwarmCmd joinSwarmCmd() { - return getDelegate().joinSwarmCmd(); + return interceptAnswer(getDelegate().joinSwarmCmd()); } @Override public LeaveSwarmCmd leaveSwarmCmd() { - return getDelegate().leaveSwarmCmd(); + return interceptAnswer(getDelegate().leaveSwarmCmd()); } @Override public UpdateSwarmCmd updateSwarmCmd(SwarmSpec swarmSpec) { - return getDelegate().updateSwarmCmd(swarmSpec); + return interceptAnswer(getDelegate().updateSwarmCmd(swarmSpec)); } @Override public UpdateSwarmNodeCmd updateSwarmNodeCmd() { - return getDelegate().updateSwarmNodeCmd(); + return interceptAnswer(getDelegate().updateSwarmNodeCmd()); } @Override public ListSwarmNodesCmd listSwarmNodesCmd() { - return getDelegate().listSwarmNodesCmd(); + return interceptAnswer(getDelegate().listSwarmNodesCmd()); } @Override public ListServicesCmd listServicesCmd() { - return getDelegate().listServicesCmd(); + return interceptAnswer(getDelegate().listServicesCmd()); } @Override public CreateServiceCmd createServiceCmd(ServiceSpec serviceSpec) { - return getDelegate().createServiceCmd(serviceSpec); + return interceptAnswer(getDelegate().createServiceCmd(serviceSpec)); } @Override public InspectServiceCmd inspectServiceCmd(String serviceId) { - return getDelegate().inspectServiceCmd(serviceId); + return interceptAnswer(getDelegate().inspectServiceCmd(serviceId)); } @Override public UpdateServiceCmd updateServiceCmd(String serviceId, ServiceSpec serviceSpec) { - return getDelegate().updateServiceCmd(serviceId, serviceSpec); + return interceptAnswer(getDelegate().updateServiceCmd(serviceId, serviceSpec)); } @Override public RemoveServiceCmd removeServiceCmd(String serviceId) { - return getDelegate().removeServiceCmd(serviceId); + return interceptAnswer(getDelegate().removeServiceCmd(serviceId)); } @Override public ListTasksCmd listTasksCmd() { - return getDelegate().listTasksCmd(); + return interceptAnswer(getDelegate().listTasksCmd()); } @Override public LogSwarmObjectCmd logServiceCmd(String serviceId) { - return getDelegate().logServiceCmd(serviceId); + return interceptAnswer(getDelegate().logServiceCmd(serviceId)); } @Override public LogSwarmObjectCmd logTaskCmd(String taskId) { - return getDelegate().logTaskCmd(taskId); + return interceptAnswer(getDelegate().logTaskCmd(taskId)); } @Override public PruneCmd pruneCmd(PruneType pruneType) { - return getDelegate().pruneCmd(pruneType); + return interceptAnswer(getDelegate().pruneCmd(pruneType)); } } diff --git a/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java b/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java index a9505d1..1746ac9 100644 --- a/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java +++ b/src/test/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClientTest.java @@ -22,10 +22,9 @@ import static org.hamcrest.CoreMatchers.sameInstance; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.lang.reflect.Constructor; @@ -38,6 +37,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.mockito.InOrder; import org.mockito.exceptions.base.MockitoException; import com.github.dockerjava.api.DockerClient; @@ -57,7 +57,7 @@ public class DelegatingDockerClientTest { * Each element in the returned {@link Iterable} is an Object[] whose contents * matches the arguments taken by this class's constructor. *

- * The annotation name = "{0}" says that the name of each set of + * Note: The annotation name = "{0}" says that the name of each set of * data should be first element of the array. * * @return {@link Iterable} of [ {@link String}, {@link Method} ]. @@ -99,6 +99,32 @@ public DelegatingDockerClientTest(String methodName, Method dockerClientMethod) this.dockerClientMethod = dockerClientMethod; } + private interface HookPoints { + void interceptAnswerCalled(Object originalAnswer); + + void interceptVoidCalled(); + } + + private static class DelegatingDockerClientUnderTest extends DelegatingDockerClient { + final HookPoints hooks = mock(HookPoints.class); + + protected DelegatingDockerClientUnderTest(DockerClient delegate) { + super(delegate); + } + + @Override + protected T interceptAnswer(T originalAnswer) { + hooks.interceptAnswerCalled(originalAnswer); + return super.interceptAnswer(originalAnswer); + } + + @Override + protected void interceptVoid() { + hooks.interceptVoidCalled(); + super.interceptVoid(); + } + } + @Test public void methodIsDelegatedCorrectly() throws Exception { // Given @@ -111,7 +137,7 @@ public void methodIsDelegatedCorrectly() throws Exception { if (mockReturnValue != null) { when(dockerClientMethod.invoke(mockDelegate, mockParameters)).thenReturn(mockReturnValue); } - final DelegatingDockerClient instanceUnderTest = new DelegatingDockerClient(mockDelegate); + final DelegatingDockerClientUnderTest instanceUnderTest = new DelegatingDockerClientUnderTest(mockDelegate); // When final Object actualReturnValue = dockerClientMethod.invoke(instanceUnderTest, mockParameters); @@ -120,8 +146,14 @@ public void methodIsDelegatedCorrectly() throws Exception { if (mockReturnValue != null) { assertThat("Returned value is what delegate returned", actualReturnValue, sameInstance(mockReturnValue)); } - dockerClientMethod.invoke(verify(mockDelegate, times(1)), mockParameters); - verifyNoMoreInteractions(mockDelegate); + final InOrder inOrder = inOrder(mockDelegate, instanceUnderTest.hooks); + dockerClientMethod.invoke(inOrder.verify(mockDelegate, times(1)), mockParameters); + if (mockReturnValue != null) { + inOrder.verify(instanceUnderTest.hooks).interceptAnswerCalled(actualReturnValue); + } else { + inOrder.verify(instanceUnderTest.hooks).interceptVoidCalled(); + } + inOrder.verifyNoMoreInteractions(); } private static Object[] createFakeArgumentValues(final Parameter[] methodParameters) throws Exception { From 1295d3de79069cb834977302d8257e23bdbe2a29 Mon Sep 17 00:00:00 2001 From: Peter Darton Date: Fri, 23 Apr 2021 17:36:19 +0100 Subject: [PATCH 3/3] Improved javadocs. --- .../client/DelegatingDockerClient.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java b/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java index 4168262..d8ee197 100644 --- a/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java +++ b/src/main/java/io/jenkins/dockerjavaapi/client/DelegatingDockerClient.java @@ -53,10 +53,11 @@ * delegate method's answer and returns whatever * {@link #interceptAnswer(Object)} returned. * - *

+ *

* If you are writing a Jenkins plugin that needs a class to implement/wrap * {@link DockerClient}, you'd be best advised to extend this one, otherwise - * your code could fail whenever the version of this plugin changes. + * your code could fail whenever the version of this plugin changes and the + * {@link DockerClient} gains additional methods. */ @SuppressWarnings("deprecation") public class DelegatingDockerClient implements DockerClient { @@ -75,7 +76,8 @@ public DelegatingDockerClient(@Nonnull DockerClient delegate) { /** * Obtains the underlying {@link DockerClient} interface. Subclasses can - * override this if they need to hook into every call. + * override this if they need to hook into every call before anything else + * happens. * * @return the {@link DockerClient} to be delegated to. */ @@ -85,10 +87,18 @@ protected DockerClient getDelegate() { } /** - * Called just before the result is returned. Allows a subclass to act just - * before the method returns and/or to alter the result. + * Called just before the result is returned. Subclasses can override this if + * they need to hook into every call just before the method returns and/or to + * alter the result. + *

+ * Note: If a subclass only wishes to act upon certain specific + * {@link DockerClient} calls then it may be clearer to override those specific + * methods instead. This hook is intended for use by subclasses that need to act + * upon "all methods" or need to act on methods that were not part of the + * {@link DockerClient} API at the time they were implemented. * * @param originalAnswer The result from the delegate. + * @param The type of the originalAnswer. * @return The result to be returned instead. */ protected T interceptAnswer(T originalAnswer) { @@ -98,6 +108,12 @@ protected T interceptAnswer(T originalAnswer) { /** * Called just before the method returns void. Allows a subclass to act just * before the method returns. + *

+ * Note: If a subclass only wishes to act upon certain specific + * {@link DockerClient} calls then it may be clearer to override those specific + * methods instead. This hook is intended for use by subclasses that need to act + * upon "all methods" or need to act on methods that were not part of the + * {@link DockerClient} API at the time they were implemented. */ protected void interceptVoid() { }