diff --git a/README.md b/README.md index 12a43ffee..e03eceec6 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,51 @@ You can use Maven and add this dependency to your project's POM: Alternatively, you can download the [release on GitHub](https://github.com/Adyen/adyen-java-api-library/releases). +### Modular artifacts + +The default artifact contains every API, model, webhook, and Terminal API class. Applications that +only use part of the library can instead depend on the `core` classifier and one or more API-family +classifiers: + +| Classifier | Contents | +|---|---| +| `core` | Client, HTTP transport, shared request and error models, serializers, and utilities | +| `checkout` | Checkout API and models | +| `platforms` | Balance Platform, Balance Control, Capital, Legal Entity Management, Management, Session Authentication, and Transfers APIs and models | +| `payments` | Bin Lookup, Data Protection, Disputes, Open Banking, Payments, Payouts, Recurring, and Stored Value APIs and models | +| `terminal` | Terminal API, Cloud Device, Mobile, Payments App, and Terminal Fleet Management classes and models | +| `classic-platforms` | Classic Platforms APIs and models | +| `webhooks` | Webhook models and HMAC validation | +| `webhook-handlers` | Convenience webhook handlers; also requires `webhooks`, `payments`, and `terminal` | + +Every API-family classifier requires `core`. For example, a Maven application that only uses the +Checkout API can use: + +```xml + + com.adyen + adyen-java-api-library + 43.0.0 + core + + + com.adyen + adyen-java-api-library + 43.0.0 + checkout + +``` + +The equivalent Gradle dependencies are: + +```groovy +implementation("com.adyen:adyen-java-api-library:43.0.0:core") +implementation("com.adyen:adyen-java-api-library:43.0.0:checkout") +``` + +The unclassified artifact remains available and unchanged for applications that prefer a single +dependency or need the complete library. + ## Using the library ### General use with API key diff --git a/pom.xml b/pom.xml index 69284fdfd..9e88d2c99 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,207 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + core-jar + package + + jar + + + core + + com/adyen/*.class + com/adyen/constants/** + com/adyen/enums/** + com/adyen/httpclient/** + com/adyen/model/*.class + com/adyen/model/checkout/JSON*.class + com/adyen/serializer/** + com/adyen/service/exception/** + com/adyen/service/resource/*.class + com/adyen/util/*.class + + + com/adyen/httpclient/TerminalLocalAPIHostnameVerifier.class + com/adyen/serializer/SaleToAcquirerDataSerializer.class + com/adyen/util/HMACValidator.class + + + + + checkout-jar + package + + jar + + + checkout + + com/adyen/model/checkout/** + com/adyen/model/checkoututility/** + com/adyen/service/checkout/** + + + com/adyen/model/checkout/JSON*.class + + + + + platforms-jar + package + + jar + + + platforms + + com/adyen/model/balancecontrol/** + com/adyen/model/balanceplatform/** + com/adyen/model/capital/** + com/adyen/model/legalentitymanagement/** + com/adyen/model/management/** + com/adyen/model/sessionauthentication/** + com/adyen/model/transfers/** + com/adyen/service/BalanceControlApi*.class + com/adyen/service/balancecontrol/** + com/adyen/service/balanceplatform/** + com/adyen/service/capital/** + com/adyen/service/legalentitymanagement/** + com/adyen/service/management/** + com/adyen/service/sessionauthentication/** + com/adyen/service/transfers/** + + + + + payments-jar + package + + jar + + + payments + + com/adyen/model/additionalData/** + com/adyen/model/binlookup/** + com/adyen/model/dataprotection/** + com/adyen/model/disputes/** + com/adyen/model/openbanking/** + com/adyen/model/payment/** + com/adyen/model/payout/** + com/adyen/model/recurring/** + com/adyen/model/storedvalue/** + com/adyen/service/BinLookupApi*.class + com/adyen/service/DataProtectionApi*.class + com/adyen/service/DisputesApi*.class + com/adyen/service/PaymentApi*.class + com/adyen/service/RecurringApi*.class + com/adyen/service/StoredValueApi*.class + com/adyen/service/binlookup/** + com/adyen/service/dataprotection/** + com/adyen/service/disputes/** + com/adyen/service/openbanking/** + com/adyen/service/payment/** + com/adyen/service/payout/** + com/adyen/service/recurring/** + com/adyen/service/storedvalue/** + + + + + terminal-jar + package + + jar + + + terminal + + com/adyen/builders/** + com/adyen/httpclient/TerminalLocalAPIHostnameVerifier.class + com/adyen/model/applicationinfo/** + com/adyen/model/clouddevice/** + com/adyen/model/nexo/** + com/adyen/model/paymentsapp/** + com/adyen/model/posmobile/** + com/adyen/model/posterminalmanagement/** + com/adyen/model/tapi/** + com/adyen/model/terminal/** + com/adyen/security/** + com/adyen/serializer/SaleToAcquirerDataSerializer.class + com/adyen/service/PaymentsAppApi*.class + com/adyen/service/PosMobileApi*.class + com/adyen/service/PosPayment*.class + com/adyen/service/PosTerminalManagementApi*.class + com/adyen/service/TerminalCloudAPI*.class + com/adyen/service/TerminalLocalAPI*.class + com/adyen/service/clouddevice/** + com/adyen/service/paymentsapp/** + com/adyen/service/posmobile/** + com/adyen/service/resource/terminal/** + com/adyen/terminal/** + com/adyen/util/tapi/** + + + + + classic-platforms-jar + package + + jar + + + classic-platforms + + com/adyen/model/marketpayaccount/** + com/adyen/model/marketpayconfiguration/** + com/adyen/model/marketpayfund/** + com/adyen/model/marketpayhop/** + com/adyen/service/classicplatforms/** + + + + + webhooks-jar + package + + jar + + + webhooks + + com/adyen/model/acswebhooks/** + com/adyen/model/balancewebhooks/** + com/adyen/model/configurationwebhooks/** + com/adyen/model/disputewebhooks/** + com/adyen/model/managementwebhooks/** + com/adyen/model/marketpaywebhooks/** + com/adyen/model/negativebalancewarningwebhooks/** + com/adyen/model/notification/** + com/adyen/model/relayedauthorizationwebhooks/** + com/adyen/model/reportwebhooks/** + com/adyen/model/tokenizationwebhooks/** + com/adyen/model/transactionwebhooks/** + com/adyen/model/transferwebhooks/** + com/adyen/util/HMACValidator.class + + + + + webhook-handlers-jar + package + + jar + + + webhook-handlers + + com/adyen/notification/** + + + + org.jacoco diff --git a/src/test/java/com/adyen/ModularArtifactsTest.java b/src/test/java/com/adyen/ModularArtifactsTest.java new file mode 100644 index 000000000..4d16e170f --- /dev/null +++ b/src/test/java/com/adyen/ModularArtifactsTest.java @@ -0,0 +1,145 @@ +package com.adyen; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.xml.parsers.DocumentBuilderFactory; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +class ModularArtifactsTest { + + @Test + void modularArtifactsPartitionAllCompiledClasses() throws Exception { + Map jarPatterns = readJarPatterns(); + assertEquals( + Set.of( + "core", + "checkout", + "platforms", + "payments", + "terminal", + "classic-platforms", + "webhooks", + "webhook-handlers"), + jarPatterns.keySet()); + + Map> ownersByClass = new HashMap<>(); + try (Stream compiledClasses = Files.walk(Path.of("target/classes/com/adyen"))) { + compiledClasses + .filter(path -> path.toString().endsWith(".class")) + .map(path -> Path.of("target/classes").relativize(path).toString().replace('\\', '/')) + .forEach( + className -> + ownersByClass.put( + className, + jarPatterns.entrySet().stream() + .filter(entry -> entry.getValue().matches(className)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()))); + } + + List unassigned = + ownersByClass.entrySet().stream() + .filter(entry -> entry.getValue().isEmpty()) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + List duplicated = + ownersByClass.entrySet().stream() + .filter(entry -> entry.getValue().size() > 1) + .map(entry -> entry.getKey() + " -> " + entry.getValue()) + .collect(Collectors.toList()); + + assertTrue(unassigned.isEmpty(), "Classes missing from modular artifacts: " + unassigned); + assertTrue(duplicated.isEmpty(), "Classes duplicated across modular artifacts: " + duplicated); + } + + private static Map readJarPatterns() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + NodeList plugins = + factory + .newDocumentBuilder() + .parse(Path.of("pom.xml").toFile()) + .getElementsByTagName("plugin"); + Map patternsByClassifier = new HashMap<>(); + + for (int pluginIndex = 0; pluginIndex < plugins.getLength(); pluginIndex++) { + Element plugin = (Element) plugins.item(pluginIndex); + if (!"maven-jar-plugin".equals(text(plugin, "artifactId"))) { + continue; + } + NodeList executions = plugin.getElementsByTagName("execution"); + for (int executionIndex = 0; executionIndex < executions.getLength(); executionIndex++) { + Element execution = (Element) executions.item(executionIndex); + NodeList classifiers = execution.getElementsByTagName("classifier"); + if (classifiers.getLength() == 0) { + continue; + } + String classifier = classifiers.item(0).getTextContent().trim(); + patternsByClassifier.put( + classifier, + new JarPatterns(patterns(execution, "include"), patterns(execution, "exclude"))); + } + } + return patternsByClassifier; + } + + private static String text(Element element, String tagName) { + return element.getElementsByTagName(tagName).item(0).getTextContent().trim(); + } + + private static List patterns(Element execution, String tagName) { + NodeList nodes = execution.getElementsByTagName(tagName); + List patterns = new ArrayList<>(); + for (int index = 0; index < nodes.getLength(); index++) { + patterns.add(Pattern.compile(toRegex(nodes.item(index).getTextContent().trim()))); + } + return patterns; + } + + private static String toRegex(String glob) { + StringBuilder regex = new StringBuilder("^"); + for (int index = 0; index < glob.length(); index++) { + char character = glob.charAt(index); + if (character == '*') { + boolean doubleWildcard = index + 1 < glob.length() && glob.charAt(index + 1) == '*'; + regex.append(doubleWildcard ? ".*" : "[^/]*"); + if (doubleWildcard) { + index++; + } + } else if ("\\.[]{}()+-^$|".indexOf(character) >= 0) { + regex.append('\\').append(character); + } else { + regex.append(character); + } + } + return regex.append('$').toString(); + } + + private static final class JarPatterns { + private final List includes; + private final List excludes; + + private JarPatterns(List includes, List excludes) { + this.includes = includes; + this.excludes = excludes; + } + + private boolean matches(String className) { + return includes.stream().anyMatch(pattern -> pattern.matcher(className).matches()) + && excludes.stream().noneMatch(pattern -> pattern.matcher(className).matches()); + } + } +}