BAEL 3320 JCommander (#7971)
* init jcommander * add model layer jcommander app * service scaffolding * init jcommander cli layer * wire up services and commands * splitter impl; validator impl; tests and cleanup * cleanup pom * integration tests * fix uuid validator example * optimise uuid regex; if-else to switch * review comments * fix builder formatting * change list assertion in fetch charges tests * missing minor edit * move to new module libraries-3 * rm unwanted files
This commit is contained in:
parent
f2d9829b91
commit
e16bd73565
|
@ -0,0 +1,9 @@
|
||||||
|
## Libraries-3
|
||||||
|
|
||||||
|
This module contains articles about various Java libraries.
|
||||||
|
These are small libraries that are relatively easy to use and do not require any separate module of their own.
|
||||||
|
|
||||||
|
The code examples related to different libraries are each in their own module.
|
||||||
|
|
||||||
|
Remember, for advanced libraries like [Jackson](/jackson) and [JUnit](/testing-modules) we already have separate modules. Please make sure to have a look at the existing modules in such cases.
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>libraries-3</artifactId>
|
||||||
|
<name>libraries-3</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>jboss-public-repository-group</id>
|
||||||
|
<name>JBoss Public Repository Group</name>
|
||||||
|
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<updatePolicy>never</updatePolicy>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<updatePolicy>daily</updatePolicy>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.beust</groupId>
|
||||||
|
<artifactId>jcommander</artifactId>
|
||||||
|
<version>${jcommander.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<jcommander.version>1.78</jcommander.version>
|
||||||
|
<lombok.version>1.18.6</lombok.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
</properties>
|
||||||
|
</project>
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.baeldung.jcommander.helloworld;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
|
||||||
|
public class HelloWorldApp {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute:
|
||||||
|
* mvn exec:java -Dexec.mainClass=com.baeldung.jcommander.helloworld.HelloWorldApp -q \
|
||||||
|
* -Dexec.args="--name JavaWorld"
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HelloWorldArgs jArgs = new HelloWorldArgs();
|
||||||
|
JCommander helloCmd = JCommander
|
||||||
|
.newBuilder()
|
||||||
|
.addObject(jArgs)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
helloCmd.parse(args);
|
||||||
|
System.out.println("Hello " + jArgs.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HelloWorldArgs {
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = "--name",
|
||||||
|
description = "User name",
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.cli.UsageBasedBilling;
|
||||||
|
|
||||||
|
public class UsageBasedBillingApp {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Entry-point: invokes the cli passing the command-line args
|
||||||
|
*
|
||||||
|
* Invoking "Submit" sub-command:
|
||||||
|
* mvn exec:java \
|
||||||
|
-Dexec.mainClass=com.baeldung.jcommander.usagebilling.UsageBasedBillingApp -q \
|
||||||
|
-Dexec.args="submit --customer cb898e7a-f2a0-46d2-9a09-531f1cee1839 --subscription subscriptionPQRMN001 --pricing-type PRE_RATED --timestamp 2019-10-03T10:58:00 --quantity 7 --price 24.56"
|
||||||
|
*
|
||||||
|
* Invoking "Fetch" sub-command:
|
||||||
|
* mvn exec:java \
|
||||||
|
-Dexec.mainClass=com.baeldung.jcommander.usagebilling.UsageBasedBillingApp -q \
|
||||||
|
-Dexec.args="fetch --customer cb898e7a-f2a0-46d2-9a09-531f1cee1839 --subscription subscriptionPQRMN001 subscriptionPQRMN002 subscriptionPQRMN003 --itemized"
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new UsageBasedBilling().run(args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.cli.splitter.ColonParameterSplitter;
|
||||||
|
import com.baeldung.jcommander.usagebilling.cli.validator.UUIDValidator;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesRequest;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse;
|
||||||
|
import com.baeldung.jcommander.usagebilling.service.FetchCurrentChargesService;
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.baeldung.jcommander.usagebilling.cli.UsageBasedBilling.*;
|
||||||
|
import static com.baeldung.jcommander.usagebilling.service.FetchCurrentChargesService.getDefault;
|
||||||
|
|
||||||
|
@Parameters(
|
||||||
|
commandNames = { FETCH_CMD },
|
||||||
|
commandDescription = "Fetch charges for a customer in the current month, can be itemized or aggregated"
|
||||||
|
)
|
||||||
|
@Getter
|
||||||
|
class FetchCurrentChargesCommand {
|
||||||
|
|
||||||
|
FetchCurrentChargesCommand() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private FetchCurrentChargesService service = getDefault();
|
||||||
|
|
||||||
|
@Parameter(names = "--help", help = true)
|
||||||
|
private boolean help;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--customer", "-C" },
|
||||||
|
description = "Id of the Customer who's using the services",
|
||||||
|
validateWith = UUIDValidator.class,
|
||||||
|
order = 1,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private String customerId;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--subscription", "-S" },
|
||||||
|
description = "Filter charges for specific subscription Ids, includes all subscriptions if no value is specified",
|
||||||
|
variableArity = true,
|
||||||
|
splitter = ColonParameterSplitter.class,
|
||||||
|
order = 2
|
||||||
|
)
|
||||||
|
private List<String> subscriptionIds;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--itemized" },
|
||||||
|
description = "Whether the response should contain breakdown by subscription, only aggregate values are returned by default",
|
||||||
|
order = 3
|
||||||
|
)
|
||||||
|
private boolean itemized;
|
||||||
|
|
||||||
|
void fetch() {
|
||||||
|
CurrentChargesRequest req = CurrentChargesRequest.builder()
|
||||||
|
.customerId(customerId)
|
||||||
|
.subscriptionIds(subscriptionIds)
|
||||||
|
.itemized(itemized)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CurrentChargesResponse response = service.fetch(req);
|
||||||
|
System.out.println(response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.cli.converter.ISO8601TimestampConverter;
|
||||||
|
import com.baeldung.jcommander.usagebilling.cli.validator.UUIDValidator;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.UsageRequest;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.UsageRequest.PricingType;
|
||||||
|
import com.baeldung.jcommander.usagebilling.service.SubmitUsageService;
|
||||||
|
import com.beust.jcommander.Parameter;
|
||||||
|
import com.beust.jcommander.Parameters;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static com.baeldung.jcommander.usagebilling.cli.UsageBasedBilling.*;
|
||||||
|
import static com.baeldung.jcommander.usagebilling.service.SubmitUsageService.getDefault;
|
||||||
|
|
||||||
|
@Parameters(
|
||||||
|
commandNames = { SUBMIT_CMD },
|
||||||
|
commandDescription = "Submit usage for a given customer and subscription, accepts one usage item"
|
||||||
|
)
|
||||||
|
@Getter
|
||||||
|
class SubmitUsageCommand {
|
||||||
|
|
||||||
|
SubmitUsageCommand() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubmitUsageService service = getDefault();
|
||||||
|
|
||||||
|
@Parameter(names = "--help", help = true)
|
||||||
|
private boolean help;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--customer", "-C" },
|
||||||
|
description = "Id of the Customer who's using the services",
|
||||||
|
validateWith = UUIDValidator.class,
|
||||||
|
order = 1,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private String customerId;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--subscription", "-S" },
|
||||||
|
description = "Id of the Subscription that was purchased",
|
||||||
|
order = 2,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private String subscriptionId;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--pricing-type", "-P" },
|
||||||
|
description = "Pricing type of the usage reported",
|
||||||
|
order = 3,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private PricingType pricingType;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--quantity" },
|
||||||
|
description = "Used quantity; reported quantity is added over the billing period",
|
||||||
|
order = 3,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--timestamp" },
|
||||||
|
description = "Timestamp of the usage event, must lie in the current billing period",
|
||||||
|
converter = ISO8601TimestampConverter.class,
|
||||||
|
order = 4,
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
private Instant timestamp;
|
||||||
|
|
||||||
|
@Parameter(
|
||||||
|
names = { "--price" },
|
||||||
|
description = "If PRE_RATED, unit price to be applied per unit of usage quantity reported",
|
||||||
|
order = 5
|
||||||
|
)
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
void submit() {
|
||||||
|
|
||||||
|
UsageRequest req = UsageRequest.builder()
|
||||||
|
.customerId(customerId)
|
||||||
|
.subscriptionId(subscriptionId)
|
||||||
|
.pricingType(pricingType)
|
||||||
|
.quantity(quantity)
|
||||||
|
.timestamp(timestamp)
|
||||||
|
.price(price)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String reqId = service.submit(req);
|
||||||
|
System.out.println("Generated Request Id for reference: " + reqId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.beust.jcommander.ParameterException;
|
||||||
|
import com.beust.jcommander.UnixStyleUsageFormatter;
|
||||||
|
|
||||||
|
public class UsageBasedBilling {
|
||||||
|
|
||||||
|
static final String SUBMIT_CMD = "submit";
|
||||||
|
static final String FETCH_CMD = "fetch";
|
||||||
|
|
||||||
|
private JCommander jCommander;
|
||||||
|
private SubmitUsageCommand submitUsageCmd;
|
||||||
|
private FetchCurrentChargesCommand fetchChargesCmd;
|
||||||
|
|
||||||
|
public UsageBasedBilling() {
|
||||||
|
this.submitUsageCmd = new SubmitUsageCommand();
|
||||||
|
this.fetchChargesCmd = new FetchCurrentChargesCommand();
|
||||||
|
jCommander = JCommander.newBuilder()
|
||||||
|
.addObject(this)
|
||||||
|
.addCommand(submitUsageCmd)
|
||||||
|
.addCommand(fetchChargesCmd)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
setUsageFormatter(SUBMIT_CMD);
|
||||||
|
setUsageFormatter(FETCH_CMD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(String[] args) {
|
||||||
|
String parsedCmdStr;
|
||||||
|
try {
|
||||||
|
jCommander.parse(args);
|
||||||
|
parsedCmdStr = jCommander.getParsedCommand();
|
||||||
|
|
||||||
|
switch (parsedCmdStr) {
|
||||||
|
case SUBMIT_CMD:
|
||||||
|
if (submitUsageCmd.isHelp()) {
|
||||||
|
getSubCommandHandle(SUBMIT_CMD).usage();
|
||||||
|
}
|
||||||
|
System.out.println("Parsing usage request...");
|
||||||
|
submitUsageCmd.submit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FETCH_CMD:
|
||||||
|
if (fetchChargesCmd.isHelp()) {
|
||||||
|
getSubCommandHandle(SUBMIT_CMD).usage();
|
||||||
|
}
|
||||||
|
System.out.println("Preparing fetch query...");
|
||||||
|
fetchChargesCmd.fetch();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.err.println("Invalid command: " + parsedCmdStr);
|
||||||
|
}
|
||||||
|
} catch (ParameterException e) {
|
||||||
|
System.err.println(e.getLocalizedMessage());
|
||||||
|
parsedCmdStr = jCommander.getParsedCommand();
|
||||||
|
if (parsedCmdStr != null) {
|
||||||
|
getSubCommandHandle(parsedCmdStr).usage();
|
||||||
|
} else {
|
||||||
|
jCommander.usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JCommander getSubCommandHandle(String command) {
|
||||||
|
JCommander cmd = jCommander.getCommands().get(command);
|
||||||
|
|
||||||
|
if (cmd == null) {
|
||||||
|
System.err.println("Invalid command: " + command);
|
||||||
|
}
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUsageFormatter(String subCommand) {
|
||||||
|
JCommander cmd = getSubCommandHandle(subCommand);
|
||||||
|
cmd.setUsageFormatter(new UnixStyleUsageFormatter(cmd));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli.converter;
|
||||||
|
|
||||||
|
import com.beust.jcommander.ParameterException;
|
||||||
|
import com.beust.jcommander.converters.BaseConverter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
public class ISO8601TimestampConverter extends BaseConverter<Instant> {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter TS_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss");
|
||||||
|
|
||||||
|
public ISO8601TimestampConverter(String optionName) {
|
||||||
|
super(optionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant convert(String value) {
|
||||||
|
try {
|
||||||
|
return LocalDateTime
|
||||||
|
.parse(value, TS_FORMATTER)
|
||||||
|
.atOffset(ZoneOffset.UTC)
|
||||||
|
.toInstant();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
throw new ParameterException(getErrorString(value, format("an ISO-8601 formatted timestamp (%s)", TS_FORMATTER.toString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli.splitter;
|
||||||
|
|
||||||
|
import com.beust.jcommander.converters.IParameterSplitter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
public class ColonParameterSplitter implements IParameterSplitter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> split(String value) {
|
||||||
|
return asList(value.split(":"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli.validator;
|
||||||
|
|
||||||
|
import com.beust.jcommander.IParameterValidator;
|
||||||
|
import com.beust.jcommander.ParameterException;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class UUIDValidator implements IParameterValidator {
|
||||||
|
|
||||||
|
private static final String UUID_REGEX =
|
||||||
|
"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(String name, String value) throws ParameterException {
|
||||||
|
if (!isValidUUID(value)) {
|
||||||
|
throw new ParameterException(
|
||||||
|
"String parameter " + value + " is not a valid UUID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidUUID(String value) {
|
||||||
|
return Pattern
|
||||||
|
.compile(UUID_REGEX)
|
||||||
|
.matcher(value).matches();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.model;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class CurrentChargesRequest {
|
||||||
|
|
||||||
|
private String customerId;
|
||||||
|
private List<String> subscriptionIds;
|
||||||
|
private boolean itemized;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.model;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class CurrentChargesResponse {
|
||||||
|
|
||||||
|
private String customerId;
|
||||||
|
private BigDecimal amountDue;
|
||||||
|
private List<LineItem> lineItems;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb
|
||||||
|
.append("Current Month Charges: {")
|
||||||
|
.append("\n\tcustomer: ")
|
||||||
|
.append(this.customerId)
|
||||||
|
.append("\n\ttotalAmountDue: ")
|
||||||
|
.append(this.amountDue.setScale(2, RoundingMode.HALF_UP))
|
||||||
|
.append("\n\tlineItems: [");
|
||||||
|
|
||||||
|
for (LineItem li : this.lineItems) {
|
||||||
|
sb
|
||||||
|
.append("\n\t\t{")
|
||||||
|
.append("\n\t\t\tsubscription: ")
|
||||||
|
.append(li.subscriptionId)
|
||||||
|
.append("\n\t\t\tamount: ")
|
||||||
|
.append(li.amount.setScale(2, RoundingMode.HALF_UP))
|
||||||
|
.append("\n\t\t\tquantity: ")
|
||||||
|
.append(li.quantity)
|
||||||
|
.append("\n\t\t},");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("\n\t]\n}\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public static class LineItem {
|
||||||
|
|
||||||
|
private String subscriptionId;
|
||||||
|
private BigDecimal amount;
|
||||||
|
private Integer quantity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.model;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class UsageRequest {
|
||||||
|
|
||||||
|
private String customerId;
|
||||||
|
private String subscriptionId;
|
||||||
|
private PricingType pricingType;
|
||||||
|
private Integer quantity;
|
||||||
|
private BigDecimal price;
|
||||||
|
private Instant timestamp;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb
|
||||||
|
.append("\nUsage: {")
|
||||||
|
.append("\n\tcustomer: ")
|
||||||
|
.append(this.customerId)
|
||||||
|
.append("\n\tsubscription: ")
|
||||||
|
.append(this.subscriptionId)
|
||||||
|
.append("\n\tquantity: ")
|
||||||
|
.append(this.quantity)
|
||||||
|
.append("\n\ttimestamp: ")
|
||||||
|
.append(this.timestamp)
|
||||||
|
.append("\n\tpricingType: ")
|
||||||
|
.append(this.pricingType);
|
||||||
|
|
||||||
|
if (PricingType.PRE_RATED == this.pricingType) {
|
||||||
|
sb
|
||||||
|
.append("\n\tpreRatedAt: ")
|
||||||
|
.append(this.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("\n}\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PricingType {
|
||||||
|
PRE_RATED, UNRATED
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.service;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesRequest;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse.LineItem;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Arrays.fill;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.concurrent.ThreadLocalRandom.current;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
class DefaultFetchCurrentChargesService implements FetchCurrentChargesService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CurrentChargesResponse fetch(CurrentChargesRequest request) {
|
||||||
|
List<String> subscriptions = request.getSubscriptionIds();
|
||||||
|
|
||||||
|
if (subscriptions == null || subscriptions.isEmpty()) {
|
||||||
|
System.out.println("Fetching ALL charges for customer: " + request.getCustomerId());
|
||||||
|
subscriptions = mockSubscriptions();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
System.out.println(format("Fetching charges for customer: %s and subscriptions: %s", request.getCustomerId(), subscriptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentChargesResponse charges = mockCharges(request.getCustomerId(), subscriptions, request.isItemized());
|
||||||
|
System.out.println("Fetched charges...");
|
||||||
|
return charges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CurrentChargesResponse mockCharges(String customerId, List<String> subscriptions, boolean itemized) {
|
||||||
|
List<LineItem> lineItems = mockLineItems(subscriptions);
|
||||||
|
BigDecimal amountDue = lineItems
|
||||||
|
.stream()
|
||||||
|
.map(li -> li.getAmount())
|
||||||
|
.reduce(new BigDecimal("0"), BigDecimal::add);
|
||||||
|
|
||||||
|
return CurrentChargesResponse
|
||||||
|
.builder()
|
||||||
|
.customerId(customerId)
|
||||||
|
.lineItems(itemized ? lineItems : emptyList())
|
||||||
|
.amountDue(amountDue)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LineItem> mockLineItems(List<String> subscriptions) {
|
||||||
|
return subscriptions
|
||||||
|
.stream()
|
||||||
|
.map(subscription -> LineItem.builder()
|
||||||
|
.subscriptionId(subscription)
|
||||||
|
.quantity(current().nextInt(20))
|
||||||
|
.amount(new BigDecimal(current().nextDouble(1_000)))
|
||||||
|
.build())
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> mockSubscriptions() {
|
||||||
|
String[] subscriptions = new String[5];
|
||||||
|
fill(subscriptions, UUID.randomUUID().toString());
|
||||||
|
return asList(subscriptions);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.service;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.UsageRequest;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
class DefaultSubmitUsageService implements SubmitUsageService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String submit(UsageRequest request) {
|
||||||
|
System.out.println("Submitting usage..." + request);
|
||||||
|
|
||||||
|
System.out.println("Submitted usage successfully...");
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.service;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesRequest;
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse;
|
||||||
|
|
||||||
|
public interface FetchCurrentChargesService {
|
||||||
|
|
||||||
|
static FetchCurrentChargesService getDefault() {
|
||||||
|
return new DefaultFetchCurrentChargesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentChargesResponse fetch(CurrentChargesRequest request);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.service;
|
||||||
|
|
||||||
|
import com.baeldung.jcommander.usagebilling.model.UsageRequest;
|
||||||
|
|
||||||
|
public interface SubmitUsageService {
|
||||||
|
|
||||||
|
static SubmitUsageService getDefault() {
|
||||||
|
return new DefaultSubmitUsageService();
|
||||||
|
}
|
||||||
|
|
||||||
|
String submit(UsageRequest request);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.baeldung.jcommander.helloworld;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class HelloWorldAppUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenJCommanderInvokedWithArgs_thenArgsParsed() {
|
||||||
|
|
||||||
|
HelloWorldArgs jArgs = new HelloWorldArgs();
|
||||||
|
JCommander helloCmd = JCommander
|
||||||
|
.newBuilder()
|
||||||
|
.addObject(jArgs)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when
|
||||||
|
String[] argv = new String[] {
|
||||||
|
"--name", "JavaWorld"
|
||||||
|
};
|
||||||
|
helloCmd.parse(argv);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("JavaWorld", jArgs.getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class FetchCurrentChargesCommandUnitTest {
|
||||||
|
|
||||||
|
private JCommander jc = JCommander.newBuilder()
|
||||||
|
.addObject(new FetchCurrentChargesCommand())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenParsedMultipleSubscriptionsParameter_thenParameterSubscriptionsIsPopulated() {
|
||||||
|
FetchCurrentChargesCommand cmd = (FetchCurrentChargesCommand) jc
|
||||||
|
.getObjects()
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
jc.parse(new String[] {
|
||||||
|
"-C", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
|
||||||
|
"-S", "subscriptionA001",
|
||||||
|
"-S", "subscriptionA002",
|
||||||
|
"-S", "subscriptionA003",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(cmd.getSubscriptionIds(),
|
||||||
|
contains("subscriptionA001", "subscriptionA002", "subscriptionA003"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenParsedSubscriptionsColonSeparatedParameter_thenParameterSubscriptionsIsPopulated() {
|
||||||
|
FetchCurrentChargesCommand cmd = (FetchCurrentChargesCommand) jc
|
||||||
|
.getObjects()
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
jc.parse(new String[] {
|
||||||
|
"-C", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
|
||||||
|
"-S", "subscriptionA001:subscriptionA002:subscriptionA003",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(cmd.getSubscriptionIds(),
|
||||||
|
contains("subscriptionA001", "subscriptionA002", "subscriptionA003"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenParsedSubscriptionsWithVariableArity_thenParameterSubscriptionsIsPopulated() {
|
||||||
|
FetchCurrentChargesCommand cmd = (FetchCurrentChargesCommand) jc
|
||||||
|
.getObjects()
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
jc.parse(new String[] {
|
||||||
|
"-C", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
|
||||||
|
"-S", "subscriptionA001", "subscriptionA002", "subscriptionA003",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(cmd.getSubscriptionIds(),
|
||||||
|
contains("subscriptionA001", "subscriptionA002", "subscriptionA003"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.baeldung.jcommander.usagebilling.cli;
|
||||||
|
|
||||||
|
import com.beust.jcommander.JCommander;
|
||||||
|
import com.beust.jcommander.ParameterException;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class SubmitUsageCommandUnitTest {
|
||||||
|
|
||||||
|
private JCommander jc = JCommander.newBuilder()
|
||||||
|
.addObject(new SubmitUsageCommand())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenParsedCustomerParameter_thenParameterOfTypeStringIsPopulated() {
|
||||||
|
jc.parse(new String[] {
|
||||||
|
"--customer", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
|
||||||
|
"--subscription", "subscriptionPQRMN001",
|
||||||
|
"--pricing-type", "PRE_RATED",
|
||||||
|
"--timestamp", "2019-10-03T10:58:00",
|
||||||
|
"--quantity", "7",
|
||||||
|
"--price", "24.56"
|
||||||
|
});
|
||||||
|
|
||||||
|
SubmitUsageCommand cmd = (SubmitUsageCommand) jc
|
||||||
|
.getObjects()
|
||||||
|
.get(0);
|
||||||
|
assertEquals("cb898e7a-f2a0-46d2-9a09-531f1cee1839", cmd.getCustomerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenParsedTimestampParameter_thenParameterOfTypeInstantIsPopulated() {
|
||||||
|
jc.parse(new String[] {
|
||||||
|
"--customer", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
|
||||||
|
"--subscription", "subscriptionPQRMN001",
|
||||||
|
"--pricing-type", "PRE_RATED",
|
||||||
|
"--timestamp", "2019-10-03T10:58:00",
|
||||||
|
"--quantity", "7",
|
||||||
|
"--price", "24.56"
|
||||||
|
});
|
||||||
|
|
||||||
|
SubmitUsageCommand cmd = (SubmitUsageCommand) jc
|
||||||
|
.getObjects()
|
||||||
|
.get(0);
|
||||||
|
assertEquals("2019-10-03T10:58:00Z", cmd
|
||||||
|
.getTimestamp()
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ParameterException.class)
|
||||||
|
public void whenParsedCustomerIdNotUUID_thenParameterException() {
|
||||||
|
jc.parse(new String[] {
|
||||||
|
"--customer", "customer001",
|
||||||
|
"--subscription", "subscriptionPQRMN001",
|
||||||
|
"--pricing-type", "PRE_RATED",
|
||||||
|
"--timestamp", "2019-10-03T10:58:00",
|
||||||
|
"--quantity", "7",
|
||||||
|
"--price", "24.56"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
2
pom.xml
2
pom.xml
|
@ -541,6 +541,7 @@
|
||||||
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
||||||
<module>libraries</module>
|
<module>libraries</module>
|
||||||
<module>libraries-2</module>
|
<module>libraries-2</module>
|
||||||
|
<module>libraries-3</module>
|
||||||
<module>libraries-data</module>
|
<module>libraries-data</module>
|
||||||
<module>libraries-data-2</module>
|
<module>libraries-data-2</module>
|
||||||
<module>libraries-data-db</module>
|
<module>libraries-data-db</module>
|
||||||
|
@ -1307,6 +1308,7 @@
|
||||||
|
|
||||||
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
<!-- <module>lagom</module> --> <!-- Not a maven project -->
|
||||||
<module>libraries</module>
|
<module>libraries</module>
|
||||||
|
<module>libraries-3</module>
|
||||||
<module>libraries-data</module>
|
<module>libraries-data</module>
|
||||||
<module>libraries-data-2</module>
|
<module>libraries-data-2</module>
|
||||||
<module>libraries-data-db</module>
|
<module>libraries-data-db</module>
|
||||||
|
|
Loading…
Reference in New Issue