Skip to content

Commit

Permalink
Merge pull request #68 from wiremock/plugin-api
Browse files Browse the repository at this point in the history
Introduce Plugin API and refactor extension management
  • Loading branch information
oleg-nenashev authored Aug 24, 2023
2 parents 734ccc2 + 3403f76 commit 2809071
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class WireMockContainer extends GenericContainer<WireMockContainer> {
private final StringBuilder wireMockArgs;
private final Map<String, Stub> mappingStubs = new HashMap<>();
private final Map<String, MountableFile> mappingFiles = new HashMap<>();
private final Map<String, Extension> extensions = new HashMap<>();
private final Map<String, WireMockPlugin> plugins = new HashMap<>();
private boolean isBannerDisabled = true;

/**
Expand Down Expand Up @@ -268,28 +268,39 @@ public WireMockContainer withFileFromResource(Class<?> resource, String filename
}

/**
* Add extension that will be loaded from the specified JAR file.
* @param id Unique ID of the extension, for logging purposes
* Add extension that will be loaded from the specified JAR files.
* In the internal engine, it will be handled as a single plugin.
* @param classNames Class names of the extension to be included
* @param jars JARs to be included into the container
* @return this instance
*/
public WireMockContainer withExtension(String id, Collection<String> classNames, Collection<File> jars) {
final Extension extension = new Extension(id);
extension.extensionClassNames.addAll(classNames);
extension.jars.addAll(jars);
extensions.put(id, extension);
return this;
public WireMockContainer withExtensions(Collection<String> classNames, Collection<File> jars) {
return withExtensions(WireMockPlugin.guessPluginId(classNames, jars), classNames, jars);
}

/**
* Add extension that will be loaded from the specified JAR files.
* In the internal engine, it will be handled as a single plugin.
* @param id Identifier top use
* @param classNames Class names of the extension to be included
* @param jars JARs to be included into the container
* @return this instance
*/
public WireMockContainer withExtensions(String id, Collection<String> classNames, Collection<File> jars) {
final WireMockPlugin extension = new WireMockPlugin(id)
.withExtensions(classNames)
.withJars(jars);
return withPlugin(extension);
}

/**
* Add extension that will be loaded from the specified directory with JAR files.
* @param id Unique ID of the extension, for logging purposes
* In the internal engine, it will be handled as a single plugin.
* @param classNames Class names of the extension to be included
* @param jarDirectory Directory that stores all JARs
* @return this instance
*/
public WireMockContainer withExtension(String id, Collection<String> classNames, File jarDirectory) {
public WireMockContainer withExtensions(Collection<String> classNames, File jarDirectory) {
final List<File> jarsInTheDirectory;
try (Stream<Path> walk = Files.walk(jarDirectory.toPath())) {
jarsInTheDirectory = walk
Expand All @@ -301,19 +312,28 @@ public WireMockContainer withExtension(String id, Collection<String> classNames,
throw new IllegalArgumentException("Cannot list JARs in the directory " + jarDirectory, e);
}

return withExtension(id, classNames, jarsInTheDirectory);
return withExtensions(classNames, jarsInTheDirectory);
}

/**
* Add extension that will be loaded from the classpath.
* This method can be used if the extension is a part of the WireMock bundle,
* or a Jar is already added via {@link #withExtension(String, Collection, Collection)}}
* @param id Unique ID of the extension, for logging purposes
* or a Jar is already added via {@link #withExtensions(Collection, Collection)}}.
* In the internal engine, it will be handled as a single plugin.
* @param className Class name of the extension
* @return this instance
*/
public WireMockContainer withExtension(String id, String className) {
return withExtension(id, Collections.singleton(className), Collections.emptyList());
public WireMockContainer withExtension(String className) {
return withExtensions(Collections.singleton(className), Collections.emptyList());
}

private WireMockContainer withPlugin(WireMockPlugin plugin) {
String pluginId = plugin.getPluginId();
if (plugins.containsKey(pluginId)) {
throw new IllegalArgumentException("The plugin is already included: " + pluginId);
}
plugins.put(pluginId, plugin);
return this;
}

public String getBaseUrl() {
Expand Down Expand Up @@ -344,10 +364,10 @@ protected void configure() {
}

final ArrayList<String> extensionClassNames = new ArrayList<>();
for (Map.Entry<String, Extension> entry : extensions.entrySet()) {
final Extension ext = entry.getValue();
extensionClassNames.addAll(ext.extensionClassNames);
for (File jar : ext.jars) {
for (Map.Entry<String, WireMockPlugin> entry : plugins.entrySet()) {
final WireMockPlugin ext = entry.getValue();
extensionClassNames.addAll(ext.getExtensionClassNames());
for (File jar : ext.getJars()) {
withCopyToContainer(MountableFile.forHostPath(jar.toPath()), EXTENSIONS_DIR + jar.getName());
}
}
Expand All @@ -374,13 +394,5 @@ public Stub(String name, String json) {
}
}

private static final class Extension {
final String id;
final List<File> jars = new ArrayList<>();
final List<String> extensionClassNames = new ArrayList<>();

public Extension(String id) {
this.id = id;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.wiremock.integrations.testcontainers;

import org.testcontainers.shaded.com.google.common.io.Files;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Random;

/**
* Unofficial notion of a WirteMock plugin.
* WireMock at the moment operates only on the extension level,
* and here we try to introduce a concept of a plugin that may span multiple JARs and extensions.
* {@link #extensionClassNames} may be empty for WireMock 3 that supports auto-loading
*/
/*package*/ class WireMockPlugin {
private final String pluginId;
private final List<File> jars = new ArrayList<>();
private final List<String> extensionClassNames = new ArrayList<>();

public WireMockPlugin(String id) {
this.pluginId = id;
}

public String getPluginId() {
return pluginId;
}

public WireMockPlugin withJars(Collection<File> jars) {
this.jars.addAll(jars);
return this;
}

public WireMockPlugin withJar(File jar) {
return withJars(Collections.singleton(jar));
}

public WireMockPlugin withExtensions(Collection<String> extensionClassNames) {
this.extensionClassNames.addAll(extensionClassNames);
return this;
}

public WireMockPlugin withExtension(String className) {
return withExtensions(Collections.singleton(className));
}

/**
* Get JARs associated with the extension
* @return List of JARs. Might be empty if the plugin/extension is a part of the WireMock core or already in the classpath
*/
public List<File> getJars() {
return jars;
}

/**
* Get the list of extensions. Might be empty in WireMock 3
* @return List of extension class names within the plugin
*/
public List<String> getExtensionClassNames() {
return extensionClassNames;
}

public static String guessPluginId(Collection<String> classNames, Collection<File> jars) {
File jar = jars.stream().findFirst().orElse(null);
if (jar != null) {
return Files.getNameWithoutExtension(jar.getName());
}

String className = classNames.stream().findFirst().orElse(null);
if (className != null && className.length() > 1) {
return className.substring(className.lastIndexOf('.') + 1);
}

return "plugin_" + Math.random(); // Double is fun, right? :)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class WireMockContainerExtensionTest {
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.withStartupTimeout(Duration.ofSeconds(60))
.withMapping("json-body-transformer", WireMockContainerExtensionTest.class, "json-body-transformer.json")
.withExtension("JSON Body Transformer",
.withExtensions("JSON Body Transformer",
Collections.singleton("com.ninecookies.wiremock.extensions.JsonBodyTransformer"),
Collections.singleton(Paths.get("target", "test-wiremock-extension", "wiremock-extensions-0.4.1-jar-with-dependencies.jar").toFile()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ class WireMockContainerExtensionsCombinationTest {
WireMockContainer wiremockServer = new WireMockContainer(WireMockContainer.WIREMOCK_2_LATEST)
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.withMapping("json-body-transformer", WireMockContainerExtensionsCombinationTest.class, "json-body-transformer.json")
.withExtension("Webhook",
.withExtensions("Webhook",
Collections.singleton("org.wiremock.webhooks.Webhooks"),
Collections.singleton(Paths.get("target", "test-wiremock-extension", "wiremock-webhooks-extension-2.35.0.jar").toFile()))
.withExtension("JSON Body Transformer",
.withExtensions("JSON Body Transformer",
Collections.singleton("com.ninecookies.wiremock.extensions.JsonBodyTransformer"),
Collections.singleton(Paths.get("target", "test-wiremock-extension", "wiremock-extensions-0.4.1-jar-with-dependencies.jar").toFile()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class WireMockContainerExtensionsWebhookTest {
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.withCliArg("--global-response-templating")
.withMapping("webhook-callback-template", WireMockContainerExtensionsWebhookTest.class, "webhook-callback-template.json")
.withExtension("Webhook",
.withExtensions("Webhook",
Collections.singleton("org.wiremock.webhooks.Webhooks"),
Collections.singleton(Paths.get("target", "test-wiremock-extension", "wiremock-webhooks-extension-2.35.0.jar").toFile()))
.withAccessToHost(true); // Force the host access mechanism
Expand Down

0 comments on commit 2809071

Please sign in to comment.