mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
It's best to be consistent and use the same thing everywhere. "clientId" was already used in more places and is shorter and no more ambiguous, so it's the logical one to win out. Note that this CL is almost solely a big Eclipse-assisted refactoring. There are two places that I did not change clientIdentifier -- the actual entity field on Registrar (though I did change all getters and setters), and the name of a column on the exported registrar spreadsheet. Both would require data migrations. Also fixes a few minor nits discovered in touched files, including an incorrect test in OfyFilterTest.java and some superfluous uses of String.format() when calling checkArgument(). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=133956465
213 lines
9.1 KiB
Java
213 lines
9.1 KiB
Java
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package google.registry.tools;
|
|
|
|
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
|
|
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
import static google.registry.model.registry.Registries.assertTldExists;
|
|
import static java.util.Arrays.asList;
|
|
import static org.joda.time.DateTimeZone.UTC;
|
|
|
|
import com.beust.jcommander.Parameter;
|
|
import com.beust.jcommander.Parameters;
|
|
import com.google.common.base.Function;
|
|
import com.google.common.base.Splitter;
|
|
import com.google.common.collect.FluentIterable;
|
|
import com.google.common.collect.ImmutableMultimap;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.re2j.Matcher;
|
|
import com.google.re2j.Pattern;
|
|
import google.registry.model.billing.RegistrarCredit;
|
|
import google.registry.model.billing.RegistrarCredit.CreditType;
|
|
import google.registry.model.billing.RegistrarCreditBalance;
|
|
import google.registry.model.registrar.Registrar;
|
|
import google.registry.model.registry.Registry;
|
|
import java.io.IOException;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.List;
|
|
import org.joda.money.BigMoney;
|
|
import org.joda.money.CurrencyUnit;
|
|
import org.joda.money.Money;
|
|
import org.joda.time.DateTime;
|
|
|
|
/**
|
|
* Command for creating new auction credits based on a CSV file from Pool.
|
|
*
|
|
* <p>The CSV file from the auction provider uses double-quotes around every field, so in order to
|
|
* extract the raw field value we strip off the quotes after splitting each line by commas. We are
|
|
* using a simple parsing strategy that does not support embedded quotation marks, commas, or
|
|
* newlines.
|
|
*
|
|
* <p>Example file format:
|
|
*
|
|
* <pre>
|
|
* "Affiliate","DomainName","Email","BidderId","BidderStatus","UpdatedAt",
|
|
* "SalePrice","Commissions","CurrencyCode"
|
|
* "reg1","foo.xn--q9jyb4c","email1@example.com","???_300","INACTIVE","4/3/2014 7:13:09 PM",
|
|
* "1000.0000","0.0000","JPY"
|
|
* "reg2","foo.xn--q9jyb4c","email2@example.net","???_64","WIN","4/3/2014 7:13:09 PM",
|
|
* "1000.0000","40.0000","JPY"
|
|
* </pre>
|
|
*
|
|
* <p>We only care about three fields: 1) the "Affiliate" field which corresponds to the registrar
|
|
* clientId stored in datastore, and which we use to determine which registrar gets the credit, 2)
|
|
* the "Commissions" field which contains the amount of the auction credit (as determined by logic
|
|
* on the auction provider's side, see the Finance Requirements Doc for more information), and 3)
|
|
* the "CurrencyCode" field, which we validate matches the TLD-wide currency for this TLD.
|
|
*/
|
|
// TODO(b/16009815): Switch this file to using a real CSV parser.
|
|
@Parameters(separators = " =", commandDescription = "Create new auction credits based on CSV")
|
|
final class CreateAuctionCreditsCommand extends MutatingCommand {
|
|
|
|
@Parameter(
|
|
names = "--input_file",
|
|
description = "CSV file for the Pool.com commissions report",
|
|
required = true)
|
|
private Path inputFile;
|
|
|
|
@Parameter(
|
|
names = {"-t", "--tld"},
|
|
description = "The TLD corresponding to this commissions report",
|
|
required = true)
|
|
private String tld;
|
|
|
|
@Parameter(
|
|
names = "--effective_time",
|
|
description = "The time at which these auction credits should become effective",
|
|
required = true)
|
|
private DateTime effectiveTime;
|
|
|
|
/** Enum containing the headers we expect in the Pool.com CSV file, in order. */
|
|
private enum CsvHeader {
|
|
AFFILIATE,
|
|
DOMAIN_NAME,
|
|
EMAIL,
|
|
BIDDER_ID,
|
|
BIDDER_STATUS,
|
|
UPDATED_AT,
|
|
SALE_PRICE,
|
|
COMMISSIONS,
|
|
CURRENCY_CODE;
|
|
|
|
public static List<String> getHeaders() {
|
|
return FluentIterable.from(asList(values()))
|
|
.transform(new Function<CsvHeader, String>() {
|
|
@Override
|
|
public String apply(CsvHeader header) {
|
|
// Returns the name of the header as it appears in the CSV file.
|
|
return UPPER_UNDERSCORE.to(UPPER_CAMEL, header.name());
|
|
}})
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
private static final Pattern QUOTED_STRING = Pattern.compile("\"(.*)\"");
|
|
|
|
/** Helper function to unwrap a quoted string, failing if the string is not quoted. */
|
|
private static final Function<String, String> UNQUOTER = new Function<String, String>() {
|
|
@Override
|
|
public String apply(String input) {
|
|
Matcher matcher = QUOTED_STRING.matcher(input);
|
|
checkArgument(matcher.matches(), "Input not quoted");
|
|
return matcher.group(1);
|
|
}};
|
|
|
|
/** Returns the input string of quoted CSV values split into the list of unquoted values. */
|
|
private static List<String> splitCsvLine(String line) {
|
|
return FluentIterable.from(Splitter.on(',').split(line)).transform(UNQUOTER).toList();
|
|
}
|
|
|
|
@Override
|
|
protected void init() throws Exception {
|
|
assertTldExists(tld);
|
|
ImmutableMultimap<Registrar, BigMoney> creditMap = parseCreditsFromCsv(inputFile, tld);
|
|
stageCreditCreations(creditMap);
|
|
}
|
|
|
|
/**
|
|
* Parses the provided CSV file of data from the auction provider and returns a multimap mapping
|
|
* each registrar to the collection of auction credit amounts from this TLD's auctions that should
|
|
* be awarded to this registrar, and validating that every credit amount's currency is in the
|
|
* specified TLD-wide currency.
|
|
*/
|
|
private static ImmutableMultimap<Registrar, BigMoney> parseCreditsFromCsv(
|
|
Path csvFile, String tld) throws IOException {
|
|
List<String> lines = Files.readAllLines(csvFile, StandardCharsets.UTF_8);
|
|
checkArgument(CsvHeader.getHeaders().equals(splitCsvLine(lines.get(0))),
|
|
"Expected CSV header line not present");
|
|
ImmutableMultimap.Builder<Registrar, BigMoney> builder = new ImmutableMultimap.Builder<>();
|
|
for (String line : Iterables.skip(lines, 1)) {
|
|
List<String> fields = splitCsvLine(line);
|
|
checkArgument(CsvHeader.getHeaders().size() == fields.size(), "Wrong number of fields");
|
|
try {
|
|
String registrarId = fields.get(CsvHeader.AFFILIATE.ordinal());
|
|
Registrar registrar = checkNotNull(
|
|
Registrar.loadByClientId(registrarId), "Registrar %s not found", registrarId);
|
|
CurrencyUnit tldCurrency = Registry.get(tld).getCurrency();
|
|
CurrencyUnit currency = CurrencyUnit.of((fields.get(CsvHeader.CURRENCY_CODE.ordinal())));
|
|
checkArgument(tldCurrency.equals(currency),
|
|
"Credit in wrong currency (%s should be %s)", currency, tldCurrency);
|
|
// We use BigDecimal and BigMoney to preserve fractional currency units when computing the
|
|
// total amount of each credit (since auction credits are percentages of winning bids).
|
|
BigDecimal creditAmount = new BigDecimal(fields.get(CsvHeader.COMMISSIONS.ordinal()));
|
|
BigMoney credit = BigMoney.of(currency, creditAmount);
|
|
builder.put(registrar, credit);
|
|
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
throw new IllegalArgumentException("Error in line: " + line, e);
|
|
}
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
/**
|
|
* Stages the creation of RegistrarCredit and RegistrarCreditBalance instances for each
|
|
* registrar in the provided multimap of credit amounts by registrar. The balance instance
|
|
* created is the total of all the credit amounts for a given registrar.
|
|
*/
|
|
private void stageCreditCreations(ImmutableMultimap<Registrar, BigMoney> creditMap) {
|
|
DateTime now = DateTime.now(UTC);
|
|
CurrencyUnit currency = Registry.get(tld).getCurrency();
|
|
for (Registrar registrar : creditMap.keySet()) {
|
|
// Use RoundingMode.UP to be nice and give registrars the extra fractional units.
|
|
Money totalAmount =
|
|
BigMoney.total(currency, creditMap.get(registrar)).toMoney(RoundingMode.UP);
|
|
System.out.printf("Total auction credit balance for %s: %s\n",
|
|
registrar.getClientId(), totalAmount);
|
|
|
|
// Create the actual credit and initial credit balance.
|
|
RegistrarCredit credit = new RegistrarCredit.Builder()
|
|
.setParent(registrar)
|
|
.setType(CreditType.AUCTION)
|
|
.setCreationTime(now)
|
|
.setCurrency(currency)
|
|
.setTld(tld)
|
|
.build();
|
|
RegistrarCreditBalance creditBalance = new RegistrarCreditBalance.Builder()
|
|
.setParent(credit)
|
|
.setEffectiveTime(effectiveTime)
|
|
.setWrittenTime(now)
|
|
.setAmount(totalAmount)
|
|
.build();
|
|
stageEntityChange(null, credit);
|
|
stageEntityChange(null, creditBalance);
|
|
}
|
|
}
|
|
}
|