Java tutorial
// Copyright 2016 The Nomulus 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.Preconditions.checkArgument; import static google.registry.model.RoidSuffixes.isRoidSuffixUsed; import static google.registry.util.CollectionUtils.findDuplicates; import static google.registry.util.DomainNameUtils.canonicalizeDomainName; import com.beust.jcommander.Parameter; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import google.registry.model.pricing.StaticPremiumListPricingEngine; import google.registry.model.registry.Registries; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.Registry.TldType; import google.registry.model.registry.label.PremiumList; import google.registry.tools.params.OptionalIntervalParameter; import google.registry.tools.params.OptionalStringParameter; import google.registry.tools.params.TransitionListParameter.BillingCostTransitions; import google.registry.tools.params.TransitionListParameter.TldStateTransitions; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import org.joda.money.Money; import org.joda.time.DateTime; import org.joda.time.Duration; import org.joda.time.Interval; /** Shared base class for commands to create or update a TLD. */ abstract class CreateOrUpdateTldCommand extends MutatingCommand { @Inject @Named("dnsWriterNames") Set<String> dnsWriterNames; @Parameter(description = "Names of the TLDs", required = true) List<String> mainParameters; @Parameter(names = "--escrow", description = "Whether to enable nightly RDE escrow deposits", arity = 1) private Boolean escrow; @Parameter(names = "--dns", description = "Set to false to pause writing to the DNS queue", arity = 1) private Boolean dns; @Nullable @Parameter(names = "--add_grace_period", description = "Length of the add grace period") Duration addGracePeriod; @Nullable @Parameter(names = "--redemption_grace_period", description = "Length of the redemption grace period") Duration redemptionGracePeriod; @Nullable @Parameter(names = "--pending_delete_length", description = "Length of the pending delete period") Duration pendingDeleteLength; @Nullable @Parameter(names = "--automatic_transfer_length", description = "Length of the automatic transfer period") private Duration automaticTransferLength; @Nullable @Parameter(names = "--restore_billing_cost", description = "One-time billing cost for restoring a domain") private Money restoreBillingCost; @Nullable @Parameter(names = "--roid_suffix", description = "The suffix to be used for ROIDs, e.g. COM for .com domains (which then " + "creates roids looking like 123ABC-COM)") String roidSuffix; @Nullable @Parameter(names = "--server_status_change_cost", description = "One-time billing cost for a server status change") private Money serverStatusChangeCost; @Nullable @Parameter(names = "--tld_type", description = "Tld type (REAL or TEST)") private TldType tldType; @Nullable @Parameter(names = "--premium_price_ack_required", description = "Whether operations on premium domains require explicit ack of prices", arity = 1) private Boolean premiumPriceAckRequired; @Nullable @Parameter(names = "--create_billing_cost", description = "Per-year billing cost for creating a domain") Money createBillingCost; @Nullable @Parameter(names = "--drive_folder_id", description = "Id of the folder in drive used to publish information for this TLD", converter = OptionalStringParameter.class, validateWith = OptionalStringParameter.class) Optional<String> driveFolderId; @Nullable @Parameter(names = "--lordn_username", description = "Username for LORDN uploads", converter = OptionalStringParameter.class, validateWith = OptionalStringParameter.class) Optional<String> lordnUsername; @Nullable @Parameter(names = "--premium_list", description = "The name of the premium list to apply to the TLD", converter = OptionalStringParameter.class, validateWith = OptionalStringParameter.class) Optional<String> premiumListName; @Parameter(names = "--tld_state_transitions", converter = TldStateTransitions.class, validateWith = TldStateTransitions.class, description = "Comma-delimited list of TLD state transitions, of the form " + "<time>=<tld-state>[,<time>=<tld-state>]*") ImmutableSortedMap<DateTime, TldState> tldStateTransitions = ImmutableSortedMap.of(); @Parameter(names = "--renew_billing_cost_transitions", converter = BillingCostTransitions.class, validateWith = BillingCostTransitions.class, description = "Comma-delimited list of renew billing cost transitions, of the form " + "<time>=<money-amount>[,<time>=<money-amount>]* where each amount " + "represents the per-year billing cost for renewing a domain") ImmutableSortedMap<DateTime, Money> renewBillingCostTransitions = ImmutableSortedMap.of(); @Parameter(names = "--eap_fee_schedule", converter = BillingCostTransitions.class, validateWith = BillingCostTransitions.class, description = "Comma-delimited list of EAP fees effective on specific dates, of the form " + "<time>=<money-amount>[,<time>=<money-amount>]* where each amount represents the " + "EAP fee for creating a new domain under the TLD.") ImmutableSortedMap<DateTime, Money> eapFeeSchedule = ImmutableSortedMap.of(); @Nullable @Parameter(names = "--reserved_lists", description = "A comma-separated list of reserved list names to be applied to the TLD") List<String> reservedListNames; @Nullable @Parameter(names = "--allowed_registrants", description = "A comma-separated list of allowed registrants for the TLD") List<String> allowedRegistrants; @Nullable @Parameter(names = "--allowed_nameservers", description = "A comma-separated list of allowed nameservers for the TLD") List<String> allowedNameservers; @Parameter(names = { "-o", "--override_reserved_list_rules" }, description = "Override restrictions on reserved list naming") boolean overrideReservedListRules; @Nullable @Parameter(names = "--claims_period_end", description = "The end of the claims period") DateTime claimsPeriodEnd; @Nullable @Parameter(names = "--dns_writer", description = "The name of the DnsWriter implementation to use", converter = OptionalStringParameter.class, validateWith = OptionalStringParameter.class) Optional<String> dnsWriter; @Nullable @Parameter(names = "--lrp_period", description = "LRP period (in ISO-8601 format, e.g. 2004-06-09T12:30:00Z/2004-07-10T13:30:00Z", converter = OptionalIntervalParameter.class, validateWith = OptionalIntervalParameter.class) private Optional<Interval> lrpPeriod; /** Returns the existing registry (for update) or null (for creates). */ @Nullable abstract Registry getOldRegistry(String tld); abstract ImmutableSet<String> getAllowedRegistrants(Registry oldRegistry); abstract ImmutableSet<String> getAllowedNameservers(Registry oldRegistry); abstract ImmutableSet<String> getReservedLists(Registry oldRegistry); abstract Optional<Map.Entry<DateTime, TldState>> getTldStateTransitionToAdd(); /** Subclasses can override this to set their own properties. */ void setCommandSpecificProperties(@SuppressWarnings("unused") Registry.Builder builder) { } /** Subclasses can override this to assert that the command can be run in this environment. */ void assertAllowedEnvironment() { } protected abstract void initTldCommand() throws Exception; @Override protected final void init() throws Exception { assertAllowedEnvironment(); initTldCommand(); String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters)); checkArgument(duplicates.isEmpty(), "Duplicate arguments found: \"%s\"", duplicates); Set<String> tlds = ImmutableSet.copyOf(mainParameters); checkArgument(roidSuffix == null || tlds.size() == 1, "Can't update roid suffixes on multiple TLDs simultaneously"); for (String tld : tlds) { checkArgument(tld.equals(canonicalizeDomainName(tld))); checkArgument(!CharMatcher.javaDigit().matches(tld.charAt(0)), "TLDs cannot begin with a number."); Registry oldRegistry = getOldRegistry(tld); if (roidSuffix != null) { checkArgument( !isRoidSuffixUsed(roidSuffix) || (oldRegistry != null && roidSuffix.equals(oldRegistry.getRoidSuffix())), "The roid suffix %s is already in use", roidSuffix); } // TODO(b/26901539): Add a flag to set the pricing engine once we have more than one option. Registry.Builder builder = oldRegistry == null ? new Registry.Builder().setTldStr(tld) .setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME) : oldRegistry.asBuilder(); if (escrow != null) { builder.setEscrowEnabled(escrow); } if (dns != null) { builder.setDnsPaused(!dns); } Optional<Map.Entry<DateTime, TldState>> tldStateTransitionToAdd = getTldStateTransitionToAdd(); if (!tldStateTransitions.isEmpty()) { builder.setTldStateTransitions(tldStateTransitions); } else if (tldStateTransitionToAdd.isPresent()) { ImmutableSortedMap.Builder<DateTime, TldState> newTldStateTransitions = ImmutableSortedMap .naturalOrder(); if (oldRegistry != null) { checkArgument( oldRegistry.getTldStateTransitions().lastKey() .isBefore(tldStateTransitionToAdd.get().getKey()), "Cannot add %s at %s when there is a later transition already scheduled", tldStateTransitionToAdd.get().getValue(), tldStateTransitionToAdd.get().getKey()); newTldStateTransitions.putAll(oldRegistry.getTldStateTransitions()); } builder.setTldStateTransitions( newTldStateTransitions.put(getTldStateTransitionToAdd().get()).build()); } if (!renewBillingCostTransitions.isEmpty()) { // TODO(b/20764952): need invoicing support for multiple renew billing costs. if (renewBillingCostTransitions.size() > 1) { System.err.println("----------------------\n" + "WARNING: Do not set multiple renew cost transitions until b/20764952 is fixed.\n" + "----------------------\n"); } builder.setRenewBillingCostTransitions(renewBillingCostTransitions); } if (!eapFeeSchedule.isEmpty()) { builder.setEapFeeSchedule(eapFeeSchedule); } if (addGracePeriod != null) { builder.setAddGracePeriodLength(addGracePeriod); } if (redemptionGracePeriod != null) { builder.setRedemptionGracePeriodLength(redemptionGracePeriod); } if (pendingDeleteLength != null) { builder.setPendingDeleteLength(pendingDeleteLength); } if (automaticTransferLength != null) { builder.setAutomaticTransferLength(automaticTransferLength); } if (driveFolderId != null) { builder.setDriveFolderId(driveFolderId.orNull()); } if (createBillingCost != null) { builder.setCreateBillingCost(createBillingCost); } if (restoreBillingCost != null) { builder.setRestoreBillingCost(restoreBillingCost); } if (roidSuffix != null) { builder.setRoidSuffix(roidSuffix); } if (serverStatusChangeCost != null) { builder.setServerStatusChangeBillingCost(serverStatusChangeCost); } if (tldType != null) { builder.setTldType(tldType); } if (premiumPriceAckRequired != null) { builder.setPremiumPriceAckRequired(premiumPriceAckRequired); } if (lordnUsername != null) { builder.setLordnUsername(lordnUsername.orNull()); } if (claimsPeriodEnd != null) { builder.setClaimsPeriodEnd(claimsPeriodEnd); } if (premiumListName != null) { if (premiumListName.isPresent()) { Optional<PremiumList> premiumList = PremiumList.get(premiumListName.get()); checkArgument(premiumList.isPresent(), String.format("The premium list '%s' doesn't exist", premiumListName.get())); builder.setPremiumList(premiumList.get()); } else { builder.setPremiumList(null); } } if (dnsWriter != null) { if (dnsWriter.isPresent()) { checkArgument(dnsWriterNames.contains(dnsWriter.get()), "The DNS writer '%s' doesn't exist", dnsWriter.get()); builder.setDnsWriter(dnsWriter.get()); } } if (lrpPeriod != null) { builder.setLrpPeriod(lrpPeriod.orNull()); } ImmutableSet<String> newReservedListNames = getReservedLists(oldRegistry); checkReservedListValidityForTld(tld, newReservedListNames); builder.setReservedListsByName(newReservedListNames); builder.setAllowedRegistrantContactIds(getAllowedRegistrants(oldRegistry)); builder.setAllowedFullyQualifiedHostNames(getAllowedNameservers(oldRegistry)); // Update the Registry object. setCommandSpecificProperties(builder); stageEntityChange(oldRegistry, builder.build()); } } @Override public String execute() throws Exception { try { return super.execute(); } finally { // Manually reset the cache here so that subsequent commands (e.g. in SetupOteCommand) see // the latest version of the data. // TODO(b/24903801): change all those places to use uncached code paths to get Registries. Registries.resetCache(); } } private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames) { ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); for (String reservedListName : reservedListNames) { if (!reservedListName.startsWith("common_") && !reservedListName.startsWith(tld + "_")) { builder.add(reservedListName); } } ImmutableList<String> invalidNames = builder.build(); if (!invalidNames.isEmpty()) { String errMsg = String.format("The reserved list(s) %s cannot be applied to the tld %s", Joiner.on(", ").join(invalidNames), tld); if (overrideReservedListRules) { System.err.println("Error overriden: " + errMsg); } else { throw new IllegalArgumentException(errMsg); } } } }