mv com/google/domain/registry google/registry

This change renames directories in preparation for the great package
rename. The repository is now in a broken state because the code
itself hasn't been updated. However this should ensure that git
correctly preserves history for each file.
This commit is contained in:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,35 @@
// 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 com.google.domain.registry.model;
import java.lang.reflect.Field;
/**
* A helper that exposes package-private fields in this package for reflective lookup.
* <p>
* By adding a subclass of this to every package in the model, we can write generic code that can
* access fields with package private access. The other alternative is to call
* {@link Field#setAccessible} with {@code true} on any such Field objects, but that does not work
* reliably in Google App Engine cross-package because of its custom security manager
* implementation.
*/
public abstract class AbstractFieldExposer {
public abstract Object getFieldValue(Object instance, Field field) throws IllegalAccessException;
public abstract void setFieldValue(Object instance, Field field, Object value)
throws IllegalAccessException;
public abstract void setAccessible(Field field);
}

View file

@ -0,0 +1,95 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
FIELD_EXPOSERS = [
"FieldExposer.java",
"billing/FieldExposer.java",
"common/FieldExposer.java",
"contact/FieldExposer.java",
"dns/FieldExposer.java",
"domain/FieldExposer.java",
"domain/allocate/FieldExposer.java",
"domain/fee/FieldExposer.java",
"domain/launch/FieldExposer.java",
"domain/rgp/FieldExposer.java",
"domain/secdns/FieldExposer.java",
"eppcommon/FieldExposer.java",
"eppinput/FieldExposer.java",
"eppoutput/FieldExposer.java",
"export/FieldExposer.java",
"host/FieldExposer.java",
"index/FieldExposer.java",
"mark/FieldExposer.java",
"ofy/FieldExposer.java",
"poll/FieldExposer.java",
"registrar/FieldExposer.java",
"registry/FieldExposer.java",
"registry/label/FieldExposer.java",
"reporting/FieldExposer.java",
"server/FieldExposer.java",
"rde/FieldExposer.java",
"smd/FieldExposer.java",
"tmch/FieldExposer.java",
"transfer/FieldExposer.java",
"translators/FieldExposer.java",
]
# Generate FieldExposer classes to work around AppEngine's security limitations.
genrule(
name = "field_exposers",
srcs = ["generate_field_exposer.sh"],
outs = FIELD_EXPOSERS,
cmd = "for FILE in $(OUTS); do " +
"./$(location generate_field_exposer.sh) $$FILE >> $$FILE;" +
"done",
visibility = ["//visibility:private"],
)
# Generate a registry of FieldExposers.
genrule(
name = "field_exposer_registry",
srcs = ["generate_field_exposer_registry.sh"],
outs = ["FieldExposerRegistry.java"],
cmd = "./$(location generate_field_exposer_registry.sh) \"" +
", ".join(FIELD_EXPOSERS) +
");\" >\"$@\"",
visibility = ["//visibility:private"],
)
java_library(
name = "model",
srcs = glob([
"*.java",
"*/*.java",
"*/*/*.java",
]) + ["FieldExposerRegistry.java"] + FIELD_EXPOSERS,
visibility = ["//visibility:public"],
deps = [
":field_exposer_registry",
":field_exposers",
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/cache",
"//java/com/google/common/collect",
"//java/com/google/common/hash",
"//java/com/google/common/io",
"//java/com/google/common/math",
"//java/com/google/common/net",
"//java/com/google/common/primitives",
"//java/com/google/common/reflect",
"//java/com/google/common/util/concurrent",
"//java/com/google/domain/registry/config",
"//java/com/google/domain/registry/util",
"//java/com/google/domain/registry/xml",
"//third_party/java/appengine:appengine-api",
"//third_party/java/dagger",
"//third_party/java/joda_money",
"//third_party/java/joda_time",
"//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject",
"//third_party/java/objectify:objectify-v4_1",
"//third_party/java/servlet/servlet_api",
],
)

View file

@ -0,0 +1,41 @@
// 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 com.google.domain.registry.model;
import javax.xml.bind.annotation.XmlTransient;
/**
* Base class for entities that are the root of a Registry 2.0 entity group that gets enrolled in
* commit logs for backup purposes.
*
* <p>The commit log system needs to preserve the ordering of closely timed mutations to entities
* in a single entity group. We require an {@link UpdateAutoTimestamp} field on the root of a group
* so that we can enforce strictly increasing timestamps.
*/
public abstract class BackupGroupRoot extends ImmutableObject {
/**
* An automatically managed timestamp of when this object was last written to datastore.
*
* <p>Note that this is distinct from the EPP-specified {@link EppResource#lastEppUpdateTime}, in
* that this is updated on every save, rather than only in response to an {@code <update>} command
*/
@XmlTransient
UpdateAutoTimestamp updateTimestamp = UpdateAutoTimestamp.create(null);
/** Get the {@link UpdateAutoTimestamp} for this entity. */
public final UpdateAutoTimestamp getUpdateAutoTimestamp() {
return updateTimestamp;
}
}

View file

@ -0,0 +1,108 @@
// 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 com.google.domain.registry.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.base.Optional;
import com.google.domain.registry.model.ofy.ObjectifyService;
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
import java.lang.reflect.Field;
/** Interface for {@link ImmutableObject} subclasses that have a builder. */
public interface Buildable {
Builder<?> asBuilder();
/**
* Boilerplate for immutable builders.
* <p>
* This can be used without implementing {@link Buildable}.
*/
public abstract static class Builder<S> {
private S instance;
protected Builder() {
this.instance = new TypeInstantiator<S>(getClass()){}.instantiate();
// Only ImmutableObject is allowed, but enforcing that via the generics gets ugly.
checkState(instance instanceof ImmutableObject);
}
protected Builder(S instance) {
this.instance = checkNotNull(instance);
}
protected S getInstance() {
return checkNotNull(instance, "Cannot modify state after calling 'build()'.");
}
/** Build the instance. */
public S build() {
try {
// If this object has a Long or long Objectify @Id field that is not set, set it now.
Field idField = null;
try {
idField = ModelUtils.getAllFields(instance.getClass()).get(
ofy().factory().getMetadata(instance.getClass()).getKeyMetadata().getIdFieldName());
} catch (Exception e) {
// Expected if the class is not registered with Objectify.
}
if (idField != null
&& !idField.getType().equals(String.class)
&& Optional.fromNullable((Long) ModelUtils.getFieldValue(instance, idField))
.or(0L) == 0) {
ModelUtils.setFieldValue(instance, idField, ObjectifyService.allocateId());
}
return instance;
} finally {
// Clear the internal instance so you can't accidentally mutate it through this builder.
instance = null;
}
}
}
/** Boilerplate for abstract immutable builders that need to be able to cast "this". */
public abstract class GenericBuilder<S, B extends GenericBuilder<?, ?>> extends Builder<S> {
protected GenericBuilder() {}
protected GenericBuilder(S instance) {
super(instance);
}
@SuppressWarnings("unchecked")
protected B thisCastToDerived() {
return (B) this;
}
}
/**
* Interface for objects that can produce an "overlay", which means a copy where non-null fields
* from another object are copied over, but null fields on the source are not.
* <p>
* Warning: Do not use {@code emptyToNull} methods in the getters of an {@link Overlayable}! We
* use null to mean "skip this field" whereas empty means "set this field to empty", so they are
* semantically different.
*
* @param <T> the derived type
*/
public interface Overlayable<T> extends Buildable {
/** Return an overlay of this object using non-null fields from the source. */
T overlay(T source);
}
}

View file

@ -0,0 +1,40 @@
// 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 com.google.domain.registry.model;
import com.google.domain.registry.model.translators.CreateAutoTimestampTranslatorFactory;
import org.joda.time.DateTime;
/**
* A timestamp that auto-updates when first saved to datastore.
*
* @see CreateAutoTimestampTranslatorFactory
*/
public class CreateAutoTimestamp extends ImmutableObject {
DateTime timestamp;
/** Returns the timestamp. */
public DateTime getTimestamp() {
return timestamp;
}
public static CreateAutoTimestamp create(DateTime timestamp) {
CreateAutoTimestamp instance = new CreateAutoTimestamp();
instance.timestamp = timestamp;
return instance;
}
}

View file

@ -0,0 +1,129 @@
// 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 com.google.domain.registry.model;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.billing.BillingEvent;
import com.google.domain.registry.model.billing.RegistrarBillingEntry;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCreditBalance;
import com.google.domain.registry.model.common.EntityGroupRoot;
import com.google.domain.registry.model.common.GaeUserIdConverter;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.DomainBase;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.export.LogsExportCursor;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.model.index.DomainApplicationIndex;
import com.google.domain.registry.model.index.EppResourceIndex;
import com.google.domain.registry.model.index.EppResourceIndexBucket;
import com.google.domain.registry.model.index.ForeignKeyIndex;
import com.google.domain.registry.model.ofy.CommitLogBucket;
import com.google.domain.registry.model.ofy.CommitLogCheckpoint;
import com.google.domain.registry.model.ofy.CommitLogCheckpointRoot;
import com.google.domain.registry.model.ofy.CommitLogManifest;
import com.google.domain.registry.model.ofy.CommitLogMutation;
import com.google.domain.registry.model.poll.PollMessage;
import com.google.domain.registry.model.rde.RdeRevision;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.RegistryCursor;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.model.registry.label.ReservedList;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.model.server.Lock;
import com.google.domain.registry.model.server.ServerSecret;
import com.google.domain.registry.model.smd.SignedMarkRevocationList;
import com.google.domain.registry.model.tmch.ClaimsListShard;
import com.google.domain.registry.model.tmch.ClaimsListShard.ClaimsListRevision;
import com.google.domain.registry.model.tmch.ClaimsListShard.ClaimsListSingleton;
import com.google.domain.registry.model.tmch.TmchCrl;
import com.googlecode.objectify.Key;
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
public final class EntityClasses {
/** Set of entity classes. */
@SuppressWarnings("unchecked") // varargs
public static final ImmutableSet<Class<? extends ImmutableObject>> ALL_CLASSES =
ImmutableSet.<Class<? extends ImmutableObject>>of(
BillingEvent.Cancellation.class,
BillingEvent.Modification.class,
BillingEvent.OneTime.class,
BillingEvent.Recurring.class,
ClaimsListShard.class,
ClaimsListRevision.class,
ClaimsListSingleton.class,
CommitLogBucket.class,
CommitLogCheckpoint.class,
CommitLogCheckpointRoot.class,
CommitLogManifest.class,
CommitLogMutation.class,
ContactResource.class,
DomainApplication.class,
DomainApplicationIndex.class,
DomainBase.class,
DomainResource.class,
EntityGroupRoot.class,
EppResourceIndex.class,
EppResourceIndexBucket.class,
ForeignKeyIndex.ForeignKeyContactIndex.class,
ForeignKeyIndex.ForeignKeyDomainIndex.class,
ForeignKeyIndex.ForeignKeyHostIndex.class,
GaeUserIdConverter.class,
HistoryEntry.class,
HostResource.class,
Lock.class,
LogsExportCursor.class,
PollMessage.class,
PollMessage.Autorenew.class,
PollMessage.OneTime.class,
PremiumList.class,
PremiumList.PremiumListEntry.class,
PremiumList.PremiumListRevision.class,
RdeRevision.class,
Registrar.class,
RegistrarBillingEntry.class,
RegistrarContact.class,
RegistrarCredit.class,
RegistrarCreditBalance.class,
Registry.class,
RegistryCursor.class,
ReservedList.class,
ServerSecret.class,
SignedMarkRevocationList.class,
TmchCrl.class);
/**
* Function that converts an Objectify-registered class to its datastore kind name.
*
* <p>Note that this mapping is not one-to-one, since polymorphic subclasses of an entity all
* have the same datastore kind. (In theory, two distinct top-level entities could also map to
* the same kind since it's just {@code class.getSimpleName()}, but we test against that.)
*/
public static final Function<Class<? extends ImmutableObject>, String> CLASS_TO_KIND_FUNCTION =
new Function<Class<? extends ImmutableObject>, String>() {
@Override
public String apply(Class<? extends ImmutableObject> clazz) {
return Key.getKind(clazz);
}
};
private EntityClasses() {}
}

View file

@ -0,0 +1,324 @@
// 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 com.google.domain.registry.model;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static com.google.domain.registry.util.CollectionUtils.difference;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
import com.google.domain.registry.model.ofy.CommitLogManifest;
import com.google.domain.registry.model.transfer.TransferData;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import org.joda.time.DateTime;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
/** An Epp entity object such as a contact or a host. */
@XmlTransient
public abstract class EppResource extends BackupGroupRoot implements Buildable, ResponseData {
/**
* Unique identifier in the registry for this resource.
*
* <p>This is in the (\w|_){1,80}-\w{1,8} format specified by RFC 5730 for roidType.
*/
@Id
@XmlElement(name = "roid")
String repoId;
/** The ID of the registrar that is currently sponsoring this resource. */
@Index
@XmlElement(name = "clID")
String currentSponsorClientId;
/** The ID of the registrar that created this resource. */
@XmlElement(name = "crID")
String creationClientId;
/**
* The ID of the registrar that last updated this resource.
*
* <p>This does not refer to the last delta made on this object, which might include out-of-band
* edits; it only includes EPP-visible modifications such as {@literal <update>}. Can be null if
* the resource has never been modified.
*/
@XmlElement(name = "upID")
String lastEppUpdateClientId;
/** The time when this resource was created. */
// Map the method to XML, not the field, because if we map the field (with an adaptor class) it
// will never be omitted from the xml even if the timestamp inside creationTime is null and we
// return null from the adaptor. (Instead it gets written as an empty tag.)
@XmlTransient
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
/**
* The time when this resource was or will be deleted.
*
* <ul>
* <li>For deleted resources, this is in the past.
* <li>For pending-delete resources, this is in the near future.
* <li>For active resources, this is {@code END_OF_TIME}.
* </ul>
*
* <p>This scheme allows for setting pending deletes in the future and having them magically drop
* out of the index at that time, as long as we query for resources whose deletion time is before
* now.
*/
@Index
@XmlTransient
DateTime deletionTime;
/**
* The time that this resource was last updated.
*
* <p>This does not refer to the last delta made on this object, which might include out-of-band
* edits; it only includes EPP-visible modifications such as {@literal <update>}. Can be null if
* the resource has never been modified.
*/
@XmlElement(name = "upDate")
DateTime lastEppUpdateTime;
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
// Map the method to XML, not the field, so subclasses can override it.
@XmlTransient
DateTime lastTransferTime;
/** Status values associated with this resource. */
Set<StatusValue> status;
/** Data about any pending or past transfers on this contact. */
@XmlTransient
TransferData transferData;
/**
* Sorted map of {@link DateTime} keys (modified time) to {@link CommitLogManifest} entries.
*
* <p><b>Note:</b> Only the last revision on a given date is stored. The key is the transaction
* timestamp, not midnight.
*
* @see com.google.domain.registry.model.translators.CommitLogRevisionsTranslatorFactory
*/
@XmlTransient
ImmutableSortedMap<DateTime, Ref<CommitLogManifest>> revisions = ImmutableSortedMap.of();
public final String getRepoId() {
return repoId;
}
@XmlElement(name = "crDate")
public final DateTime getCreationTime() {
return creationTime.getTimestamp();
}
public final String getCreationClientId() {
return creationClientId;
}
public final DateTime getLastEppUpdateTime() {
return lastEppUpdateTime;
}
public final String getLastEppUpdateClientId() {
return lastEppUpdateClientId;
}
public final String getCurrentSponsorClientId() {
return currentSponsorClientId;
}
public final ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(status);
}
public final TransferData getTransferData() {
return Optional.fromNullable(transferData).or(TransferData.EMPTY);
}
/** Returns whether there is any transferData. */
public final boolean hasTransferData() {
return transferData != null;
}
@XmlElement(name = "trDate")
public DateTime getLastTransferTime() {
return lastTransferTime;
}
public final DateTime getDeletionTime() {
return deletionTime;
}
public ImmutableSortedMap<DateTime, Ref<CommitLogManifest>> getRevisions() {
return nullToEmptyImmutableCopy(revisions);
}
/** Return a clone of the resource with timed status values modified using the given time. */
public abstract EppResource cloneProjectedAtTime(DateTime now);
/** Get the foreign key string for this resource. */
public abstract String getForeignKey();
/** Override of {@link Buildable#asBuilder} so that the extra methods are visible. */
@Override
public abstract Builder<?, ?> asBuilder();
/** EppResources that are loaded via foreign keys should implement this marker interface. */
public interface ForeignKeyedEppResource {}
/** Abstract builder for {@link EppResource} types. */
public abstract static class Builder<T extends EppResource, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
/** Create a {@link Builder} wrapping a new instance. */
protected Builder() {}
/** Create a {@link Builder} wrapping the given instance. */
protected Builder(T instance) {
super(instance);
}
/** Set the time this resource was created. Should only be used in tests. */
@VisibleForTesting
public B setCreationTimeForTest(DateTime creationTime) {
getInstance().creationTime = CreateAutoTimestamp.create(creationTime);
return thisCastToDerived();
}
/** Set the time after which this resource should be considered deleted. */
public B setDeletionTime(DateTime deletionTime) {
getInstance().deletionTime = deletionTime;
return thisCastToDerived();
}
/** Set the current sponsoring registrar. */
public B setCurrentSponsorClientId(String currentSponsorClientId) {
getInstance().currentSponsorClientId = currentSponsorClientId;
return thisCastToDerived();
}
/** Set the registrar that created this resource. */
public B setCreationClientId(String creationClientId) {
getInstance().creationClientId = creationClientId;
return thisCastToDerived();
}
/** Set the time when a {@literal <update>} was performed on this resource. */
public B setLastEppUpdateTime(DateTime lastEppUpdateTime) {
getInstance().lastEppUpdateTime = lastEppUpdateTime;
return thisCastToDerived();
}
/** Set the registrar who last performed a {@literal <update>} on this resource. */
public B setLastEppUpdateClientId(String lastEppUpdateClientId) {
getInstance().lastEppUpdateClientId = lastEppUpdateClientId;
return thisCastToDerived();
}
/** Set the time when this resource was transferred. */
public B setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return thisCastToDerived();
}
/** Set this resource's status values. */
public B setStatusValues(ImmutableSet<StatusValue> statusValues) {
getInstance().status = statusValues;
return thisCastToDerived();
}
/** Add to this resource's status values. */
public B addStatusValue(StatusValue statusValue) {
return addStatusValues(ImmutableSet.of(statusValue));
}
/** Remove from this resource's status values. */
public B removeStatusValue(StatusValue statusValue) {
return removeStatusValues(ImmutableSet.of(statusValue));
}
/** Add to this resource's status values. */
public B addStatusValues(ImmutableSet<StatusValue> statusValues) {
return setStatusValues(ImmutableSet.copyOf(
union(getInstance().getStatusValues(), statusValues)));
}
/** Remove from this resource's status values. */
public B removeStatusValues(ImmutableSet<StatusValue> statusValues) {
return setStatusValues(ImmutableSet.copyOf(
difference(getInstance().getStatusValues(), statusValues)));
}
/** Set this resource's transfer data. */
public B setTransferData(TransferData transferData) {
getInstance().transferData = transferData;
return thisCastToDerived();
}
/** Set this resource's repoId. */
public B setRepoId(String repoId) {
getInstance().repoId = repoId;
return thisCastToDerived();
}
/** Wipe out any personal information in the resource. */
public B wipeOut() {
return thisCastToDerived();
}
/** Build the resource, nullifying empty strings and sets and setting defaults. */
@Override
public T build() {
// An EPP object has an implicit status of OK if no pending operations or prohibitions exist
// (i.e. no other status value besides LINKED is present).
removeStatusValue(StatusValue.OK);
if (difference(getInstance().getStatusValues(), StatusValue.LINKED).isEmpty()) {
addStatusValue(StatusValue.OK);
}
return buildWithoutImplicitStatusValues();
}
/** Build the resource, nullifying empty strings and sets and setting defaults. */
public T buildWithoutImplicitStatusValues() {
// If TransferData is totally empty, set it to null.
if (TransferData.EMPTY.equals(getInstance().transferData)) {
setTransferData(null);
}
// If there is no deletion time, set it to END_OF_TIME.
setDeletionTime(Optional.fromNullable(getInstance().deletionTime).or(END_OF_TIME));
return ImmutableObject.cloneEmptyToNull(super.build());
}
}
}

View file

@ -0,0 +1,407 @@
// 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 com.google.domain.registry.model;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.transform;
import static com.google.domain.registry.model.RoidSuffixes.getRoidSuffixForTld;
import static com.google.domain.registry.model.index.ForeignKeyIndex.loadAndGetReference;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.DateTimeUtils.isAtOrAfter;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import static com.google.domain.registry.util.DateTimeUtils.latestOf;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.model.EppResource.Builder;
import com.google.domain.registry.model.EppResource.ForeignKeyedEppResource;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.DomainBase;
import com.google.domain.registry.model.domain.ReferenceUnion;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.model.index.ForeignKeyIndex;
import com.google.domain.registry.model.ofy.CommitLogManifest;
import com.google.domain.registry.model.ofy.CommitLogMutation;
import com.google.domain.registry.model.transfer.TransferData;
import com.google.domain.registry.model.transfer.TransferStatus;
import com.google.domain.registry.util.FormattingLogger;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.util.ResultNow;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
/** Utilities for working with {@link EppResource}. */
public final class EppResourceUtils {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
/** Returns the full domain repoId of the format HEX-TLD for the specified long id and tld. */
public static String createDomainRoid(long repoId, String tld) {
return createRoid(repoId, getRoidSuffixForTld(tld));
}
/**
* Returns the full contact/host repoId of the format HEX-GOOGLE for the specified long repo id.
*/
public static String createContactHostRoid(long repoId) {
return createRoid(
repoId, RegistryEnvironment.get().config().getContactAndHostRepositoryIdentifier());
}
private static String createRoid(long repoId, String roidSuffix) {
// %X is uppercase hexadecimal.
return String.format("%X-%s", repoId, roidSuffix);
}
/** Helper to call {@link EppResource#cloneProjectedAtTime} without warnings. */
@SuppressWarnings("unchecked")
private static final <T extends EppResource> T cloneProjectedAtTime(T resource, DateTime now) {
return (T) resource.cloneProjectedAtTime(now);
}
/**
* Loads the last created version of an {@link EppResource} from the datastore by foreign key.
*
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
* created resource was deleted before time "now".
*
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
* it may have various expirable conditions and status values that might implicitly change its
* state as time progresses even if it has not been updated in the datastore. Rather, the
* resource must be combined with a timestamp to view its current state. We use a global last
* updated timestamp on the entire entity group (which is essentially free since all writes to
* the entity group must be serialized anyways) to guarantee monotonically increasing write
* times, so forwarding our projected time to the greater of "now", and this update timestamp
* guarantees that we're not projecting into the past.
*
* @param clazz the resource type to load
* @param foreignKey id to match
* @param now the current logical time to project resources at
*/
public static <T extends EppResource> T loadByUniqueId(
Class<T> clazz, String foreignKey, DateTime now) {
// For regular foreign-keyed resources, get the ref by loading the FKI; for domain applications,
// we can construct the ref directly, since the provided foreignKey is just the repoId.
Ref<T> resourceRef = ForeignKeyedEppResource.class.isAssignableFrom(clazz)
? loadAndGetReference(clazz, foreignKey, now)
: Ref.create(Key.create(null, clazz, foreignKey));
if (resourceRef == null) {
return null;
}
T resource = resourceRef.get();
if (resource == null
// You'd think this couldn't happen, but it can. For polymorphic entities, a Ref or Key is
// of necessity a reference to the base type (since datastore doesn't have polymorphism and
// Objectify is faking it). In the non-foreign-key code path above where we directly create
// a Ref, there is no way to know whether the Ref points to an instance of the desired
// subclass without loading it. Due to type erasure, it gets stuffed into "resource" without
// causing a ClassCastException even if it's the wrong type until you actually try to use it
// as the wrong type, at which point it blows up somewhere else in the code. Concretely,
// this means that without this line bad things would happen if you tried to load a
// DomainApplication using the id of a DomainResource (but not vice versa).
|| !clazz.isInstance(resource)
|| isAtOrAfter(now, resource.getDeletionTime())) {
return null;
}
// When setting status values based on a time, choose the greater of "now" and the resource's
// UpdateAutoTimestamp. For non-mutating uses (info, whois, etc.), this is equivalent to rolling
// "now" forward to at least the last update on the resource, so that a read right after a write
// doesn't appear stale. For mutating flows, if we had to roll now forward then the flow will
// fail when it tries to save anything via Ofy, since "now" is needed to be > the last update
// time for writes.
return cloneProjectedAtTime(
resource,
latestOf(now, resource.getUpdateAutoTimestamp().getTimestamp()));
}
/** Loads returns the hosts specified by the given ReferenceUnions. */
public static ImmutableSet<HostResource> loadReferencedNameservers(
Set<ReferenceUnion<HostResource>> hostRefs) {
ImmutableSet.Builder<HostResource> builder = new ImmutableSet.Builder<>();
for (ReferenceUnion<HostResource> hostRef : hostRefs) {
HostResource host = hostRef.getLinked().get();
if (host != null) {
builder.add(host);
}
}
return builder.build();
}
/** Loads and returns the contacts specified by the given ReferenceUnions. */
public static ImmutableSet<ContactResource> loadReferencedContacts(
Set<ReferenceUnion<ContactResource>> contactRefs) {
ImmutableSet.Builder<ContactResource> builder = new ImmutableSet.Builder<>();
for (ReferenceUnion<ContactResource> contactRef : contactRefs) {
builder.add(contactRef.getLinked().get());
}
return builder.build();
}
/**
* Checks multiple {@link EppResource} objects from the datastore by unique ids.
* <p>
* There are currently no resources that support checks and do not use foreign keys. If we need to
* support that case in the future, we can loosen the type to allow any {@link EppResource} and
* add code to do the lookup by id directly.
*
* @param clazz the resource type to load
* @param uniqueIds a list of ids to match
* @param now the logical time of the check
*/
public static <T extends EppResource> Set<String> checkResourcesExist(
Class<T> clazz, List<String> uniqueIds, final DateTime now) {
return ForeignKeyIndex.load(clazz, uniqueIds, now).keySet();
}
/**
* Loads resources that match some filter and that have {@link EppResource#deletionTime} that is
* not before "now".
*
* <p>This is an eventually consistent query.
*
* @param clazz the resource type to load
* @param now the logical time of the check
* @param filterDefinition the filter to apply when loading resources
* @param filterValue the acceptable value for the filter
*/
public static <T extends EppResource> Iterable<T> queryNotDeleted(
Class<T> clazz, DateTime now, String filterDefinition, Object filterValue) {
return transform(
ofy().load().type(clazz)
.filter(filterDefinition, filterValue)
.filter("deletionTime >", now.toDate()),
EppResourceUtils.<T>transformAtTime(now));
}
/**
* Returns a Function that transforms an EppResource to the given DateTime, suitable for use with
* Iterables.transform() over a collection of EppResources.
*/
public static <T extends EppResource> Function<T, T> transformAtTime(final DateTime now) {
return new Function<T, T>() {
@Override
public T apply(T resource) {
return cloneProjectedAtTime(resource, now);
}};
}
/**
* The lifetime of a resource is from its creation time, inclusive, through its deletion time,
* exclusive, which happily maps to the behavior of Interval.
*/
private static Interval getLifetime(EppResource resource) {
return new Interval(resource.getCreationTime(), resource.getDeletionTime());
}
public static boolean isActive(EppResource resource, DateTime time) {
return getLifetime(resource).contains(time);
}
public static boolean isDeleted(EppResource resource, DateTime time) {
return !isActive(resource, time);
}
/** Process an automatic transfer on a resource. */
public static void setAutomaticTransferSuccessProperties(
Builder<?, ?> builder, TransferData transferData) {
checkArgument(TransferStatus.PENDING.equals(transferData.getTransferStatus()));
builder.removeStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData(transferData.asBuilder()
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.setServerApproveEntities(null)
.setServerApproveBillingEvent(null)
.setServerApproveAutorenewEvent(null)
.setServerApproveAutorenewPollMessage(null)
.build())
.setLastTransferTime(transferData.getPendingTransferExpirationTime())
.setCurrentSponsorClientId(transferData.getGainingClientId());
}
/**
* Perform common operations for projecting an {@link EppResource} at a given time:
* <ul>
* <li>Process an automatic transfer.
* </ul>
*/
public static <T extends EppResource> void projectResourceOntoBuilderAtTime(
T resource, Builder<?, ?> builder, DateTime now) {
TransferData transferData = resource.getTransferData();
// If there's a pending transfer that has expired, process it.
DateTime expirationTime = transferData.getPendingTransferExpirationTime();
if (TransferStatus.PENDING.equals(transferData.getTransferStatus())
&& isBeforeOrAt(expirationTime, now)) {
setAutomaticTransferSuccessProperties(builder, transferData);
}
}
/**
* Rewinds an {@link EppResource} object to a given point in time.
*
* <p>This method costs nothing if {@code resource} is already current. Otherwise it needs to
* perform a single asynchronous key fetch operation.
*
* <p><b>Warning:</b> A resource can only be rolled backwards in time, not forwards; therefore
* {@code resource} should be whatever's currently in datastore.
*
* <p><b>Warning:</b> Revisions are granular to 24-hour periods. It's recommended that
* {@code timestamp} be set to midnight. Otherwise you must take into consideration that under
* certain circumstances, a resource might be restored to a revision on the previous day, even if
* there were revisions made earlier on the same date as {@code timestamp}; however, a resource
* will never be restored to a revision occuring after {@code timestamp}. This behavior is due to
* the way {@link com.google.domain.registry.model.translators.CommitLogRevisionsTranslatorFactory
* CommitLogRevisionsTranslatorFactory} manages the {@link EppResource#revisions} field. Please
* note however that the creation and deletion times of a resource are granular to the
* millisecond.
*
* @return an asynchronous operation returning resource at {@code timestamp} or {@code null} if
* if resource is deleted or not yet created
*/
public static <T extends EppResource>
Result<T> loadAtPointInTime(final T resource, final DateTime timestamp) {
// If we're before the resource creation time, don't try to find a "most recent revision".
if (timestamp.isBefore(resource.getCreationTime())) {
return new ResultNow<>(null);
}
// If the resource was not modified after the requested time, then use it as-is, otherwise find
// the most recent revision asynchronously, and return an async result that wraps that revision
// and returns it projected forward to exactly the desired timestamp, or null if the resource is
// deleted at that timestamp.
final Result<T> loadResult =
(isAtOrAfter(timestamp, resource.getUpdateAutoTimestamp().getTimestamp()))
? new ResultNow<>(resource)
: loadMostRecentRevisionAtTime(resource, timestamp);
return new Result<T>() {
@Override
public T now() {
T loadedResource = loadResult.now();
return loadedResource == null ? null
: (isActive(loadedResource, timestamp)
? cloneProjectedAtTime(loadedResource, timestamp)
: null);
}};
}
/**
* Returns an asynchronous result holding the most recent datastore revision of a given
* EppResource before or at the provided timestamp using the EppResource revisions map, falling
* back to using the earliest revision or the resource as-is if there are no revisions.
*
* @see #loadAtPointInTime(EppResource, DateTime)
*/
private static <T extends EppResource> Result<T> loadMostRecentRevisionAtTime(
final T resource, final DateTime timestamp) {
final Key<T> resourceKey = Key.create(resource);
final Ref<CommitLogManifest> revision = findMostRecentRevisionAtTime(resource, timestamp);
if (revision == null) {
logger.severefmt("No revision found for %s, falling back to resource.", resourceKey);
return new ResultNow<>(resource);
}
final Result<CommitLogMutation> mutationResult =
ofy().load().key(CommitLogMutation.createKey(revision.getKey(), resourceKey));
return new Result<T>() {
@Override
public T now() {
CommitLogMutation mutation = mutationResult.now();
if (mutation != null) {
return ofy().load().fromEntity(mutation.getEntity());
}
logger.severefmt(
"Couldn't load mutation for revision at %s for %s, falling back to resource."
+ " Revision: %s",
timestamp, resourceKey, revision);
return resource;
}
};
}
@Nullable
private static <T extends EppResource> Ref<CommitLogManifest>
findMostRecentRevisionAtTime(final T resource, final DateTime timestamp) {
final Key<T> resourceKey = Key.create(resource);
Entry<?, Ref<CommitLogManifest>> revision = resource.getRevisions().floorEntry(timestamp);
if (revision != null) {
logger.infofmt("Found revision history at %s for %s: %s", timestamp, resourceKey, revision);
return revision.getValue();
}
// Fall back to the earliest revision if we don't have one before the requested timestamp.
revision = resource.getRevisions().firstEntry();
if (revision != null) {
logger.severefmt("Found no revision history at %s for %s, using earliest revision: %s",
timestamp, resourceKey, revision);
return revision.getValue();
}
// Ultimate fallback: There are no revisions whatsoever, so return null.
logger.severefmt("Found no revision history at all for %s", resourceKey);
return null;
}
/**
* Find keys of domains or applications that reference a specified contact or host.
*
* <p>This is an eventually consistent query.
*
* @param clazz the referent type (contact or host)
* @param ref the referent key
* @param now the logical time of the check
* @param limit max number of keys to return
*/
public static List<Key<DomainBase>> queryDomainsUsingResource(
Class<? extends EppResource> clazz, Ref<? extends EppResource> ref, DateTime now, int limit) {
checkArgument(ContactResource.class.equals(clazz) || HostResource.class.equals(clazz));
return ofy().load().type(DomainBase.class)
.filter(
clazz.equals(ContactResource.class)
? "allContacts.contactId.linked"
: "nameservers.linked",
ref)
.filter("deletionTime >", now)
.limit(limit)
.keys()
.list();
}
/** Clone a contact or host with an eventually-consistent notion of LINKED. */
public static EppResource cloneResourceWithLinkedStatus(EppResource resource, DateTime now) {
Builder<?, ?> builder = resource.asBuilder();
if (queryDomainsUsingResource(resource.getClass(), Ref.create(resource), now, 1).isEmpty()) {
builder.removeStatusValue(StatusValue.LINKED);
} else {
builder.addStatusValue(StatusValue.LINKED);
}
return builder.build();
}
/** Exception to throw when failing to parse a repo id. */
public static class InvalidRepoIdException extends Exception {
public InvalidRepoIdException(String message) {
super(message);
}
}
private EppResourceUtils() {}
}

View file

@ -0,0 +1,167 @@
// 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 com.google.domain.registry.model;
import static com.google.common.base.Functions.identity;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Maps.transformValues;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps;
import com.google.domain.registry.model.domain.ReferenceUnion;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Ignore;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import javax.annotation.concurrent.Immutable;
import javax.xml.bind.annotation.XmlTransient;
/** An immutable object that implements {@link #equals}, {@link #hashCode} and {@link #toString}. */
@Immutable
@XmlTransient
public abstract class ImmutableObject implements Cloneable {
@Ignore
@XmlTransient
Integer hashCode;
private boolean equalsImmutableObject(ImmutableObject other) {
return getClass().equals(other.getClass())
&& hashCode() == other.hashCode()
&& ModelUtils.getFieldValues(this).equals(ModelUtils.getFieldValues(other));
}
@Override
public boolean equals(Object other) {
return other instanceof ImmutableObject && equalsImmutableObject((ImmutableObject) other);
}
@Override
public int hashCode() {
if (hashCode == null) {
hashCode = Arrays.hashCode(ModelUtils.getFieldValues(this).values().toArray());
}
return hashCode;
}
/** Returns a clone of the given object. */
@SuppressWarnings("unchecked")
protected static <T extends ImmutableObject> T clone(T t) {
try {
T clone = (T) t.clone();
// Clear the hashCode since we often mutate clones before handing them out.
clone.hashCode = null;
return clone;
} catch (CloneNotSupportedException e) { // Yes it is.
throw new IllegalStateException();
}
}
/** Returns a clone of the given object with empty fields set to null. */
protected static <T extends ImmutableObject> T cloneEmptyToNull(T t) {
return ModelUtils.cloneEmptyToNull(t);
}
/**
* Returns a string view of the object, formatted like:
*
* <pre>
* ModelObject (@12345): {
* field1=value1
* field2=[a,b,c]
* field3=AnotherModelObject: {
* foo=bar
* }
* }
* </pre>
*/
@Override
public String toString() {
return toStringHelper(identity());
}
/**
* Similar to toString(), with a full expansion of embedded ImmutableObjects,
* collections, and references.
*/
public String toHydratedString() {
return toStringHelper(new Function<Object, Object>() {
@Override
public Object apply(Object input) {
if (input instanceof ReferenceUnion) {
return apply(((ReferenceUnion<?>) input).getLinked().get());
} else if (input instanceof Ref) {
// Only follow references of type Ref, not of type Key (the latter deliberately used for
// references that should not be followed)
return apply(((Ref<?>) input).get());
} else if (input instanceof Map) {
return transformValues((Map<?, ?>) input, this);
} else if (input instanceof Collection) {
return transform((Collection<?>) input, this);
} else if (input instanceof ImmutableObject) {
return ((ImmutableObject) input).toHydratedString();
}
return input;
}});
}
public String toStringHelper(Function<Object, Object> transformation) {
Map<String, Object> sortedFields = Maps.newTreeMap();
sortedFields.putAll(
transformValues(ModelUtils.getFieldValues(this), transformation));
return String.format(
"%s (@%s): {\n%s",
getClass().getSimpleName(),
System.identityHashCode(this),
Joiner.on('\n').join(sortedFields.entrySet()))
.replaceAll("\n", "\n ") + "\n}";
}
/** Helper function to recursively convert a ImmutableObject to a Map of generic objects. */
private static final Function<Object, Object> TO_MAP_HELPER = new Function<Object, Object>() {
@Override
public Object apply(Object o) {
if (o == null) {
return null;
} else if (o instanceof ImmutableObject) {
Map<String, Object> result =
Maps.transformValues(ModelUtils.getFieldValues(o), this);
return result;
} else if (o instanceof Map) {
return Maps.transformValues((Map<?, ?>) o, this);
} else if (o instanceof Set) {
return FluentIterable.from((Set<?>) o).transform(this).toSet();
} else if (o instanceof Collection) {
return FluentIterable.from((Collection<?>) o).transform(this).toList();
} else if (o instanceof Number || o instanceof Boolean) {
return o;
} else {
return o.toString();
}
}};
/** Returns a map of all object fields (including sensitive data) that's used to produce diffs. */
@SuppressWarnings("unchecked")
public Map<String, Object> toDiffableFieldMap() {
return (Map<String, Object>) TO_MAP_HELPER.apply(this);
}
}

View file

@ -0,0 +1,91 @@
// 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 com.google.domain.registry.model;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.FluentIterable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Helper class for {@link Jsonifiable} classes to generate JSON maps for RPC responses.
*
* <p>The returned map is mutable. Map entries can be {@code null} but list entries can not. If a
* list is passed as {@code null}, it'll be substituted with empty list. Lists are not mutable.
*/
public final class JsonMapBuilder {
private static final Function<Jsonifiable, Map<String, Object>> TO_JSON_OBJECT =
new Function<Jsonifiable, Map<String, Object>>() {
@Override
public Map<String, Object> apply(Jsonifiable input) {
return input.toJsonMap();
}};
private final Map<String, Object> map = new LinkedHashMap<>();
public JsonMapBuilder put(String name, @Nullable Boolean value) {
map.put(name, value);
return this;
}
public JsonMapBuilder put(String name, @Nullable Number value) {
map.put(name, value);
return this;
}
public JsonMapBuilder put(String name, @Nullable String value) {
map.put(name, value);
return this;
}
public JsonMapBuilder put(String name, @Nullable Jsonifiable value) {
map.put(name, value == null ? null : value.toJsonMap());
return this;
}
public JsonMapBuilder put(String name, @Nullable Enum<?> value) {
map.put(name, value == null ? null : value.name());
return this;
}
public <T> JsonMapBuilder putString(String name, @Nullable T value) {
map.put(name, value == null ? null : value.toString());
return this;
}
public <T> JsonMapBuilder putListOfStrings(String name, @Nullable Iterable<T> value) {
map.put(name, value == null ? Collections.EMPTY_LIST
: FluentIterable.from(value).transform(Functions.toStringFunction()).toList());
return this;
}
public JsonMapBuilder putListOfJsonObjects(
String name, @Nullable Iterable<? extends Jsonifiable> value) {
map.put(name, value == null ? Collections.EMPTY_LIST
: FluentIterable.from(value).transform(TO_JSON_OBJECT).toList());
return this;
}
/** Returns mutable JSON object. Please dispose of the builder object after calling me. */
public Map<String, Object> build() {
return map;
}
}

View file

@ -0,0 +1,29 @@
// 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 com.google.domain.registry.model;
import java.util.Map;
/** Interface for objects that may be converted to JSON. */
public interface Jsonifiable {
/**
* Returns a JSON representation of this object.
*
* <p>The returned value must not return sensitive fields, so that it may be safe to return to
* the client via an API response.
*/
Map<String, Object> toJsonMap();
}

View file

@ -0,0 +1,296 @@
// 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 com.google.domain.registry.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.base.Predicates.or;
import static com.google.common.collect.Iterables.all;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.transformValues;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static java.util.Arrays.asList;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Parent;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.AbstractList;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/** A collection of static methods that deal with reflection on model classes. */
public class ModelUtils {
/** Caches all instance fields on an object, including non-public and inherited fields. */
private static final LoadingCache<Class<?>, ImmutableMap<String, Field>> ALL_FIELDS_CACHE =
CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, ImmutableMap<String, Field>>() {
@Override
public ImmutableMap<String, Field> load(Class<?> clazz) {
Deque<Class<?>> hierarchy = new LinkedList<>();
// Walk the hierarchy up to but not including ImmutableObject (to ignore hashCode).
for (; clazz != ImmutableObject.class; clazz = clazz.getSuperclass()) {
// Add to the front, so that shadowed fields show up later in the list.
// This will mean that getFieldValues will show the most derived value.
hierarchy.addFirst(clazz);
}
Map<String, Field> fields = new LinkedHashMap<>();
for (Class<?> hierarchyClass : hierarchy) {
Package pakkage = hierarchyClass.getPackage();
// Don't use hierarchyClass.getFields() because it only picks up public fields.
for (Field field : hierarchyClass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
// Strictly speaking this shouldn't be necessary since all of these fields
// are already accessible to their FieldExposer, but it is more performant
// to access fields if they are marked accessible this way because it skips
// various security checks.
checkNotNull(
FIELD_EXPOSERS.get(pakkage),
"No FieldExposer registered for %s", pakkage.getName())
.setAccessible(field);
fields.put(field.getName(), field);
}
}
return ImmutableMap.copyOf(fields);
}});
/** Per-package trampolines to expose package-private fields for reflection. */
private static final Map<Package, AbstractFieldExposer> FIELD_EXPOSERS = Maps.uniqueIndex(
FieldExposerRegistry.getFieldExposers(),
new Function<AbstractFieldExposer, Package>() {
@Override
public Package apply(AbstractFieldExposer exposer) {
return exposer.getClass().getPackage();
}});
/** Lists all instance fields on an object, including non-public and inherited fields. */
static Map<String, Field> getAllFields(Class<?> clazz) {
return ALL_FIELDS_CACHE.getUnchecked(clazz);
}
/** Return a string representing the persisted schema of a type or enum. */
static String getSchema(Class<?> clazz) {
StringBuilder stringBuilder = new StringBuilder();
Iterable<?> body;
if (clazz.isEnum()) {
stringBuilder.append("enum ");
body = FluentIterable.from(asList(clazz.getEnumConstants()));
} else {
stringBuilder.append("class ");
body = FluentIterable.from(getAllFields(clazz).values())
.filter(new Predicate<Field>() {
@Override
public boolean apply(Field field) {
return !field.isAnnotationPresent(Ignore.class);
}})
.transform(new Function<Field, Object>() {
@Override
public Object apply(Field field) {
String annotation = field.isAnnotationPresent(Id.class)
? "@Id "
: field.isAnnotationPresent(Parent.class)
? "@Parent "
: "";
String type = field.getType().isArray()
? field.getType().getComponentType().getName() + "[]"
: field.getGenericType().toString().replaceFirst("class ", "");
return String.format("%s%s %s", annotation, type, field.getName());
}});
}
return stringBuilder
.append(clazz.getName()).append(" {\n ")
.append(Joiner.on(";\n ").join(Ordering.usingToString().sortedCopy(body)))
.append(";\n}")
.toString();
}
/**
* Returns the set of Class objects of all persisted fields. This includes the parameterized
* type(s) of any fields (if any).
*/
static Set<Class<?>> getPersistedFieldTypes(Class<?> clazz) {
ImmutableSet.Builder<Class<?>> builder = new ImmutableSet.Builder<>();
for (Field field : getAllFields(clazz).values()) {
// Skip fields that aren't persisted to datastore.
if (field.isAnnotationPresent(Ignore.class)) {
continue;
}
// If the field's type is the same as the field's class object, then it's a non-parameterized
// type, and thus we just add it directly. We also don't bother looking at the parameterized
// types of Key and Ref objects, since they are just references to other objects and don't
// actual embed themselves in the persisted object anyway.
Class<?> fieldClazz = field.getType();
Type fieldType = field.getGenericType();
builder.add(fieldClazz);
if (fieldType.equals(fieldClazz) || Ref.class.equals(clazz) || Key.class.equals(clazz)) {
continue;
}
// If the field is a parameterized type, then also add the parameterized field.
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
for (Type actualType : parameterizedType.getActualTypeArguments()) {
if (actualType instanceof Class<?>) {
builder.add((Class<?>) actualType);
} else {
// We intentionally ignore types that are parameterized on non-concrete types. In theory
// we could have collections embedded within collections, but Objectify does not allow
// that.
}
}
}
}
return builder.build();
}
/** Retrieves a field value via reflection. */
static Object getFieldValue(Object instance, Field field) {
try {
return Preconditions.checkNotNull(
FIELD_EXPOSERS.get(field.getDeclaringClass().getPackage()),
"No FieldExposer registered for %s", field.getDeclaringClass().getPackage().getName())
.getFieldValue(instance, field);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
/** Sets a field value via reflection. */
static void setFieldValue(Object instance, Field field, Object value) {
try {
Preconditions.checkNotNull(
FIELD_EXPOSERS.get(field.getDeclaringClass().getPackage()),
"No FieldExposer registered for %s", field.getDeclaringClass().getPackage().getName())
.setFieldValue(instance, field, value);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
/**
* Returns a map from field names (including non-public and inherited fields) to values.
* <p>
* This turns arrays into {@link List} objects so that ImmutableObject can more easily use the
* returned map in its implementation of {@link ImmutableObject#toString} and
* {@link ImmutableObject#equals}, which work by comparing and printing these maps.
*/
static Map<String, Object> getFieldValues(Object instance) {
// Don't make this ImmutableMap because field values can be null.
Map<String, Object> values = new LinkedHashMap<>();
for (Field field : getAllFields(instance.getClass()).values()) {
Object value = getFieldValue(instance, field);
if (value != null && value.getClass().isArray()) {
// It's surprisingly difficult to convert arrays into lists if the array might be primitive.
final Object arrayValue = value;
value = new AbstractList<Object>() {
@Override
public Object get(int index) {
return Array.get(arrayValue, index);
}
@Override
public int size() {
return Array.getLength(arrayValue);
}};
}
values.put(field.getName(), value);
}
return values;
}
/** Functional helper for {@link #cloneEmptyToNull}. */
private static final Function<Object, ?> CLONE_EMPTY_TO_NULL = new Function<Object, Object>() {
@Override
public Object apply(Object obj) {
if (obj instanceof ImmutableSortedMap) {
// ImmutableSortedMapTranslatorFactory handles empty for us. If the object is null, then
// its on-save hook can't run.
return obj;
}
if ("".equals(obj)
|| (obj instanceof Collection && ((Collection<?>) obj).isEmpty())
|| (obj instanceof Map && ((Map<?, ?>) obj).isEmpty())
|| (obj != null && obj.getClass().isArray() && Array.getLength(obj) == 0)) {
return null;
}
Predicate<Object> immutableObjectOrNull = or(isNull(), instanceOf(ImmutableObject.class));
if ((obj instanceof Set || obj instanceof List)
&& all((Iterable<?>) obj, immutableObjectOrNull)) {
// Recurse into sets and lists, but only if they contain ImmutableObjects.
FluentIterable<?> fluent = FluentIterable.from((Iterable<?>) obj).transform(this);
return (obj instanceof List) ? newArrayList(fluent) : newLinkedHashSet(fluent);
}
if (obj instanceof Map && all(((Map<?, ?>) obj).values(), immutableObjectOrNull)) {
// Recurse into maps with ImmutableObject values.
return transformValues((Map<?, ?>) obj, this);
}
if (obj instanceof ImmutableObject) {
// Recurse on the fields of an ImmutableObject.
ImmutableObject copy = ImmutableObject.clone((ImmutableObject) obj);
for (Field field : getAllFields(obj.getClass()).values()) {
Object oldValue = getFieldValue(obj, field);
Object newValue = apply(oldValue);
if (!Objects.equals(oldValue, newValue)) {
setFieldValue(copy, field, newValue);
}
}
return copy;
}
return obj;
}};
/** Returns a clone of the object and sets empty collections, arrays, maps and strings to null. */
@SuppressWarnings("unchecked")
protected static <T extends ImmutableObject> T cloneEmptyToNull(T obj) {
return (T) CLONE_EMPTY_TO_NULL.apply(obj);
}
@VisibleForTesting
static void resetCaches() {
ALL_FIELDS_CACHE.invalidateAll();
}
}

View file

@ -0,0 +1,65 @@
// 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 com.google.domain.registry.model;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.CacheUtils.memoizeWithShortExpiration;
import com.google.common.base.Supplier;
import com.google.common.collect.HashBiMap;
import com.google.domain.registry.model.registry.Registry;
import com.googlecode.objectify.Work;
/** Utility class for dealing with EPP ROID suffixes. */
public final class RoidSuffixes {
private static Supplier<HashBiMap<String, String>> roidSuffixMapCache =
memoizeWithShortExpiration(new Supplier<HashBiMap<String, String>>() {
@Override
public HashBiMap<String, String> get() {
return ofy().doTransactionless(new Work<HashBiMap<String, String>>() {
@Override
public HashBiMap<String, String> run() {
HashBiMap<String, String> bimap = HashBiMap.create();
for (Registry registry :
ofy().load().type(Registry.class).ancestor(getCrossTldKey()).list()) {
bimap.put(registry.getTldStr(), registry.getRoidSuffix());
}
return bimap;
}
});
}
});
/**
* Returns the roid suffix corresponding to the given tld using the per-tld roidSuffix field.
*
* @throws IllegalStateException if there is no such tld, or the tld does not have a roid suffix
* configured on it
*/
public static String getRoidSuffixForTld(String tld) {
String roidSuffix = roidSuffixMapCache.get().get(tld);
checkState(roidSuffix != null, "Could not find ROID suffix for TLD %s", tld);
return roidSuffix;
}
public static boolean isRoidSuffixUsed(String roidSuffix) {
return roidSuffixMapCache.get().containsValue(roidSuffix);
}
}

View file

@ -0,0 +1,75 @@
// 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 com.google.domain.registry.model;
import static com.google.common.base.Predicates.assignableFrom;
import static com.google.common.base.Predicates.or;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Ordering;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;
/** Utility methods for getting the version of the model schema from the model code. */
public final class SchemaVersion {
/**
* Returns a set of classes corresponding to all types persisted within the model classes, sorted
* by the string representation.
*/
private static SortedSet<Class<?>> getAllPersistedTypes() {
SortedSet<Class<?>> persistedTypes = new TreeSet<>(Ordering.usingToString());
Queue<Class<?>> queue = new ArrayDeque<>();
// Do a breadth-first search for persisted types, starting with @Entity types and expanding each
// ImmutableObject by querying it for all its persisted field types.
persistedTypes.addAll(EntityClasses.ALL_CLASSES);
queue.addAll(persistedTypes);
while (!queue.isEmpty()) {
Class<?> clazz = queue.remove();
if (ImmutableObject.class.isAssignableFrom(clazz)) {
for (Class<?> persistedFieldType : ModelUtils.getPersistedFieldTypes(clazz)) {
if (persistedTypes.add(persistedFieldType)) {
// If we haven't seen this type before, add it to the queue to query its field types.
queue.add(persistedFieldType);
}
}
}
}
return persistedTypes;
}
/**
* Return a string representing the schema which includes the definition of all persisted entity
* types (and their field types, recursively). Each definition contains the field names and their
* types (for classes), or else a list of all possible values (for enums).
*/
public static String getSchema() {
return FluentIterable.from(getAllPersistedTypes())
.filter(or(assignableFrom(Enum.class), assignableFrom(ImmutableObject.class)))
.transform(new Function<Class<?>, String>() {
@Override
public String apply(Class<?> clazz) {
return ModelUtils.getSchema(clazz);
}})
.join(Joiner.on('\n'));
}
private SchemaVersion() {}
}

View file

@ -0,0 +1,43 @@
// 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 com.google.domain.registry.model;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.base.Optional;
import com.google.domain.registry.model.translators.UpdateAutoTimestampTranslatorFactory;
import org.joda.time.DateTime;
/**
* A timestamp that auto-updates on each save to datastore.
*
* @see UpdateAutoTimestampTranslatorFactory
*/
public class UpdateAutoTimestamp extends ImmutableObject {
DateTime timestamp;
/** Returns the timestamp, or {@link #START_OF_TIME} if it's null. */
public DateTime getTimestamp() {
return Optional.fromNullable(timestamp).or(START_OF_TIME);
}
public static UpdateAutoTimestamp create(DateTime timestamp) {
UpdateAutoTimestamp instance = new UpdateAutoTimestamp();
instance.timestamp = timestamp;
return instance;
}
}

View file

@ -0,0 +1,27 @@
// 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 com.google.domain.registry.model.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to provide a name for a class to use in external error messages. */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ExternalMessagingName {
String value();
}

View file

@ -0,0 +1,47 @@
// 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 com.google.domain.registry.model.annotations;
import com.googlecode.objectify.annotation.Entity;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for an Objectify {@link Entity} to indicate that it should not be backed up by the
* default datastore backup configuration (it may be backed up by something else).
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NotBackedUp {
Reason reason();
/** Reasons why a given entity does not need to be be backed up. */
public enum Reason {
/** This entity is transient by design and has only a short-term useful lifetime. */
TRANSIENT,
/** This entity's data is already regularly pulled down from an external source. */
EXTERNALLY_SOURCED,
/** This entity is generated automatically by the app and will be recreated if need be. */
AUTO_GENERATED,
/** Commit log entities are exported separately from the regular backups, by design. */
COMMIT_LOGS
}
}

View file

@ -0,0 +1,32 @@
// 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 com.google.domain.registry.model.annotations;
import com.googlecode.objectify.annotation.Entity;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for an Objectify {@link Entity} to indicate that it is a "virtual entity".
*
* <p>A virtual entity type exists only to define part of the parentage key hierarchy for its
* child entities, and is never actually persisted and thus has no fields besides its ID field.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface VirtualEntity {}

View file

@ -0,0 +1,560 @@
// 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 com.google.domain.registry.model.billing;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static com.google.domain.registry.util.CollectionUtils.union;
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.common.TimeOfYear;
import com.google.domain.registry.model.domain.GracePeriod;
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.model.transfer.TransferData.TransferServerApproveEntity;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.condition.IfNull;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.util.Objects;
import java.util.Set;
/** A billable event in a domain's lifecycle. */
public abstract class BillingEvent extends ImmutableObject
implements Buildable, TransferServerApproveEntity {
/** The reason for the bill. */
public enum Reason {
CREATE,
TRANSFER,
RENEW,
// TODO(b/27777398): Drop Reason.AUTO_RENEW after migration to Flag.AUTO_RENEW.
AUTO_RENEW,
RESTORE,
SERVER_STATUS,
ERROR
}
/** Set of flags that can be applied to billing events. */
public enum Flag {
ALLOCATION,
ANCHOR_TENANT,
AUTO_RENEW,
LANDRUSH,
SUNRISE,
/**
* This flag will be added to any {@link OneTime} events that are created via, e.g., an
* automated process to expand {@link Recurring} events.
*/
SYNTHETIC
}
/** Entity id. */
@Id
long id;
@Parent
Key<HistoryEntry> parent;
/** The registrar to bill. */
@Index
String clientId;
/** When this event was created. For recurring events, this is also the recurrence start time. */
@Index
DateTime eventTime;
/** The reason for the bill. */
Reason reason;
/** The fully qualified domain name of the domain that the bill is for. */
String targetId;
Set<Flag> flags;
public String getClientId() {
return clientId;
}
public DateTime getEventTime() {
return eventTime;
}
public long getId() {
return id;
}
public Reason getReason() {
return reason;
}
public String getTargetId() {
return targetId;
}
public Key<HistoryEntry> getParentKey() {
return parent;
}
public ImmutableSet<Flag> getFlags() {
return nullToEmptyImmutableCopy(flags);
}
/** Override Buildable.asBuilder() to give this method stronger typing. */
@Override
public abstract Builder<?, ?> asBuilder();
/** An abstract builder for {@link BillingEvent}. */
public abstract static class Builder<T extends BillingEvent, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
protected Builder() {}
protected Builder(T instance) {
super(instance);
}
public B setReason(Reason reason) {
getInstance().reason = reason;
return thisCastToDerived();
}
public B setId(Long id) {
getInstance().id = id;
return thisCastToDerived();
}
public B setClientId(String clientId) {
getInstance().clientId = clientId;
return thisCastToDerived();
}
public B setEventTime(DateTime eventTime) {
getInstance().eventTime = eventTime;
return thisCastToDerived();
}
public B setTargetId(String targetId) {
getInstance().targetId = targetId;
return thisCastToDerived();
}
public B setFlags(ImmutableSet<Flag> flags) {
getInstance().flags = flags;
return thisCastToDerived();
}
public B setParent(HistoryEntry parent) {
getInstance().parent = Key.create(parent);
return thisCastToDerived();
}
public B setParent(Key<HistoryEntry> parentKey) {
getInstance().parent = parentKey;
return thisCastToDerived();
}
@Override
public T build() {
T instance = getInstance();
checkNotNull(instance.reason);
checkNotNull(instance.clientId);
checkNotNull(instance.eventTime);
checkNotNull(instance.targetId);
checkNotNull(instance.parent);
return super.build();
}
}
/** A one-time billable event. */
@Entity
public static class OneTime extends BillingEvent {
/** The billable value. */
Money cost;
/** When the cost should be billed. */
@Index
DateTime billingTime;
/**
* The period in years of the action being billed for, if applicable, otherwise null.
* Used for financial reporting.
*/
@IgnoreSave(IfNull.class)
Integer periodYears = null;
public Money getCost() {
return cost;
}
public DateTime getBillingTime() {
return billingTime;
}
public Integer getPeriodYears() {
return periodYears;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for {@link OneTime} since it is immutable. */
public static class Builder extends BillingEvent.Builder<OneTime, Builder> {
public Builder() {}
private Builder(OneTime instance) {
super(instance);
}
public Builder setCost(Money cost) {
getInstance().cost = cost;
return this;
}
public Builder setPeriodYears(Integer periodYears) {
checkNotNull(periodYears);
checkArgument(periodYears > 0);
getInstance().periodYears = periodYears;
return this;
}
public Builder setBillingTime(DateTime billingTime) {
getInstance().billingTime = billingTime;
return this;
}
@Override
public OneTime build() {
OneTime instance = getInstance();
checkNotNull(instance.billingTime);
checkNotNull(instance.cost);
checkState(!instance.cost.isNegative(), "Costs should be non-negative.");
ImmutableSet<Reason> reasonsWithPeriods =
Sets.immutableEnumSet(Reason.CREATE, Reason.RENEW, Reason.TRANSFER);
checkState(
reasonsWithPeriods.contains(instance.reason) == (instance.periodYears != null),
"Period years must be set if and only if reason is CREATE, RENEW, or TRANSFER.");
return super.build();
}
}
}
/**
* A recurring billable event.
* <p>
* Unlike {@link OneTime} events, these do not store an explicit cost, since the cost of the
* recurring event might change and each time we bill for it we need to bill at the current cost,
* not the value that was in use at the time the recurrence was created.
*/
@Entity
public static class Recurring extends BillingEvent {
// TODO(b/27777398): Remove after migration is complete and Reason.AUTO_RENEW is removed.
@OnLoad
void setAutorenewFlag() {
if (Reason.AUTO_RENEW.equals(reason)) {
reason = Reason.RENEW;
flags = union(getFlags(), Flag.AUTO_RENEW);
}
}
/**
* The billing event recurs every year between {@link #eventTime} and this time on the
* [month, day, time] specified in {@link #recurrenceTimeOfYear}.
*/
@Index
DateTime recurrenceEndTime;
/**
* The eventTime recurs every year on this [month, day, time] between {@link #eventTime} and
* {@link #recurrenceEndTime}, inclusive of the start but not of the end.
* <p>
* This field is denormalized from {@link #eventTime} to allow for an efficient index, but it
* always has the same data as that field.
* <p>
* Note that this is a recurrence of the event time, not the billing time. The billing time can
* be calculated by adding the relevant grace period length to this date. The reason for this
* requirement is that the event time recurs on a {@link org.joda.time.Period} schedule (same
* day of year, which can be 365 or 366 days later) which is what {@link TimeOfYear} can model,
* whereas the billing time is a fixed {@link org.joda.time.Duration} later.
*/
@Index
TimeOfYear recurrenceTimeOfYear;
public DateTime getRecurrenceEndTime() {
return recurrenceEndTime;
}
public TimeOfYear getRecurrenceTimeOfYear() {
return recurrenceTimeOfYear;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for {@link Recurring} since it is immutable. */
public static class Builder extends BillingEvent.Builder<Recurring, Builder> {
public Builder() {}
private Builder(Recurring instance) {
super(instance);
}
public Builder setRecurrenceEndTime(DateTime recurrenceEndTime) {
getInstance().recurrenceEndTime = recurrenceEndTime;
return this;
}
@Override
public Recurring build() {
Recurring instance = getInstance();
checkNotNull(instance.eventTime);
checkNotNull(instance.reason);
instance.recurrenceTimeOfYear = TimeOfYear.fromDateTime(instance.eventTime);
instance.recurrenceEndTime =
Optional.fromNullable(instance.recurrenceEndTime).or(END_OF_TIME);
return super.build();
}
}
}
/**
* An event representing a cancellation of one of the other two billable event types.
* <p>
* This is implemented as a separate event rather than a bit on BillingEvent in order to preserve
* the immutability of billing events.
*/
@Entity
public static class Cancellation extends BillingEvent {
/** The billing time of the charge that is being cancelled. */
@Index
DateTime billingTime;
/** The one-time billing event to cancel, or null for autorenew cancellations. */
@IgnoreSave(IfNull.class)
Ref<BillingEvent.OneTime> refOneTime = null;
/** The recurring billing event to cancel, or null for non-autorenew cancellations. */
@IgnoreSave(IfNull.class)
Ref<BillingEvent.Recurring> refRecurring = null;
public DateTime getBillingTime() {
return billingTime;
}
public Ref<? extends BillingEvent> getEventRef() {
return firstNonNull(refOneTime, refRecurring);
}
/** The mapping from billable grace period types to originating billing event reasons. */
static final ImmutableMap<GracePeriodStatus, Reason> GRACE_PERIOD_TO_REASON =
ImmutableMap.of(
GracePeriodStatus.ADD, Reason.CREATE,
GracePeriodStatus.AUTO_RENEW, Reason.RENEW,
GracePeriodStatus.RENEW, Reason.RENEW,
GracePeriodStatus.TRANSFER, Reason.TRANSFER);
/**
* Creates a cancellation billing event (parented on the provided history entry, and with the
* history entry's event time) that will cancel out the provided grace period's billing event,
* using the supplied targetId and deriving other metadata (clientId, billing time, and the
* cancellation reason) from the grace period.
*/
public static BillingEvent.Cancellation forGracePeriod(
GracePeriod gracePeriod, HistoryEntry historyEntry, String targetId) {
checkArgument(gracePeriod.hasBillingEvent(),
"Cannot create cancellation for grace period without billing event");
BillingEvent.Cancellation.Builder builder = new BillingEvent.Cancellation.Builder()
.setReason(checkNotNull(GRACE_PERIOD_TO_REASON.get(gracePeriod.getType())))
.setTargetId(targetId)
.setClientId(gracePeriod.getClientId())
.setEventTime(historyEntry.getModificationTime())
// The charge being cancelled will take place at the grace period's expiration time.
.setBillingTime(gracePeriod.getExpirationTime())
.setParent(historyEntry);
// Set the grace period's billing event using the appropriate Cancellation builder method.
if (gracePeriod.getOneTimeBillingEvent() != null) {
builder.setOneTimeEventRef(gracePeriod.getOneTimeBillingEvent());
} else if (gracePeriod.getRecurringBillingEvent() != null) {
builder.setRecurringEventRef(gracePeriod.getRecurringBillingEvent());
}
return builder.build();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for {@link Cancellation} since it is immutable. */
public static class Builder extends BillingEvent.Builder<Cancellation, Builder> {
public Builder() {}
private Builder(Cancellation instance) {
super(instance);
}
public Builder setBillingTime(DateTime billingTime) {
getInstance().billingTime = billingTime;
return this;
}
public Builder setOneTimeEventRef(Ref<BillingEvent.OneTime> eventRef) {
getInstance().refOneTime = eventRef;
return this;
}
public Builder setRecurringEventRef(Ref<BillingEvent.Recurring> eventRef) {
getInstance().refRecurring = eventRef;
return this;
}
@Override
public Cancellation build() {
Cancellation instance = getInstance();
checkNotNull(instance.billingTime);
checkNotNull(instance.reason);
checkState((instance.refOneTime == null) != (instance.refRecurring == null),
"Cancellations must have exactly one billing event ref set");
return super.build();
}
}
}
/**
* An event representing a modification of an existing one-time billing event.
*/
@Entity
public static class Modification extends BillingEvent {
/** The change in cost that should be applied to the original billing event. */
Money cost;
/** The one-time billing event to modify. */
Ref<BillingEvent.OneTime> eventRef;
/**
* Description of the modification (and presumably why it was issued). This text may appear as a
* line item on an invoice or report about such modifications.
*/
String description;
public Money getCost() {
return cost;
}
public Ref<BillingEvent.OneTime> getEventRef() {
return eventRef;
}
public String getDescription() {
return description;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/**
* Create a new Modification billing event which is a refund of the given OneTime billing event
* and that is parented off the given HistoryEntry.
*
* <p>Note that this method may appear to be unused most of the time, but it is kept around
* because it is needed by one-off scrap tools that need to make billing adjustments.
*/
public static Modification createRefundFor(
OneTime billingEvent, HistoryEntry historyEntry, String description) {
return new Builder()
.setClientId(billingEvent.getClientId())
.setFlags(billingEvent.getFlags())
.setReason(billingEvent.getReason())
.setTargetId(billingEvent.getTargetId())
.setEventRef(Ref.create(billingEvent))
.setEventTime(historyEntry.getModificationTime())
.setDescription(description)
.setCost(billingEvent.getCost().negated())
.setParent(historyEntry)
.build();
}
/** A builder for {@link Modification} since it is immutable. */
public static class Builder extends BillingEvent.Builder<Modification, Builder> {
public Builder() {}
private Builder(Modification instance) {
super(instance);
}
public Builder setCost(Money cost) {
getInstance().cost = cost;
return this;
}
public Builder setEventRef(Ref<BillingEvent.OneTime> eventRef) {
getInstance().eventRef = eventRef;
return this;
}
public Builder setDescription(String description) {
getInstance().description = description;
return this;
}
@Override
@SuppressWarnings("unchecked")
public Modification build() {
Modification instance = getInstance();
checkNotNull(instance.reason);
checkNotNull(instance.eventRef);
BillingEvent.OneTime billingEvent = instance.eventRef.get();
checkArgument(Objects.equals(
instance.cost.getCurrencyUnit(),
billingEvent.cost.getCurrencyUnit()),
"Referenced billing event is in a different currency");
return super.build();
}
}
}
}

View file

@ -0,0 +1,212 @@
// 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 com.google.domain.registry.model.billing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Verify.verifyNotNull;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.JsonMapBuilder;
import com.google.domain.registry.model.Jsonifiable;
import com.google.domain.registry.model.registrar.Registrar;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Log of monthly invoices and payments for a Registrar customer.
*
* <p>This is a one-off single-entry bookkeeping system. There is a separate account for each
* (registrar, currency) pair.
*
* <p>You should never update these entities once they've been inserted into datastore. If you need
* to change something, add a correction entry.
*/
@Entity
public class RegistrarBillingEntry extends ImmutableObject implements Jsonifiable {
@Parent
Key<Registrar> parent;
/** Arbitrary unique identifier. */
@Id
long id;
/**
* External transaction identifier or {@code null} if this is an invoice entry.
*
* <p>This is the ID or token that the payment gateway gives us, which represents the transaction
* in their database.
*/
@Nullable
String transactionId;
/**
* Time at which this entry was created.
*
* <p>This value is unique and monotonic for a given ({@link #parent}, {@link #currency}) pair.
*/
@Index
DateTime created;
/** Completely arbitrary description of payment. */
String description;
/**
* Currency of transaction.
*
* <p>This field is identical to {@code amount.getCurrencyUnit()} and is only here so it can be
* indexed in datastore.
*/
@Index
CurrencyUnit currency;
/**
* Amount and currency of invoice or payment.
*
* <p>This field is positive for debits (e.g. monthly invoice entries) and negative for credits
* (e.g. credit card payment transaction entries.)
*/
Money amount;
/**
* Balance of account for this currency.
*
* <p>This is {@code amount + previous.balance}.
*/
Money balance;
public Key<Registrar> getParent() {
return parent;
}
public long getId() {
return id;
}
@Nullable
public String getTransactionId() {
return transactionId;
}
public DateTime getCreated() {
return verifyNotNull(created, "created missing: %s", this);
}
public String getDescription() {
return verifyNotNull(description, "description missing: %s", this);
}
public CurrencyUnit getCurrency() {
return verifyNotNull(currency, "currency missing: %s", this);
}
public Money getAmount() {
return verifyNotNull(amount, "amount missing: %s", this);
}
public Money getBalance() {
return verifyNotNull(balance, "balance missing: %s", this);
}
@Override
public Map<String, Object> toJsonMap() {
return new JsonMapBuilder()
.put("id", id)
.put("transactionId", getTransactionId())
.putString("created", getCreated())
.put("description", getDescription())
.putString("currency", getCurrency())
.putString("amount", getAmount().getAmount())
.putString("balance", getBalance().getAmount())
.build();
}
/** A builder for constructing a {@link RegistrarBillingEntry}, since it's immutable. */
public static class Builder extends Buildable.Builder<RegistrarBillingEntry> {
@Nullable
private RegistrarBillingEntry previous;
public Builder() {}
public Builder setParent(Registrar parent) {
getInstance().parent = Key.create(parent);
return this;
}
public Builder setCreated(DateTime created) {
getInstance().created = created;
return this;
}
public Builder setPrevious(@Nullable RegistrarBillingEntry previous) {
this.previous = previous;
return this;
}
public Builder setTransactionId(@Nullable String transactionId) {
getInstance().transactionId = transactionId;
return this;
}
public Builder setDescription(String description) {
getInstance().description = checkNotNull(emptyToNull(description));
return this;
}
public Builder setAmount(Money amount) {
checkArgument(!amount.isZero(), "Amount can't be zero");
getInstance().amount = amount;
getInstance().currency = amount.getCurrencyUnit();
return this;
}
@Override
public RegistrarBillingEntry build() {
checkNotNull(getInstance().parent, "parent");
checkNotNull(getInstance().created, "created");
checkNotNull(getInstance().description, "description");
checkNotNull(getInstance().amount, "amount");
if (previous == null) {
getInstance().balance = getInstance().amount;
} else {
getInstance().balance = previous.balance.plus(getInstance().amount);
checkState(getInstance().parent.equals(previous.parent),
"Parent not same as previous:\nNew: %s\nPrevious: %s",
getInstance(), previous);
checkState(getInstance().created.isAfter(previous.created),
"Created timestamp not after previous:\nNew: %s\nPrevious: %s",
getInstance(), previous);
}
return cloneEmptyToNull(super.build());
}
}
}

View file

@ -0,0 +1,100 @@
// 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 com.google.domain.registry.model.billing;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Maps.EntryTransformer;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registry.Registries;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.util.CacheUtils;
import com.googlecode.objectify.cmd.Query;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import java.util.Map;
/** Utilities for managing the billing of {@link Registrar} customers. */
public final class RegistrarBillingUtils {
private static final Supplier<ImmutableSortedSet<CurrencyUnit>> CURRENCIES_CACHE =
CacheUtils.memoizeWithShortExpiration(
new Supplier<ImmutableSortedSet<CurrencyUnit>>() {
@Override
public ImmutableSortedSet<CurrencyUnit> get() {
return FluentIterable
.from(Registries.getTlds())
.transform(new Function<String, CurrencyUnit>() {
@Override
public CurrencyUnit apply(String tld) {
return Registry.get(tld).getCurrency();
}})
.toSortedSet(Ordering.natural());
}
});
/**
* Returns set of currencies in which registrars may be billed.
*
* <p>Each TLD has a currency associated with it. We don't do conversions. The registrar customer
* gets a separate bill for each currency.
*/
public static ImmutableSortedSet<CurrencyUnit> getCurrencies() {
return CURRENCIES_CACHE.get();
}
/**
* Returns query of {@link RegistrarBillingEntry} for each currency, most recent first.
*
* <p><b>Note:</b> Currency map keys are returned in sorted order, from {@link #getCurrencies()}.
*/
public static ImmutableMap<CurrencyUnit, Query<RegistrarBillingEntry>> getBillingEntryQueries(
final Registrar registrar) {
return Maps.toMap(getCurrencies(),
new Function<CurrencyUnit, Query<RegistrarBillingEntry>>() {
@Override
public Query<RegistrarBillingEntry> apply(CurrencyUnit currency) {
return ofy().load()
.type(RegistrarBillingEntry.class)
.ancestor(registrar)
.filter("currency", currency)
.order("-created");
}});
}
/** Returns amount of money registrar currently owes registry in each currency. */
public static Map<CurrencyUnit, Money> loadBalance(Registrar registrar) {
return Maps.transformEntries(getBillingEntryQueries(registrar),
new EntryTransformer<CurrencyUnit, Query<RegistrarBillingEntry>, Money>() {
@Override
public Money transformEntry(
CurrencyUnit currency, Query<RegistrarBillingEntry> query) {
RegistrarBillingEntry entry = query.first().now();
return entry != null ? entry.getBalance() : Money.zero(currency);
}});
}
private RegistrarBillingUtils() {}
}

View file

@ -0,0 +1,216 @@
// 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 com.google.domain.registry.model.billing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registry.Registry;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
/** A per-registrar billing credit, applied toward future charges for registrar activity. */
@Entity
public final class RegistrarCredit extends ImmutableObject implements Buildable {
/**
* The type of credit represented. The ordering below determines the order in which credits of
* of different types will be applied to an invoice charge.
*/
// Note: Right now the ordering is actually maintained manually via a hard-coded table in the
// relevant billing query, so if adding a credit type here, add it there as well.
// TODO(b/19031546): make the query automatically reflect the order in this enum.
public enum CreditType {
/** Credit awarded as an incentive to participate in sunrise/landrush auctions. */
AUCTION("Auction Credit"),
/** Credit awarded as part of a promotional deal. */
PROMOTION("Promotional Credit");
/** A descriptive name for a credit of this type. */
private String descriptiveName;
CreditType(String descriptiveName) {
this.descriptiveName = descriptiveName;
}
public String getDescriptiveName() {
return descriptiveName;
}
}
@Id
long id;
/** The registrar to whom this credit belongs. */
@Parent
Ref<Registrar> parent;
/** The type of credit. */
CreditType type;
/**
* The time that this credit was created. If a registrar has multiple credits of a given type,
* the older credits will be applied first.
*/
DateTime creationTime;
/** The currency in which the balance for this credit is stored. */
CurrencyUnit currency;
/** The line item description to use when displaying this credit on an invoice. */
String description;
/**
* The TLD in which this credit applies.
*
* <p>For auction credits, this is also the TLD for which the relevant auctions occurred.
*/
String tld;
public Ref<Registrar> getParent() {
return parent;
}
public CreditType getType() {
return type;
}
public DateTime getCreationTime() {
return creationTime;
}
public CurrencyUnit getCurrency() {
return currency;
}
public String getDescription() {
return description;
}
public String getTld() {
return tld;
}
/** Returns a string representation of this credit. */
public String getSummary() {
String fields = Joiner.on(' ').join(type, creationTime, tld);
return String.format("%s (%s/%d) - %s", description, parent.getKey().getName(), id, fields);
}
/** Returns the default description for this {@link RegistrarCredit} instance. */
private String getDefaultDescription() {
return type.getDescriptiveName() + " for ." + tld;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A Builder for {@link RegistrarCredit}. */
public static class Builder extends Buildable.Builder<RegistrarCredit> {
public Builder() {}
public Builder(RegistrarCredit instance) {
super(instance);
}
public Builder setParent(Registrar parent) {
getInstance().parent = Ref.create(parent);
return this;
}
public Builder setType(CreditType type) {
getInstance().type = type;
return this;
}
public Builder setCreationTime(DateTime creationTime) {
getInstance().creationTime = creationTime;
return this;
}
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setDescription(String description) {
getInstance().description = description;
return this;
}
public Builder setTld(String tld) {
getInstance().tld = tld;
return this;
}
@Override
public RegistrarCredit build() {
RegistrarCredit instance = getInstance();
checkNotNull(instance.parent, "parent credit");
checkNotNull(instance.type, "type");
checkNotNull(instance.creationTime, "creationTime");
checkNotNull(instance.currency, "currency");
assertTldExists(checkNotNull(instance.tld, "tld"));
checkArgument(
Registry.get(instance.tld).getCurrency().equals(instance.currency),
"Credits must be in the currency of the assigned TLD");
instance.description =
Optional.fromNullable(instance.description).or(instance.getDefaultDescription());
return super.build();
}
}
/** Ordering that sorts credits first by type and then by creation time. */
private static final Ordering<RegistrarCredit> CREDIT_PRIORITY_ORDERING =
new Ordering<RegistrarCredit>() {
@Override
public int compare(RegistrarCredit left, RegistrarCredit right) {
return ComparisonChain.start()
.compare(left.type, right.type)
.compare(left.creationTime, right.creationTime)
.result();
}
};
/**
* Loads all RegistrarCredit entities for the given Registrar.
*
* <p>The resulting list sorts the credits first by type and then by creation time.
*/
public static ImmutableList<RegistrarCredit> loadAllForRegistrar(Registrar registrar) {
return FluentIterable.from(ofy().load().type(RegistrarCredit.class).ancestor(registrar))
.toSortedList(CREDIT_PRIORITY_ORDERING);
}
}

View file

@ -0,0 +1,253 @@
// 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 com.google.domain.registry.model.billing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ForwardingNavigableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.impl.ref.DeadRef;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.util.HashMap;
import java.util.Map;
/**
* The balance of a {@link RegistrarCredit} at a given point in time.
*
* <p>A credit balance has two related times in addition to the monetary amount: the effective time,
* which represents the time at which the amount becomes the actual credit balance; and the
* written time, which represents the time at which this balance object was saved.
*
* <p>The active balance of a credit object before (at) any given point in time T can be found by
* taking the balance object with the latest effective time that is before (before or at) T, and
* breaking any ties by choosing the mostly recently written among those balances.
*/
@Entity
public final class RegistrarCreditBalance extends ImmutableObject implements Buildable {
@Id
long id;
/** The registrar credit object for which this represents a balance. */
@Parent
Ref<RegistrarCredit> parent;
/** The time at which this balance amount should become effective. */
DateTime effectiveTime;
/**
* The time at which this balance update was written.
*
* <p>Used to break ties in cases where there are multiple balances with the same effective time,
* as the last written balance will take priority.
*/
DateTime writtenTime;
/** The monetary amount of credit balance remaining as of the effective time. */
Money amount;
public Ref<RegistrarCredit> getParent() {
return parent;
}
public DateTime getEffectiveTime() {
return effectiveTime;
}
public DateTime getWrittenTime() {
return writtenTime;
}
public Money getAmount() {
return amount;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A Builder for an {@link RegistrarCreditBalance}. */
public static class Builder extends Buildable.Builder<RegistrarCreditBalance> {
public Builder() {}
public Builder(RegistrarCreditBalance instance) {
super(instance);
}
public RegistrarCreditBalance.Builder setParent(RegistrarCredit parent) {
// Use a DeadRef so that we can retrieve the actual instance provided later on in build().
getInstance().parent = new DeadRef<>(Key.create(parent), parent);
return this;
}
public RegistrarCreditBalance.Builder setEffectiveTime(DateTime effectiveTime) {
getInstance().effectiveTime = effectiveTime;
return this;
}
public RegistrarCreditBalance.Builder setWrittenTime(DateTime writtenTime) {
getInstance().writtenTime = writtenTime;
return this;
}
public RegistrarCreditBalance.Builder setAmount(Money amount) {
checkArgument(amount.isPositiveOrZero(), "Credit balance amount cannot be negative");
getInstance().amount = amount;
return this;
}
@Override
public RegistrarCreditBalance build() {
RegistrarCreditBalance instance = getInstance();
checkNotNull(instance.parent);
checkNotNull(instance.effectiveTime);
checkNotNull(instance.writtenTime);
checkNotNull(instance.amount);
RegistrarCredit credit = instance.parent.get();
checkState(
instance.amount.getCurrencyUnit().equals(credit.getCurrency()),
"Currency of balance amount differs from credit currency (%s vs %s)",
instance.amount.getCurrencyUnit(),
credit.getCurrency());
return super.build();
}
}
/**
* A map of maps representing the historical credit balance information for a given credit.
*
* <p>Specifically, this class provides a high-level view of the balances for a given credit
* by in essence grouping them first by effective time and then by written time. This facilitates
* the printing of a readable representation of a credit's balance history, and the retrieval of
* the active balance at a given time (as described above on RegistrarCreditBalance).
*/
public static class BalanceMap
extends ForwardingNavigableMap<DateTime, ImmutableSortedMap<DateTime, Money>> {
/**
* Constructs a BalanceMap for the given registrar credit by loading all RegistrarCreditBalance
* entities for the credit and then inserting them into a map of maps keyed first by effective
* time and then by written time with the balance amount as the value.
*/
public static BalanceMap createForCredit(RegistrarCredit registrarCredit) {
// Build up the data in a mutable map of maps.
Map<DateTime, Map<DateTime, Money>> map = new HashMap<>();
for (RegistrarCreditBalance balance :
ofy().load().type(RegistrarCreditBalance.class).ancestor(registrarCredit)) {
// Create the submap at this key if it doesn't exist already.
Map<DateTime, Money> submap =
Optional.fromNullable(map.get(balance.effectiveTime))
.or(new HashMap<DateTime, Money>());
submap.put(balance.writtenTime, balance.amount);
map.put(balance.effectiveTime, submap);
}
// Wrap the mutable map of maps in an immutable BalanceMap.
return new BalanceMap(map);
}
/** The immutable map of maps used as the backing map. */
private final ImmutableSortedMap<DateTime, ImmutableSortedMap<DateTime, Money>> delegate;
/**
* Constructs an immutable BalanceMap from balance data provided as a map of maps.
*
* <p>The constructed BalanceMap delegates to an immutable copy of the provided map of maps.
* This copy is created by first making a view of the map in which each submap is replaced by
* an immutable copy, and then making an immutable copy of that view.
*/
@VisibleForTesting
BalanceMap(Map<DateTime, ? extends Map<DateTime, Money>> data) {
delegate = ImmutableSortedMap.copyOf(
Maps.transformValues(
data,
new Function<Map<DateTime, Money>, ImmutableSortedMap<DateTime, Money>>() {
@Override
public ImmutableSortedMap<DateTime, Money> apply(Map<DateTime, Money> map) {
return ImmutableSortedMap.copyOf(map, Ordering.natural());
}
}),
Ordering.natural());
}
@Override
protected ImmutableSortedMap<DateTime, ImmutableSortedMap<DateTime, Money>> delegate() {
return delegate;
}
/**
* Returns the most recently written balance for the effective time corresponding to this entry,
* or {@link Optional#absent()} if this entry is null.
*/
private Optional<Money> getMostRecentlyWrittenBalance(
Map.Entry<DateTime, ImmutableSortedMap<DateTime, Money>> balancesAtEffectiveTime) {
return balancesAtEffectiveTime == null
? Optional.<Money>absent()
// Don't use Optional.fromNullable() here since it's an error if there's a empty submap.
: Optional.of(balancesAtEffectiveTime.getValue().lastEntry().getValue());
}
/**
* Returns the active balance at a given time as described above on RegistrarCreditBalance, or
* {@link Optional#absent()} if no balance was active at that time (i.e. the time provided is
* before the first effectiveTime of any balance for the credit this BalanceMap represents).
*/
public Optional<Money> getActiveBalanceAtTime(DateTime time) {
return getMostRecentlyWrittenBalance(delegate.floorEntry(time));
}
/**
* Returns the active balance before a given time as described above on RegistrarCreditBalance,
* or {@link Optional#absent()} if no balance was active before that time (i.e. the time
* provided is before or at the first effectiveTime of any balance for the credit).
*/
public Optional<Money> getActiveBalanceBeforeTime(DateTime time) {
return getMostRecentlyWrittenBalance(delegate.lowerEntry(time));
}
/** Returns a string representation of this BalanceMap's data. */
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Map.Entry<DateTime, ? extends Map<DateTime, Money>> entry : delegate.entrySet()) {
builder.append(String.format(" - %s\n", entry.getKey()));
for (Map.Entry<DateTime, Money> subEntry : entry.getValue().entrySet()) {
builder.append(
String.format(" - %s - %s\n", subEntry.getKey(), subEntry.getValue()));
}
}
return builder.toString();
}
}
}

View file

@ -0,0 +1,35 @@
// 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 com.google.domain.registry.model.common;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
/** A singleton entity in the datastore. */
public abstract class CrossTldSingleton extends ImmutableObject {
public static final long SINGLETON_ID = 1; // There is always exactly one of these.
@Id
long id = SINGLETON_ID;
@Parent
Key<EntityGroupRoot> parent = getCrossTldKey();
}

View file

@ -0,0 +1,47 @@
// 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 com.google.domain.registry.model.common;
import com.google.domain.registry.model.BackupGroupRoot;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
/**
* The root key for the entity group which is known as the cross-tld entity group for historical
* reasons.
*
* <p>This exists as a storage place for common configuration options and global settings that
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
* reason this common entity group exists is because it enables strongly consistent queries and
* updates across this seldomly updated data. This shared entity group also helps cut down on
* a potential ballooning in the number of entity groups enlisted in transactions.
*
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in
* a single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was
* the entity group for the single namespace where global data applicable for all TLDs lived.
*/
@Entity
public class EntityGroupRoot extends BackupGroupRoot {
@Id
private String id;
/** The root key for cross-tld resources such as registrars. */
public static final Key<EntityGroupRoot> getCrossTldKey() {
return Key.create(EntityGroupRoot.class, "cross-tld");
}
}

View file

@ -0,0 +1,78 @@
// 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 com.google.domain.registry.model.common;
import static com.google.domain.registry.model.ofy.ObjectifyService.allocateId;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.appengine.api.users.User;
import com.google.common.base.Splitter;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.annotations.NotBackedUp;
import com.google.domain.registry.model.annotations.NotBackedUp.Reason;
import com.googlecode.objectify.VoidWork;
import com.googlecode.objectify.Work;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
/**
* A helper class to convert email addresses to GAE user ids. It does so by persisting a User
* object with the email address to datastore, and then immediately reading it back.
*/
@Entity
@NotBackedUp(reason = Reason.TRANSIENT)
public class GaeUserIdConverter extends ImmutableObject {
@Id
public long id;
User user;
/**
* Converts an email address to a GAE user id.
*
* @return Numeric GAE user id (in String form), or null if email address has no GAE id
*/
public static String convertEmailAddressToGaeUserId(String emailAddress) {
final GaeUserIdConverter gaeUserIdConverter = new GaeUserIdConverter();
gaeUserIdConverter.id = allocateId();
gaeUserIdConverter.user =
new User(emailAddress, Splitter.on('@').splitToList(emailAddress).get(1));
try {
// Perform these operations in a transactionless context to avoid enlisting in some outer
// transaction (if any).
ofy().doTransactionless(new VoidWork() {
@Override
public void vrun() {
ofy().saveWithoutBackup().entity(gaeUserIdConverter).now();
}});
// The read must be done in its own transaction to avoid reading from the session cache.
return ofy().transactNew(new Work<String>() {
@Override
public String run() {
return ofy().load().entity(gaeUserIdConverter).safe().user.getUserId();
}});
} finally {
ofy().doTransactionless(new VoidWork() {
@Override
public void vrun() {
ofy().deleteWithoutBackup().entity(gaeUserIdConverter).now();
}});
}
}
}

View file

@ -0,0 +1,56 @@
// 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 com.google.domain.registry.model.common;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
/** An object that's equivalent to a {@code Range<Long>} that can be persisted to datastore. */
@Embed
public class PersistedRangeLong extends ImmutableObject {
private Long lowerBound = null;
private BoundType lowerBoundType = null;
private Long upperBound = null;
private BoundType upperBoundType = null;
public Range<Long> asRange() {
Range<Long> range = Range.all();
if (lowerBound != null) {
range = range.intersection(Range.downTo(lowerBound, lowerBoundType));
}
if (upperBound != null) {
range = range.intersection(Range.upTo(upperBound, upperBoundType));
}
return range;
}
public static PersistedRangeLong create(Range<Long> range) {
PersistedRangeLong instance = new PersistedRangeLong();
if (range.hasLowerBound()) {
instance.lowerBound = range.lowerEndpoint();
instance.lowerBoundType = range.lowerBoundType();
}
if (range.hasUpperBound()) {
instance.upperBound = range.upperEndpoint();
instance.upperBoundType = range.upperBoundType();
}
return instance;
}
}

View file

@ -0,0 +1,93 @@
// 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 com.google.domain.registry.model.common;
import static com.google.domain.registry.util.DateTimeUtils.isAtOrAfter;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import com.google.common.base.Splitter;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Index;
import org.joda.time.DateTime;
import java.util.List;
/**
* A time of year (month, day, millis of day) that can be stored in a sort-friendly format.
* <p>
* This is conceptually similar to {@code MonthDay} in Joda or more generally to Joda's
* {@code Partial}, but the parts we need are too simple to justify a full implementation of
* {@code Partial}.
* <p>
* For simplicity, the native representation of this class's data is its stored format. This allows
* it to be embeddable with no translation needed and also delays parsing of the string on load
* until it's actually needed.
*/
@Embed
public class TimeOfYear extends ImmutableObject {
/**
* The time as "month day millis" with all fields left-padded with zeroes so that lexographic
* sorting will do the right thing.
*/
@Index
String timeString;
/**
* Constructs a {@link TimeOfYear} from a {@link DateTime}.
* <p>
* This handles leap years in an intentionally peculiar way by always treating February 29 as
* February 28. It is impossible to construct a {@link TimeOfYear} for February 29th.
*/
public static TimeOfYear fromDateTime(DateTime dateTime) {
DateTime nextYear = dateTime.plusYears(1); // This turns February 29 into February 28.
TimeOfYear instance = new TimeOfYear();
instance.timeString = String.format(
"%02d %02d %08d",
nextYear.getMonthOfYear(),
nextYear.getDayOfMonth(),
nextYear.getMillisOfDay());
return instance;
}
/** Get the first {@link DateTime} with this month/day/millis that is at or after the start. */
public DateTime atOrAfter(DateTime start) {
DateTime withSameYear = getDateTimeWithSameYear(start);
return isAtOrAfter(withSameYear, start) ? withSameYear : withSameYear.plusYears(1);
}
/** Get the first {@link DateTime} with this month/day/millis that is at or before the end. */
public DateTime beforeOrAt(DateTime end) {
DateTime withSameYear = getDateTimeWithSameYear(end);
return isBeforeOrAt(withSameYear, end) ? withSameYear : withSameYear.minusYears(1);
}
/**
* Return a new datetime with the same year as the parameter but projected to the month, day, and
* time of day of this object.
*/
private DateTime getDateTimeWithSameYear(DateTime date) {
List<String> monthDayMillis = Splitter.on(' ').splitToList(timeString);
// Do not be clever and use Ints.stringConverter here. That does radix guessing, and bad things
// will happen because of the leading zeroes.
return date
.withMonthOfYear(Integer.parseInt(monthDayMillis.get(0)))
.withDayOfMonth(Integer.parseInt(monthDayMillis.get(1)))
.withMillisOfDay(Integer.parseInt(monthDayMillis.get(2)));
}
}

View file

@ -0,0 +1,189 @@
// 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 com.google.domain.registry.model.common;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import static com.google.domain.registry.util.DateTimeUtils.latestOf;
import com.google.common.base.Function;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.util.TypeUtils;
import com.googlecode.objectify.mapper.Mapper;
import org.joda.time.DateTime;
import java.util.NavigableMap;
import java.util.TreeMap;
/**
* An entity property whose value transitions over time. Each value it takes on becomes active
* at a corresponding instant, and remains active until the next transition occurs. At least one
* "start of time" value (corresponding to START_OF_TIME, i.e. the Unix epoch) must be provided
* so that the property will have a value for all possible times.
* <p>
* This concept is naturally represented by a sorted map of {@code DateTime} to {@code V}, but
* the AppEngine datastore cannot natively represent a map keyed on non-strings. Instead, we
* store an ordered list of transitions and use Objectify's @Mapify annotation to automatically
* recreate the sorted map on load from the datastore, which is used as a backing map for this
* property; the property itself also implements Map by way of extending ForwardingMap, so that
* this property can stored directly as the @Mapify field in the entity.
* <p>
* The type parameter {@code T} specifies a user-defined subclass of {@code TimedTransition<V>} to
* use for storing the list of transitions. The user is given this choice of subclass so that the
* field of the value type stored in the transition can be given a customized name.
*/
public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedTransition<V>>
extends ForwardingMap<DateTime, T> {
/**
* A transition to a value of type {@code V} at a certain time. This superclass only has a field
* for the {@code DateTime}, which means that subclasses should supply the field of type {@code V}
* and implementations of the abstract getter and setter methods to access that field. This design
* is so that subclasses tagged with @Embed can define a custom field name for their value, for
* the purpose of backwards compatibility and better readability of the datastore representation.
* <p>
* The public visibility of this class exists only so that it can be subclassed; clients should
* never call any methods on this class or attempt to access its members, but should instead
* treat it as a customizable implementation detail of {@code TimedTransitionProperty}. However,
* note that subclasses must also have public visibility so that they can be instantiated via
* reflection in a call to {@code fromValueMap}.
*/
public abstract static class TimedTransition<V> extends ImmutableObject {
/** The time at which this value becomes the active value. */
private DateTime transitionTime;
/** Returns the value that this transition will activate. */
protected abstract V getValue();
/** Sets the value that will be activated at this transition's time. */
protected abstract void setValue(V value);
}
/** Mapper for use with @Mapify; extracts the time from a TimedTransition to use it as a key. */
public static class TimeMapper implements Mapper<DateTime, TimedTransition<?>> {
@Override
public DateTime getKey(TimedTransition<?> transition) {
return transition.transitionTime;
}
}
/**
* Converts the provided value map into the equivalent transition map, using transition objects
* of the given TimedTransition subclass. The value map must be sorted according to the natural
* ordering of its DateTime keys, and keys cannot be earlier than START_OF_TIME.
*/
// NB: The Class<T> parameter could be eliminated by getting the class via reflection, but then
// the callsite cannot infer T, so unless you explicitly call this as .<V, T>fromValueMap() it
// will default to using just TimedTransition<V>, which fails at runtime.
private static <V, T extends TimedTransition<V>> NavigableMap<DateTime, T> makeTransitionMap(
ImmutableSortedMap<DateTime, V> valueMap,
final Class<T> timedTransitionSubclass) {
checkArgument(
Ordering.natural().equals(valueMap.comparator()),
"Timed transition value map must have transition time keys in chronological order");
return Maps.transformEntries(valueMap, new Maps.EntryTransformer<DateTime, V, T>() {
// For each entry in the input value map, make the output map have an entry at the
// corresponding time that points to a transition containing that time and that value.
@Override
public T transformEntry(DateTime transitionTime, V value) {
checkArgument(!transitionTime.isBefore(START_OF_TIME),
"Timed transition times cannot be earlier than START_OF_TIME / Unix Epoch");
T subclass = TypeUtils.instantiate(timedTransitionSubclass);
((TimedTransition<V>) subclass).transitionTime = transitionTime;
subclass.setValue(value);
return subclass;
}});
}
/**
* Returns a new immutable {@code TimedTransitionProperty} representing the given map of DateTime
* to value, with transitions constructed using the given {@code TimedTransition} subclass.
* <p>
* This method should be the normal method for constructing a {@TimedTransitionProperty}.
*/
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> fromValueMap(
ImmutableSortedMap<DateTime, V> valueMap,
final Class<T> timedTransitionSubclass) {
return new TimedTransitionProperty<>(ImmutableSortedMap.copyOf(
makeTransitionMap(valueMap, timedTransitionSubclass)));
}
/**
* Returns a new mutable {@code TimedTransitionProperty} representing the given map of DateTime
* to value, with transitions constructed using the given {@code TimedTransition} subclass.
* <p>
* This method should only be used for initializing fields that are declared with the @Mapify
* annotation. The map for those fields must be mutable so that Objectify can load values from
* the datastore into the map, but clients should still never mutate the field's map directly.
*/
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> forMapify(
ImmutableSortedMap<DateTime, V> valueMap,
Class<T> timedTransitionSubclass) {
return new TimedTransitionProperty<>(new TreeMap<>(
makeTransitionMap(valueMap, timedTransitionSubclass)));
}
/** The backing map of DateTime to TimedTransition subclass used to store the transitions. */
private final NavigableMap<DateTime, T> backingMap;
/** Returns a new {@code TimedTransitionProperty} backed by the provided map instance. */
private TimedTransitionProperty(NavigableMap<DateTime, T> backingMap) {
checkArgument(backingMap.get(START_OF_TIME) != null,
"Must provide transition entry for the start of time (Unix Epoch)");
this.backingMap = backingMap;
}
/**
* Checks whether this TimedTransitionProperty is in a valid state, i.e. whether it has a
* transition entry for START_OF_TIME, and throws IllegalStateException if not.
*/
public void checkValidity() {
checkState(backingMap.get(START_OF_TIME) != null,
"Timed transition values missing required entry for the start of time (Unix Epoch)");
}
@Override
protected NavigableMap<DateTime, T> delegate() {
return backingMap;
}
/** Returns the map of DateTime to value that is the "natural" representation of this property. */
public ImmutableSortedMap<DateTime, V> toValueMap() {
return ImmutableSortedMap.copyOfSorted(Maps.transformValues(
backingMap,
new Function<T, V>() {
@Override
public V apply(T timedTransition) {
return timedTransition.getValue();
}}));
}
/**
* Returns the value of the property that is active at the specified time. The active value for
* a time before START_OF_TIME is extrapolated to be the value that is active at START_OF_TIME.
*/
public V getValueAtTime(DateTime time) {
// Retrieve the current value by finding the latest transition before or at the given time,
// where any given time earlier than START_OF_TIME is replaced by START_OF_TIME.
return backingMap.floorEntry(latestOf(START_OF_TIME, time)).getValue().getValue();
}
}

View file

@ -0,0 +1,37 @@
// 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 com.google.domain.registry.model.contact;
import com.google.domain.registry.model.eppcommon.Address;
import com.googlecode.objectify.annotation.Embed;
/**
* EPP Contact Address
* <p>
* This class is embedded inside the {@link PostalInfo} of an EPP contact to hold its address. The
* fields are all defined in parent class {@link Address}, but the subclass is still necessary to
* pick up the contact namespace.
* <p>
* This does not implement {@code Overlayable} because it is intended to be bulk replaced on update.
*
* @see PostalInfo
*/
@Embed
public class ContactAddress extends Address {
/** Builder for {@link ContactAddress}. */
public static class Builder extends Address.Builder<ContactAddress> {}
}

View file

@ -0,0 +1,51 @@
// 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 com.google.domain.registry.model.contact;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.googlecode.objectify.annotation.Embed;
import javax.xml.bind.annotation.XmlType;
/** A version of authInfo specifically for contacts. */
@Embed
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
public class ContactAuthInfo extends AuthInfo {
public static ContactAuthInfo create(PasswordAuth pw) {
ContactAuthInfo instance = new ContactAuthInfo();
instance.pw = pw;
return instance;
}
@Override
public void verifyAuthorizedFor(EppResource eppResource) throws BadAuthInfoException {
ContactResource contact = (ContactResource) eppResource;
PasswordAuth passwordAuth = checkNotNull(getPw());
// It's rather strange to specify a repoId on a contact auth info. Instead of explicitly
// rejecting it, we'll just make sure the repoId matches this particular contact.
if (passwordAuth.getRepoId() != null && !contact.getRepoId().equals(getRepoId())) {
throw new BadAuthInfoException();
}
if (!contact.getAuthInfo().getPw().getValue().equals(passwordAuth.getValue())) {
throw new BadAuthInfoException();
}
}
}

View file

@ -0,0 +1,239 @@
// 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 com.google.domain.registry.model.contact;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.contact.ContactResource.Builder;
import com.google.domain.registry.model.contact.PostalInfo.Type;
import com.google.domain.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceCheck;
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** A collection of {@link ContactResource} commands. */
public class ContactCommand {
/** The fields on "chgType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
@XmlTransient
public static class ContactCreateOrChange extends ImmutableObject
implements ResourceCreateOrChange<ContactResource.Builder> {
/** Postal info for the contact. */
List<PostalInfo> postalInfo;
/** Contacts voice number. */
ContactPhoneNumber voice;
/** Contacts fax number. */
ContactPhoneNumber fax;
/** Contacts email address. */
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String email;
/** Authorization info (aka transfer secret) of the contact. */
ContactAuthInfo authInfo;
/** Disclosure policy. */
Disclose disclose;
/** Helper method to move between the postal infos list and the individual getters. */
protected Map<Type, PostalInfo> getPostalInfosAsMap() {
// There can be no more than 2 postalInfos (enforced by the schema), and if there are 2 they
// must be of different types (not enforced). If the type is repeated, uniqueIndex will throw.
checkState(nullToEmpty(postalInfo).size() <= 2);
return Maps.uniqueIndex(nullToEmpty(postalInfo), new Function<PostalInfo, Type>() {
@Override
public Type apply(PostalInfo info) {
return info.getType();
}});
}
@Override
public void applyTo(Builder builder) {
if (authInfo != null) {
builder.setAuthInfo(authInfo);
}
if (disclose != null) {
builder.setDisclose(disclose);
}
if (email != null) {
builder.setEmailAddress(email);
}
if (fax != null) {
builder.setFaxNumber(fax);
}
if (voice != null) {
builder.setVoiceNumber(voice);
}
}
}
/** An abstract contact command that contains authorization info. */
@XmlTransient
public static class AbstractContactAuthCommand extends AbstractSingleResourceCommand {
/** Authorization info used to validate if client has permissions to perform this operation. */
ContactAuthInfo authInfo;
@Override
public ContactAuthInfo getAuthInfo() {
return authInfo;
}
}
/**
* A create command for a {@link ContactResource}, mapping "createType" from
* {@link "http://tools.ietf.org/html/rfc5733"}.
*/
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
@XmlRootElement
public static class Create extends ContactCreateOrChange
implements SingleResourceCommand, ResourceCreateOrChange<ContactResource.Builder> {
/**
* Unique identifier for this contact.
* <p>
* This is only unique in the sense that for any given lifetime specified as the time range from
* (creationTime, deletionTime) there can only be one contact in the datastore with this id.
* However, there can be many contacts with the same id and non-overlapping lifetimes.
*/
@XmlElement(name = "id")
String contactId;
@Override
public String getTargetId() {
return contactId;
}
@Override
public ContactAuthInfo getAuthInfo() {
return authInfo;
}
@Override
public void applyTo(ContactResource.Builder builder) {
super.applyTo(builder);
if (contactId != null) {
builder.setContactId(contactId);
}
Map<Type, PostalInfo> postalInfosAsMap = getPostalInfosAsMap();
if (postalInfosAsMap.containsKey(Type.INTERNATIONALIZED)) {
builder.setInternationalizedPostalInfo(postalInfosAsMap.get(Type.INTERNATIONALIZED));
}
if (postalInfosAsMap.containsKey(Type.LOCALIZED)) {
builder.setLocalizedPostalInfo(postalInfosAsMap.get(Type.LOCALIZED));
}
}
}
/** A delete command for a {@link ContactResource}. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
/** An info request for a {@link ContactResource}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Info extends AbstractContactAuthCommand {}
/** A check request for {@link ContactResource}. */
@XmlRootElement
public static class Check extends ResourceCheck {}
/** A transfer operation for a {@link ContactResource}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Transfer extends AbstractContactAuthCommand {}
/** An update to a {@link ContactResource}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.AddRemove, ContactResource.Builder, Update.Change> {
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
protected AddRemove innerAdd;
@XmlElement(name = "rem")
protected AddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
return innerChange;
}
@Override
protected AddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
protected AddRemove getNullableInnerRemove() {
return innerRemove;
}
/** The inner change type on a contact update command. */
public static class AddRemove extends ResourceUpdate.AddRemove {}
/** The inner change type on a contact update command. */
@XmlType(propOrder = {"postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
public static class Change extends ContactCreateOrChange {
/**
* The spec requires the following behaviors:
* <ul>
* <li>If you update part of a postal info, the fields that you didn't update are unchanged.
* <li>If you update one postal info but not the other, the other is deleted.
* </ul>
* Therefore, if you want to preserve one postal info and update another you need to send the
* update and also something that technically updates the preserved one, even if it only
* "updates" it by setting just one field to the same value.
*/
@Override
public void applyTo(ContactResource.Builder builder) {
super.applyTo(builder);
Map<Type, PostalInfo> postalInfosAsMap = getPostalInfosAsMap();
if (postalInfosAsMap.containsKey(Type.INTERNATIONALIZED)) {
builder.overlayInternationalizedPostalInfo(postalInfosAsMap.get(Type.INTERNATIONALIZED));
if (postalInfosAsMap.size() == 1) {
builder.setLocalizedPostalInfo(null);
}
}
if (postalInfosAsMap.containsKey(Type.LOCALIZED)) {
builder.overlayLocalizedPostalInfo(postalInfosAsMap.get(Type.LOCALIZED));
if (postalInfosAsMap.size() == 1) {
builder.setInternationalizedPostalInfo(null);
}
}
}
}
}
}

View file

@ -0,0 +1,35 @@
// 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 com.google.domain.registry.model.contact;
import com.google.domain.registry.model.eppcommon.PhoneNumber;
import com.googlecode.objectify.annotation.Embed;
/**
* EPP Contact Phone Number
* <p>
* This class is embedded inside a {@link ContactResource} hold the phone number of an EPP contact.
* The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is still
* necessary to pick up the contact namespace.
*
* @see ContactResource
*/
@Embed
public class ContactPhoneNumber extends PhoneNumber {
/** Builder for {@link ContactPhoneNumber}. */
public static class Builder extends PhoneNumber.Builder<ContactPhoneNumber> {}
}

View file

@ -0,0 +1,260 @@
// 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 com.google.domain.registry.model.contact;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.EppResource.ForeignKeyedEppResource;
import com.google.domain.registry.model.annotations.ExternalMessagingName;
import com.google.domain.registry.model.contact.PostalInfo.Type;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.googlecode.objectify.annotation.Cache;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.condition.IfNull;
import org.joda.time.DateTime;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** A persistable Contact resource including mutable and non-mutable fields. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"contactId",
"repoId",
"status",
"postalInfosAsList",
"voice",
"fax",
"email",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"lastTransferTime",
"authInfo",
"disclose" })
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
@Entity
@ExternalMessagingName("contact")
public class ContactResource extends EppResource implements ForeignKeyedEppResource {
/**
* Unique identifier for this contact.
* <p>
* This is only unique in the sense that for any given lifetime specified as the time range from
* (creationTime, deletionTime) there can only be one contact in the datastore with this id.
* However, there can be many contacts with the same id and non-overlapping lifetimes.
*/
@XmlTransient
String contactId;
/**
* Localized postal info for the contact. All contained values must be representable in the 7-bit
* US-ASCII character set. Personal info; cleared by wipeOut().
*/
@IgnoreSave(IfNull.class)
@XmlTransient
PostalInfo localizedPostalInfo;
/** Internationalized postal info for the contact. Personal info; cleared by wipeOut(). */
@IgnoreSave(IfNull.class)
@XmlTransient
PostalInfo internationalizedPostalInfo;
/** Contacts voice number. Personal info; cleared by wipeOut(). */
@IgnoreSave(IfNull.class)
ContactPhoneNumber voice;
/** Contacts fax number. Personal info; cleared by wipeOut(). */
@IgnoreSave(IfNull.class)
ContactPhoneNumber fax;
/** Contacts email address. Personal info; cleared by wipeOut(). */
@IgnoreSave(IfNull.class)
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String email;
/** Authorization info (aka transfer secret) of the contact. */
ContactAuthInfo authInfo;
// If any new fields are added which contain personal information, make sure they are cleared by
// the wipeOut() function, so that data is not kept around for deleted contacts.
/** Disclosure policy. */
@IgnoreSave(IfNull.class)
Disclose disclose;
@XmlElement(name = "id")
public String getContactId() {
return contactId;
}
public PostalInfo getLocalizedPostalInfo() {
return localizedPostalInfo;
}
public PostalInfo getInternationalizedPostalInfo() {
return internationalizedPostalInfo;
}
public ContactPhoneNumber getVoiceNumber() {
return voice;
}
public ContactPhoneNumber getFaxNumber() {
return fax;
}
public String getEmailAddress() {
return email;
}
public AuthInfo getAuthInfo() {
return authInfo;
}
public Disclose getDisclose() {
return disclose;
}
@Override
public String getForeignKey() {
return contactId;
}
/**
* Postal info for the contact.
* <p>
* The XML marshalling expects the {@link PostalInfo} objects in a list, but we can't actually
* persist them to datastore that way because Objectify can't handle collections of embedded
* objects that themselves contain collections, and there's a list of streets inside. This method
* transforms the persisted format to the XML format for marshalling.
*/
@XmlElement(name = "postalInfo")
public List<PostalInfo> getPostalInfosAsList() {
return FluentIterable
.from(Lists.newArrayList(localizedPostalInfo, internationalizedPostalInfo))
.filter(Predicates.notNull())
.toList();
}
@Override
public ContactResource cloneProjectedAtTime(DateTime now) {
Builder builder = this.asBuilder();
projectResourceOntoBuilderAtTime(this, builder, now);
return builder.build();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link ContactResource}, since it is immutable. */
public static class Builder extends EppResource.Builder<ContactResource, Builder> {
public Builder() {}
private Builder(ContactResource instance) {
super(instance);
}
public Builder setContactId(String contactId) {
getInstance().contactId = contactId;
return this;
}
public Builder setLocalizedPostalInfo(PostalInfo localizedPostalInfo) {
checkArgument(localizedPostalInfo == null
|| Type.LOCALIZED.equals(localizedPostalInfo.getType()));
getInstance().localizedPostalInfo = localizedPostalInfo;
return this;
}
public Builder setInternationalizedPostalInfo(PostalInfo internationalizedPostalInfo) {
checkArgument(internationalizedPostalInfo == null
|| Type.INTERNATIONALIZED.equals(internationalizedPostalInfo.getType()));
getInstance().internationalizedPostalInfo = internationalizedPostalInfo;
return this;
}
public Builder overlayLocalizedPostalInfo(PostalInfo localizedPostalInfo) {
return setLocalizedPostalInfo(getInstance().localizedPostalInfo == null
? localizedPostalInfo
: getInstance().localizedPostalInfo.overlay(localizedPostalInfo));
}
public Builder overlayInternationalizedPostalInfo(PostalInfo internationalizedPostalInfo) {
return setInternationalizedPostalInfo(getInstance().internationalizedPostalInfo == null
? internationalizedPostalInfo
: getInstance().internationalizedPostalInfo.overlay(internationalizedPostalInfo));
}
public Builder setVoiceNumber(ContactPhoneNumber voiceNumber) {
getInstance().voice = voiceNumber;
return this;
}
public Builder setFaxNumber(ContactPhoneNumber faxNumber) {
getInstance().fax = faxNumber;
return this;
}
public Builder setEmailAddress(String emailAddress) {
getInstance().email = emailAddress;
return this;
}
public Builder setAuthInfo(ContactAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return this;
}
public Builder setDisclose(Disclose disclose) {
getInstance().disclose = disclose;
return this;
}
@Override
public Builder wipeOut() {
this.setEmailAddress(null);
this.setFaxNumber(null);
this.setInternationalizedPostalInfo(null);
this.setLocalizedPostalInfo(null);
this.setVoiceNumber(null);
return super.wipeOut();
}
@Override
public ContactResource build() {
return super.build();
}
}
}

View file

@ -0,0 +1,136 @@
// 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 com.google.domain.registry.model.contact;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppcommon.PresenceMarker;
import com.googlecode.objectify.annotation.Embed;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
/** The "discloseType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
@Embed
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
public class Disclose extends ImmutableObject {
List<PostalInfoChoice> name;
List<PostalInfoChoice> org;
List<PostalInfoChoice> addr;
PresenceMarker voice;
PresenceMarker fax;
PresenceMarker email;
@XmlAttribute
Boolean flag;
public ImmutableList<PostalInfoChoice> getNames() {
return nullToEmptyImmutableCopy(name);
}
public ImmutableList<PostalInfoChoice> getOrgs() {
return nullToEmptyImmutableCopy(org);
}
public ImmutableList<PostalInfoChoice> getAddrs() {
return nullToEmptyImmutableCopy(addr);
}
public PresenceMarker getVoice() {
return voice;
}
public PresenceMarker getFax() {
return fax;
}
public PresenceMarker getEmail() {
return email;
}
public Boolean getFlag() {
return flag;
}
/** The "intLocType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
@Embed
public static class PostalInfoChoice extends ImmutableObject {
@XmlAttribute
PostalInfo.Type type;
public PostalInfo.Type getType() {
return type;
}
@VisibleForTesting
public static PostalInfoChoice create(PostalInfo.Type type) {
PostalInfoChoice instance = new PostalInfoChoice();
instance.type = type;
return instance;
}
}
/** A builder for {@link Disclose} since it is immutable. */
@VisibleForTesting
public static class Builder extends Buildable.Builder<Disclose> {
public Builder setNames(ImmutableList<PostalInfoChoice> names) {
getInstance().name = names;
return this;
}
public Builder setOrgs(ImmutableList<PostalInfoChoice> orgs) {
getInstance().org = orgs;
return this;
}
public Builder setAddrs(ImmutableList<PostalInfoChoice> addrs) {
getInstance().addr = addrs;
return this;
}
public Builder setVoice(PresenceMarker voice) {
getInstance().voice = voice;
return this;
}
public Builder setFax(PresenceMarker fax) {
getInstance().fax = fax;
return this;
}
public Builder setEmail(PresenceMarker email) {
getInstance().email = email;
return this;
}
public Builder setFlag(boolean flag) {
getInstance().flag = flag;
return this;
}
}
}

View file

@ -0,0 +1,122 @@
// 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 com.google.domain.registry.model.contact;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Optional;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.Buildable.Overlayable;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Implementation of both "postalInfoType" and "chgPostalInfoType" from
* {@link "http://tools.ietf.org/html/rfc5733"}.
*/
@Embed
@XmlType(propOrder = {"name", "org", "address", "type"})
public class PostalInfo extends ImmutableObject implements Overlayable<PostalInfo> {
/** The type of the address, either localized or international. */
public enum Type {
@XmlEnumValue("loc")
LOCALIZED,
@XmlEnumValue("int")
INTERNATIONALIZED
}
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String name;
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String org;
@XmlElement(name = "addr")
ContactAddress address;
@XmlAttribute
Type type;
public String getName() {
return name;
}
public String getOrg() {
return org;
}
public ContactAddress getAddress() {
return address;
}
public Type getType() {
return type;
}
@Override
public PostalInfo overlay(PostalInfo source) {
// Don't overlay the type field, as that should never change.
checkState(source.type == null || source.type == type);
return asBuilder()
.setName(Optional.fromNullable(source.getName()).or(Optional.fromNullable(name)).orNull())
.setOrg(Optional.fromNullable(source.getOrg()).or(Optional.fromNullable(org)).orNull())
.setAddress(
Optional.fromNullable(source.getAddress()).or(Optional.fromNullable(address)).orNull())
.build();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link PostalInfo}, since its changes get overlayed. */
public static class Builder extends Buildable.Builder<PostalInfo> {
public Builder() {}
private Builder(PostalInfo instance) {
super(instance);
}
public Builder setName(String name) {
getInstance().name = name;
return this;
}
public Builder setOrg(String org) {
getInstance().org = org;
return this;
}
public Builder setAddress(ContactAddress address) {
getInstance().address = address;
return this;
}
public Builder setType(Type type) {
getInstance().type = type;
return this;
}
}
}

View file

@ -0,0 +1,31 @@
// 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.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:contact-1.0",
xmlns = @XmlNs(prefix = "contact", namespaceURI = "urn:ietf:params:xml:ns:contact-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapter(UtcDateTimeAdapter.class)
package com.google.domain.registry.model.contact;
import com.google.domain.registry.xml.UtcDateTimeAdapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

View file

@ -0,0 +1,73 @@
// 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 com.google.domain.registry.model.domain;
import com.google.common.annotations.VisibleForTesting;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.contact.ContactResource;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Index;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlValue;
/**
* XML type for contact identifiers associated with a domain.
*
* @see "http://tools.ietf.org/html/rfc5731#section-2.2"
*/
@Embed
public class DesignatedContact extends ImmutableObject {
/**
* XML type for contact types. This can be either: {@code "admin"}, {@code "billing"}, or
* {@code "tech"} and corresponds to {@code contactAttrType} in {@code domain-1.0.xsd}.
*/
public enum Type {
@XmlEnumValue("admin")
ADMIN,
@XmlEnumValue("billing")
BILLING,
@XmlEnumValue("tech")
TECH,
/** The registrant type is not reflected in XML and exists only for internal use. */
REGISTRANT;
}
@VisibleForTesting
public static DesignatedContact create(Type type, ReferenceUnion<ContactResource> contact) {
DesignatedContact instance = new DesignatedContact();
instance.type = type;
instance.contactId = contact;
return instance;
}
@XmlAttribute(required = true)
Type type;
@Index
@XmlValue
ReferenceUnion<ContactResource> contactId;
public Type getType() {
return type;
}
public ReferenceUnion<ContactResource> getContactId() {
return contactId;
}
}

View file

@ -0,0 +1,173 @@
// 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 com.google.domain.registry.model.domain;
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.annotations.ExternalMessagingName;
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
import com.google.domain.registry.model.domain.launch.LaunchPhase;
import com.google.domain.registry.model.eppcommon.Trid;
import com.google.domain.registry.model.smd.EncodedSignedMark;
import com.googlecode.objectify.annotation.Cache;
import com.googlecode.objectify.annotation.EntitySubclass;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
/** An application to create a domain. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"fullyQualifiedDomainName",
"repoId",
"status",
"registrant",
"contacts",
"nameservers",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"authInfo"})
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
@EntitySubclass(index = true)
@ExternalMessagingName("application")
public class DomainApplication extends DomainBase {
/**
* The transaction id of the EPP command that created this application. This is saved off so that
* we can generate the poll message communicating the application result once it is rejected or
* allocated.
*
* <p>This field may be null for applications that were created before the field was added.
*/
@XmlTransient
Trid creationTrid;
/**
* The phase which this application is registered for. We store this only so we can return it back
* to the user on info commands.
*/
@XmlTransient
LaunchPhase phase;
/** The current status of this application. */
@XmlTransient
ApplicationStatus applicationStatus;
/** The encoded signed marks which were asserted when this application was created. */
@XmlTransient
List<EncodedSignedMark> encodedSignedMarks;
/** The amount paid at auction for the right to register the domain. Used only for reporting. */
@XmlTransient
Money auctionPrice;
@Override
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
}
public Trid getCreationTrid() {
return creationTrid;
}
public LaunchPhase getPhase() {
return phase;
}
public ApplicationStatus getApplicationStatus() {
return applicationStatus;
}
public ImmutableList<EncodedSignedMark> getEncodedSignedMarks() {
return nullToEmptyImmutableCopy(encodedSignedMarks);
}
public Money getAuctionPrice() {
return auctionPrice;
}
/** Domain applications don't expose transfer time, so override this and mark it xml transient. */
@XmlTransient
@Override
public final DateTime getLastTransferTime() {
return super.getLastTransferTime();
}
/**
* The application id is the repoId.
*/
@Override
public String getForeignKey() {
return getRepoId();
}
@Override
public DomainApplication cloneProjectedAtTime(DateTime now) {
// Applications have no grace periods and can't be transferred, so there is nothing to project.
return this;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link DomainApplication}, since it is immutable. */
public static class Builder extends DomainBase.Builder<DomainApplication, Builder> {
public Builder() {}
private Builder(DomainApplication instance) {
super(instance);
}
public Builder setCreationTrid(Trid creationTrid) {
getInstance().creationTrid = creationTrid;
return this;
}
public Builder setPhase(LaunchPhase phase) {
getInstance().phase = phase;
return this;
}
public Builder setApplicationStatus(ApplicationStatus applicationStatus) {
getInstance().applicationStatus = applicationStatus;
return this;
}
public Builder setEncodedSignedMarks(ImmutableList<EncodedSignedMark> encodedSignedMarks) {
getInstance().encodedSignedMarks = encodedSignedMarks;
return this;
}
public Builder setAuctionPrice(Money auctionPrice) {
getInstance().auctionPrice = auctionPrice;
return this;
}
}
}

View file

@ -0,0 +1,64 @@
// 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 com.google.domain.registry.model.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.googlecode.objectify.annotation.Embed;
/** A version of authInfo specifically for domains. */
@Embed
public class DomainAuthInfo extends AuthInfo {
public static DomainAuthInfo create(PasswordAuth pw) {
DomainAuthInfo instance = new DomainAuthInfo();
instance.pw = pw;
return instance;
}
@Override
public void verifyAuthorizedFor(EppResource eppResource) throws BadAuthInfoException {
DomainBase domain = (DomainBase) eppResource;
checkNotNull(getPw());
if (getRepoId() != null) {
// Make sure the repo id matches one of the contacts on the domain.
ReferenceUnion<ContactResource> foundContact = null;
for (ReferenceUnion<ContactResource> contact : domain.getReferencedContacts()) {
String contactRepoId = contact.getLinked().getKey().getName();
if (getRepoId().equals(contactRepoId)) {
foundContact = contact;
break;
}
}
if (foundContact == null) {
throw new BadAuthInfoException();
}
// Check if the password provided matches the password on the referenced contact.
if (!foundContact.getLinked().get().getAuthInfo().getPw().getValue().equals(
getPw().getValue())) {
throw new BadAuthInfoException();
}
} else {
// If not repository ID is specified, then check the password against the domain's password.
if (!domain.getAuthInfo().getPw().getValue().equals(getPw().getValue())) {
throw new BadAuthInfoException();
}
}
}
}

View file

@ -0,0 +1,294 @@
// 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 com.google.domain.registry.model.domain;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static com.google.domain.registry.model.domain.DesignatedContact.Type.REGISTRANT;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
import static com.google.domain.registry.util.CollectionUtils.union;
import static com.google.domain.registry.util.DomainNameUtils.getTldFromDomainName;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.EppResourceUtils;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.launch.LaunchNotice;
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.google.domain.registry.model.host.HostResource;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.condition.IfNull;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlTransient;
/** Shared base class for {@link DomainResource} and {@link DomainApplication}. */
@XmlTransient
@Entity
public abstract class DomainBase extends EppResource {
/**
* Fully qualified domain name (puny-coded), which serves as the foreign key for this domain.
* <p>
* This is only unique in the sense that for any given lifetime specified as the time range from
* (creationTime, deletionTime) there can only be one domain in the datastore with this name.
* However, there can be many domains with the same name and non-overlapping lifetimes.
*
* @invariant fullyQualifiedDomainName == fullyQualifiedDomainName.toLowerCase()
*/
@Index
@XmlElement(name = "name")
String fullyQualifiedDomainName;
/** The top level domain this is under, dernormalized from {@link #fullyQualifiedDomainName}. */
@Index
@XmlTransient
String tld;
/** References to hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
Set<ReferenceUnion<HostResource>> nameservers;
/**
* Associated contacts for the domain (other than registrant).
* <p>
* This field is marked with {@literal @}Ignore so that {@link DomainBase} subclasses won't
* persist it. Instead, the data in this field and in the {@link #registrant} are both stored in
* {@link DomainBase#allContacts} to allow for more efficient queries.
*/
@Ignore
@XmlElement(name = "contact")
Set<DesignatedContact> contacts;
/**
* The union of the contacts in {@link #contacts} and {@link #registrant}. This is so we can query
* across all contacts at once. It is maintained by the builder and by {@link #unpackageContacts}.
*/
@XmlTransient
Set<DesignatedContact> allContacts;
/**
* A reference to the registrant who registered this domain.
* <p>
* This field is marked with {@literal @}Ignore so that {@link DomainBase} subclasses won't
* persist it. Instead, the data in this field and in the {@link DomainBase#contacts} are
* both stored in {@link DomainBase#allContacts} to allow for more efficient queries.
*/
@Ignore
ReferenceUnion<ContactResource> registrant;
/** Authorization info (aka transfer secret) of the domain. */
DomainAuthInfo authInfo;
/**
* Data used to construct DS records for this domain.
* <p>
* This is {@literal @}XmlTransient because it needs to be returned under the "extension" tag
* of an info response rather than inside the "infData" tag.
*/
@XmlTransient
Set<DelegationSignerData> dsData;
/**
* The claims notice supplied when this application or domain was created, if there was one. It's
* {@literal @}XmlTransient because it's not returned in an info response.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
LaunchNotice launchNotice;
/**
* Name of first IDN table associated with TLD that matched the characters in this domain label.
*
* @see com.google.domain.registry.tldconfig.idn.IdnLabelValidator#findValidIdnTableForTld
*/
@IgnoreSave(IfNull.class)
@XmlTransient
String idnTableName;
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
}
public ImmutableSortedSet<DelegationSignerData> getDsData() {
return nullToEmptyImmutableSortedCopy(dsData);
}
public LaunchNotice getLaunchNotice() {
return launchNotice;
}
public String getIdnTableName() {
return idnTableName;
}
public ImmutableSet<ReferenceUnion<HostResource>> getNameservers() {
return nullToEmptyImmutableCopy(nameservers);
}
/** Loads and returns all linked nameservers. */
public ImmutableSet<HostResource> loadNameservers() {
return EppResourceUtils.loadReferencedNameservers(getNameservers());
}
public ReferenceUnion<ContactResource> getRegistrant() {
return registrant;
}
public ContactResource loadRegistrant() {
return registrant.getLinked().get();
}
public ImmutableSet<DesignatedContact> getContacts() {
return nullToEmptyImmutableCopy(contacts);
}
public AuthInfo getAuthInfo() {
return authInfo;
}
/** Returns all referenced contacts from this domain or application. */
public ImmutableSet<ReferenceUnion<ContactResource>> getReferencedContacts() {
ImmutableSet.Builder<ReferenceUnion<ContactResource>> contactsBuilder =
new ImmutableSet.Builder<>();
for (DesignatedContact designated : nullToEmptyImmutableCopy(allContacts)) {
contactsBuilder.add(designated.getContactId());
}
return contactsBuilder.build();
}
/** Loads and returns all referenced contacts from this domain or application. */
public ImmutableSet<ContactResource> loadReferencedContacts() {
return EppResourceUtils.loadReferencedContacts(getReferencedContacts());
}
public String getTld() {
return tld;
}
/**
* On load, unpackage the {@link #allContacts} field, placing the registrant into
* {@link #registrant} field and all other contacts into {@link #contacts}.
*/
@OnLoad
void unpackageContacts() {
ImmutableSet.Builder<DesignatedContact> contactsBuilder = new ImmutableSet.Builder<>();
for (DesignatedContact contact : nullToEmptyImmutableCopy(allContacts)) {
if (REGISTRANT.equals(contact.getType())){
registrant = contact.getContactId();
} else {
contactsBuilder.add(contact);
}
}
contacts = contactsBuilder.build();
}
/** An override of {@link EppResource#asBuilder} with tighter typing. */
@Override
public abstract Builder<?, ?> asBuilder();
/** A builder for constructing {@link DomainBase}, since it is immutable. */
public abstract static class Builder<T extends DomainBase, B extends Builder<?, ?>>
extends EppResource.Builder<T, B> {
protected Builder() {}
protected Builder(T instance) {
super(instance);
}
@Override
public T build() {
T instance = getInstance();
checkState(
!isNullOrEmpty(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName");
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
instance.allContacts = instance.registrant == null ? instance.contacts : union(
instance.getContacts(), DesignatedContact.create(REGISTRANT, instance.registrant));
return super.build();
}
public B setFullyQualifiedDomainName(String fullyQualifiedDomainName) {
getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName;
return thisCastToDerived();
}
public B setDsData(ImmutableSet<DelegationSignerData> dsData) {
getInstance().dsData = dsData;
return thisCastToDerived();
}
public B setRegistrant(ReferenceUnion<ContactResource> registrant) {
getInstance().registrant = registrant;
return thisCastToDerived();
}
public B setAuthInfo(DomainAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return thisCastToDerived();
}
public B setNameservers(ImmutableSet<ReferenceUnion<HostResource>> nameservers) {
getInstance().nameservers = nameservers;
return thisCastToDerived();
}
public B addNameservers(ImmutableSet<ReferenceUnion<HostResource>> nameservers) {
return setNameservers(
ImmutableSet.copyOf(union(getInstance().getNameservers(), nameservers)));
}
public B removeNameservers(ImmutableSet<ReferenceUnion<HostResource>> nameservers) {
return setNameservers(
ImmutableSet.copyOf(difference(getInstance().getNameservers(), nameservers)));
}
public B setContacts(ImmutableSet<DesignatedContact> contacts) {
getInstance().contacts = contacts;
return thisCastToDerived();
}
public B addContacts(ImmutableSet<DesignatedContact> contacts) {
return setContacts(ImmutableSet.copyOf(union(getInstance().getContacts(), contacts)));
}
public B removeContacts(ImmutableSet<DesignatedContact> contacts) {
return setContacts(ImmutableSet.copyOf(difference(getInstance().getContacts(), contacts)));
}
public B setLaunchNotice(LaunchNotice launchNotice) {
getInstance().launchNotice = launchNotice;
return thisCastToDerived();
}
public B setIdnTableName(String idnTableName) {
getInstance().idnTableName = idnTableName;
return thisCastToDerived();
}
}
}

View file

@ -0,0 +1,462 @@
// 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 com.google.domain.registry.model.domain;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.intersection;
import static com.google.domain.registry.model.index.ForeignKeyIndex.loadAndGetReference;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.google.domain.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceCheck;
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import com.google.domain.registry.model.host.HostResource;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Work;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import java.util.Set;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/** A collection of {@link DomainResource} commands. */
public class DomainCommand {
/** The default validity period (if not specified) is 1 year for all operations. */
static final Period DEFAULT_PERIOD = Period.create(1, Period.Unit.YEARS);
/**
* A common interface for {@link Create} and {@link Update} to support linking resources.
*
* @param <T> the actual type (either {@link Create} or {@link Update})
*/
public interface CreateOrUpdate<T extends CreateOrUpdate<T>> extends SingleResourceCommand {
/** Creates a copy of this command with hard links to hosts and contacts. */
public T cloneAndLinkReferences(DateTime now) throws InvalidReferenceException;
}
/** The fields on "chgType" from {@link "http://tools.ietf.org/html/rfc5731"}. */
@XmlTransient
public static class DomainCreateOrChange<B extends DomainBase.Builder<?, ?>>
extends ImmutableObject implements ResourceCreateOrChange<B> {
/** A reference to the registrant who registered this domain. */
ReferenceUnion<ContactResource> registrant;
/** Authorization info (aka transfer secret) of the domain. */
DomainAuthInfo authInfo;
@Override
public void applyTo(B builder) {
if (registrant != null) {
builder.setRegistrant(registrant);
}
if (authInfo != null) {
builder.setAuthInfo(authInfo);
}
}
}
/**
* A create command for a {@link DomainBase}, mapping "createType" from
* {@link "http://tools.ietf.org/html/rfc5731"}.
*/
@XmlRootElement
@XmlType(propOrder = {
"fullyQualifiedDomainName", "period", "nameservers", "registrant", "contacts", "authInfo" })
public static class Create
extends DomainCreateOrChange<DomainBase.Builder<?, ?>>
implements CreateOrUpdate<Create> {
/** Fully qualified domain name, which serves as a unique identifier for this domain. */
@XmlElement(name = "name")
String fullyQualifiedDomainName;
/** References to hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
Set<ReferenceUnion<HostResource>> nameservers;
/** Associated contacts for the domain (other than registrant). */
@XmlElement(name = "contact")
Set<DesignatedContact> contacts;
/** The period that this domain's state was set to last for (e.g. 1-10 years). */
Period period;
public Period getPeriod() {
return firstNonNull(period, DEFAULT_PERIOD);
}
@Override
public String getTargetId() {
return fullyQualifiedDomainName;
}
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
}
public ImmutableSet<ReferenceUnion<HostResource>> getNameservers() {
return nullSafeImmutableCopy(nameservers);
}
public ImmutableSet<DesignatedContact> getContacts() {
return nullSafeImmutableCopy(contacts);
}
public ReferenceUnion<ContactResource> getRegistrant() {
return registrant;
}
@Override
public AuthInfo getAuthInfo() {
return authInfo;
}
@Override
public void applyTo(DomainBase.Builder<?, ?> builder) {
super.applyTo(builder);
if (fullyQualifiedDomainName != null) {
builder.setFullyQualifiedDomainName(fullyQualifiedDomainName);
}
if (nameservers != null) {
builder.setNameservers(getNameservers());
}
if (contacts != null) {
builder.setContacts(getContacts());
}
}
/** Creates a copy of this {@link Create} with hard links to hosts and contacts. */
@Override
public Create cloneAndLinkReferences(DateTime now) throws InvalidReferenceException {
Create clone = clone(this);
clone.nameservers = linkHosts(clone.nameservers, now);
clone.contacts = linkContacts(clone.contacts, now);
clone.registrant = clone.registrant == null
? null : linkReference(clone.registrant, ContactResource.class, now);
return clone;
}
}
/** A delete command for a {@link DomainBase}. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
/** An info request for a {@link DomainBase}. */
@XmlRootElement
public static class Info extends ImmutableObject implements SingleResourceCommand {
/** The name of the domain to look up, and an attribute specifying the host lookup type. */
@XmlElement(name = "name")
NameWithHosts fullyQualifiedDomainName;
DomainAuthInfo authInfo;
/** Enum of the possible values for the "hosts" attribute in info flows. */
public enum HostsRequest {
@XmlEnumValue("all")
ALL,
@XmlEnumValue("del")
DELEGATED,
@XmlEnumValue("sub")
SUBORDINATE,
@XmlEnumValue("none")
NONE;
public boolean requestDelegated() {
return this == ALL || this == DELEGATED;
}
public boolean requestSubordinate() {
return this == ALL || this == SUBORDINATE;
}
}
/** Info commands use a variant syntax where the name tag has a "hosts" attribute. */
public static class NameWithHosts extends ImmutableObject {
@XmlAttribute
HostsRequest hosts;
@XmlValue
String name;
}
/** Get the enum that specifies the requested hosts (applies only to info flows). */
public HostsRequest getHostsRequest() {
// Null "hosts" is implicitly ALL.
return MoreObjects.firstNonNull(fullyQualifiedDomainName.hosts, HostsRequest.ALL);
}
@Override
public String getTargetId() {
return fullyQualifiedDomainName.name;
}
@Override
public DomainAuthInfo getAuthInfo() {
return authInfo;
}
}
/** A check request for {@link DomainResource}. */
@XmlRootElement
public static class Check extends ResourceCheck {}
/** A renew command for a {@link DomainResource}. */
@XmlRootElement
public static class Renew extends AbstractSingleResourceCommand {
@XmlElement(name = "curExpDate")
LocalDate currentExpirationDate;
/** The period that this domain's state was set to last for. */
Period period;
public LocalDate getCurrentExpirationDate() {
return currentExpirationDate;
}
public Period getPeriod() {
return firstNonNull(period, DEFAULT_PERIOD);
}
}
/** A transfer operation for a {@link DomainResource}. */
@XmlRootElement
public static class Transfer extends AbstractSingleResourceCommand {
/** The period to extend this domain's registration upon completion of the transfer. */
Period period;
/** Authorization info used to validate if client has permissions to perform this operation. */
DomainAuthInfo authInfo;
public Period getPeriod() {
return firstNonNull(period, DEFAULT_PERIOD);
}
@Override
public DomainAuthInfo getAuthInfo() {
return authInfo;
}
}
/** An update to a {@link DomainBase}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.AddRemove, DomainBase.Builder<?, ?>, Update.Change>
implements CreateOrUpdate<Update> {
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
protected AddRemove innerAdd;
@XmlElement(name = "rem")
protected AddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
return innerChange;
}
@Override
protected AddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
protected AddRemove getNullableInnerRemove() {
return innerRemove;
}
public boolean noChangesPresent() {
AddRemove emptyAddRemove = new AddRemove();
return emptyAddRemove.equals(getInnerAdd())
&& emptyAddRemove.equals(getInnerRemove())
&& new Change().equals(getInnerChange());
}
/** The inner change type on a domain update command. */
@XmlType(propOrder = {"nameservers", "contacts", "statusValues"})
public static class AddRemove extends ResourceUpdate.AddRemove {
/** References to hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
Set<ReferenceUnion<HostResource>> nameservers;
/** Associated contacts for the domain. */
@XmlElement(name = "contact")
Set<DesignatedContact> contacts;
public ImmutableSet<ReferenceUnion<HostResource>> getNameservers() {
return nullToEmptyImmutableCopy(nameservers);
}
public ImmutableSet<DesignatedContact> getContacts() {
return nullToEmptyImmutableCopy(contacts);
}
/** Creates a copy of this {@link AddRemove} with hard links to hosts and contacts. */
private AddRemove cloneAndLinkReferences(DateTime now) throws InvalidReferenceException {
AddRemove clone = clone(this);
clone.nameservers = linkHosts(clone.nameservers, now);
clone.contacts = linkContacts(clone.contacts, now);
return clone;
}
}
/** The inner change type on a domain update command. */
@XmlType(propOrder = {"registrant", "authInfo"})
public static class Change extends DomainCreateOrChange<DomainBase.Builder<?, ?>> {
public ReferenceUnion<ContactResource> getRegistrant() {
return registrant;
}
/** Creates a copy of this {@link Change} with hard links to hosts and contacts. */
Change cloneAndLinkReferences(DateTime now) throws InvalidReferenceException {
Change clone = clone(this);
clone.registrant = clone.registrant == null
? null : linkReference(clone.registrant, ContactResource.class, now);
return clone;
}
}
@Override
public void applyTo(DomainBase.Builder<?, ?> builder) throws AddRemoveSameValueException {
super.applyTo(builder);
getInnerChange().applyTo(builder);
AddRemove add = getInnerAdd();
AddRemove remove = getInnerRemove();
if (!intersection(add.getNameservers(), remove.getNameservers()).isEmpty()) {
throw new AddRemoveSameValueException();
}
builder.addNameservers(add.getNameservers());
builder.removeNameservers(remove.getNameservers());
if (!intersection(add.getContacts(), remove.getContacts()).isEmpty()) {
throw new AddRemoveSameValueException();
}
builder.addContacts(add.getContacts());
builder.removeContacts(remove.getContacts());
}
/**
* Creates a copy of this {@link Update} with hard links to hosts and contacts.
* <p>
* As a side effect, this will turn null innerAdd/innerRemove/innerChange into empty versions of
* those classes, which is harmless because the getters do that anyways.
*/
@Override
public Update cloneAndLinkReferences(DateTime now) throws InvalidReferenceException {
Update clone = clone(this);
clone.innerAdd = clone.getInnerAdd().cloneAndLinkReferences(now);
clone.innerRemove = clone.getInnerRemove().cloneAndLinkReferences(now);
clone.innerChange = clone.getInnerChange().cloneAndLinkReferences(now);
return clone;
}
}
private static Set<ReferenceUnion<HostResource>> linkHosts(
Set<ReferenceUnion<HostResource>> hosts,
DateTime now) throws InvalidReferenceException {
if (hosts == null) {
return null;
}
ImmutableSet.Builder<ReferenceUnion<HostResource>> linked = new ImmutableSet.Builder<>();
for (ReferenceUnion<HostResource> host : hosts) {
linked.add(linkReference(host, HostResource.class, now));
}
return linked.build();
}
private static Set<DesignatedContact> linkContacts(
Set<DesignatedContact> contacts, DateTime now) throws InvalidReferenceException {
if (contacts == null) {
return null;
}
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
for (DesignatedContact contact : contacts) {
linkedContacts.add(DesignatedContact.create(
contact.getType(),
linkReference(contact.getContactId(), ContactResource.class, now)));
}
return linkedContacts.build();
}
/** Turn a foreign-keyed {@link ReferenceUnion} into a linked one. */
private static <T extends EppResource> ReferenceUnion<T> linkReference(
final ReferenceUnion<T> reference, final Class<T> clazz, final DateTime now)
throws InvalidReferenceException {
if (reference.getForeignKey() == null) {
return reference;
}
Ref<T> ref = ofy().doTransactionless(new Work<Ref<T>>() {
@Override
public Ref<T> run() {
return loadAndGetReference(clazz, reference.getForeignKey(), now);
}
});
if (ref == null) {
throw new InvalidReferenceException(clazz, reference.getForeignKey());
}
return ReferenceUnion.create(ref);
}
/** Exception to throw when a referenced object does not exist. */
public static class InvalidReferenceException extends Exception {
private String foreignKey;
private Class<?> type;
InvalidReferenceException(Class<?> type, String foreignKey) {
this.type = checkNotNull(type);
this.foreignKey = foreignKey;
}
public String getForeignKey() {
return foreignKey;
}
public Class<?> getType() {
return type;
}
}
}

View file

@ -0,0 +1,41 @@
// 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 com.google.domain.registry.model.domain;
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
import org.joda.time.DateTime;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** The {@link ResponseData} returned when renewing a domain. */
@XmlRootElement(name = "renData")
@XmlType(propOrder = {"name", "expirationDate"})
public class DomainRenewData implements ResponseData {
String name;
@XmlElement(name = "exDate")
DateTime expirationDate;
public static DomainRenewData create(String name, DateTime expirationDate) {
DomainRenewData instance = new DomainRenewData();
instance.name = name;
instance.expirationDate = expirationDate;
return instance;
}
}

View file

@ -0,0 +1,414 @@
// 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 com.google.domain.registry.model.domain;
import static com.google.common.collect.Sets.intersection;
import static com.google.domain.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
import static com.google.domain.registry.model.EppResourceUtils.setAutomaticTransferSuccessProperties;
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
import static com.google.domain.registry.util.CollectionUtils.difference;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static com.google.domain.registry.util.CollectionUtils.union;
import static com.google.domain.registry.util.DateTimeUtils.earliestOf;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import static com.google.domain.registry.util.DateTimeUtils.leapSafeAddYears;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.EppResource.ForeignKeyedEppResource;
import com.google.domain.registry.model.annotations.ExternalMessagingName;
import com.google.domain.registry.model.billing.BillingEvent;
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.poll.PollMessage;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.transfer.TransferData;
import com.google.domain.registry.model.transfer.TransferStatus;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Cache;
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.condition.IfNull;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import java.util.HashSet;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
/** A persistable domain resource including mutable and non-mutable fields. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"fullyQualifiedDomainName",
"repoId",
"status",
"registrant",
"contacts",
"nameservers",
"subordinateHosts",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"registrationExpirationTime",
"lastTransferTime",
"authInfo"})
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
@EntitySubclass(index = true)
@ExternalMessagingName("domain")
public class DomainResource extends DomainBase implements ForeignKeyedEppResource {
/** The max number of years that a domain can be registered for, as set by ICANN policy. */
public static final int MAX_REGISTRATION_YEARS = 10;
/** Status values which prohibit DNS information from being published. */
private static final ImmutableSet<StatusValue> DNS_PUBLISHING_PROHIBITED_STATUSES =
ImmutableSet.of(
StatusValue.CLIENT_HOLD,
StatusValue.INACTIVE,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_HOLD);
/** Fully qualified host names of this domain's active subordinate hosts. */
@XmlElement(name = "host")
Set<String> subordinateHosts;
/** When this domain's registration will expire. */
@XmlElement(name = "exDate")
DateTime registrationExpirationTime;
/**
* The poll message associated with this domain being deleted.
* <p>
* This field should be null if the domain is not in pending delete. If it is, the field should
* refer to a {@link PollMessage} timed to when the domain is fully deleted. If the domain is
* restored, the message should be deleted.
*/
@XmlTransient
Key<PollMessage.OneTime> deletePollMessage;
/**
* The recurring billing event associated with this domain's autorenewals.
* <p>
* The recurrence should be open ended unless the domain is in pending delete or fully deleted, in
* which case it should be closed at the time the delete was requested. Whenever the domain's
* {@link #registrationExpirationTime} is changed the recurrence should be closed, a new one
* should be created, and this field should be updated to point to the new one.
*/
@XmlTransient
Ref<BillingEvent.Recurring> autorenewBillingEvent;
/**
* The recurring poll message associated with this domain's autorenewals.
* <p>
* The recurrence should be open ended unless the domain is in pending delete or fully deleted, in
* which case it should be closed at the time the delete was requested. Whenever the domain's
* {@link #registrationExpirationTime} is changed the recurrence should be closed, a new one
* should be created, and this field should be updated to point to the new one.
*/
@XmlTransient
Ref<PollMessage.Autorenew> autorenewPollMessage;
/** The unexpired grace periods for this domain (some of which may not be active yet). */
@XmlTransient
Set<GracePeriod> gracePeriods;
/**
* The id of the signed mark that was used to create the sunrise application for this domain.
* Will only be populated for domains allocated from a sunrise application.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
String smdId;
/**
* The time that the application used to allocate this domain was created. Will only be populated
* for domains allocated from an application.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
DateTime applicationTime;
/**
* A reference to the application used to allocate this domain. Will only be populated for domains
* allocated from an application.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
Ref<DomainApplication> application;
public ImmutableSet<String> getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts);
}
public DateTime getRegistrationExpirationTime() {
return registrationExpirationTime;
}
public Key<PollMessage.OneTime> getDeletePollMessage() {
return deletePollMessage;
}
public Ref<BillingEvent.Recurring> getAutorenewBillingEvent() {
return autorenewBillingEvent;
}
public Ref<PollMessage.Autorenew> getAutorenewPollMessage() {
return autorenewPollMessage;
}
public ImmutableSet<GracePeriod> getGracePeriods() {
return nullToEmptyImmutableCopy(gracePeriods);
}
public String getSmdId() {
return smdId;
}
public DateTime getApplicationTime() {
return applicationTime;
}
public Ref<DomainApplication> getApplication() {
return application;
}
@Override
public String getForeignKey() {
return fullyQualifiedDomainName;
}
/** Returns true if DNS information should be published for the given domain. */
public boolean shouldPublishToDns() {
return intersection(getStatusValues(), DNS_PUBLISHING_PROHIBITED_STATUSES).isEmpty();
}
/**
* Returns the Registry Grace Period Statuses for this domain.
* <p>
* This collects all statuses from the domain's {@link GracePeriod}s and also adds the
* PENDING_DELETE status if needed.
*/
public ImmutableSet<GracePeriodStatus> getGracePeriodStatuses() {
Set<GracePeriodStatus> gracePeriodStatuses = new HashSet<>();
for (GracePeriod gracePeriod : getGracePeriods()) {
gracePeriodStatuses.add(gracePeriod.getType());
}
if (getStatusValues().contains(StatusValue.PENDING_DELETE)
&& !gracePeriodStatuses.contains(GracePeriodStatus.REDEMPTION)) {
gracePeriodStatuses.add(GracePeriodStatus.PENDING_DELETE);
}
return ImmutableSet.copyOf(gracePeriodStatuses);
}
/**
* The logic in this method, which handles implicit server approval of transfers, very closely
* parallels the logic in {@code DomainTransferApproveFlow} which handles explicit client
* approvals.
*/
@Override
public DomainResource cloneProjectedAtTime(final DateTime now) {
TransferData transferData = getTransferData();
DateTime transferExpirationTime = transferData.getPendingTransferExpirationTime();
// If there's a pending transfer that has expired, handle it.
if (TransferStatus.PENDING.equals(transferData.getTransferStatus())
&& isBeforeOrAt(transferExpirationTime, now)) {
// Project until just before the transfer time. This will handle the case of an autorenew
// before the transfer was even requested or during the request period.
// If the transfer time is precisely the moment that the domain expires, there will not be an
// autorenew billing event (since we end the recurrence at transfer time and recurrences are
// exclusive of their ending), and we can just proceed with the transfer.
DomainResource domainAtTransferTime =
cloneProjectedAtTime(transferExpirationTime.minusMillis(1));
// If we are within an autorenew grace period, the transfer will subsume the autorenew. There
// will already be a cancellation written in advance by the transfer request flow, so we don't
// need to worry about billing, but we do need to reduce the number of years added to the
// expiration time by one to account for the year added by the autorenew.
int extraYears = transferData.getExtendedRegistrationYears();
if (domainAtTransferTime.getGracePeriodStatuses().contains(GracePeriodStatus.AUTO_RENEW)) {
extraYears--;
}
// Set the expiration, autorenew events, and grace period for the transfer. (Transfer ends
// all other graces).
Builder builder = domainAtTransferTime.asBuilder()
// Extend the registration by the correct number of years from the expiration time that
// was current on the domain right before the transfer, capped at 10 years from the
// moment of the transfer.
.setRegistrationExpirationTime(extendRegistrationWithCap(
transferExpirationTime,
domainAtTransferTime.getRegistrationExpirationTime(),
extraYears))
// Set the speculatively-written new autorenew events as the domain's autorenew events.
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage())
// Set the grace period using a ref to the prescheduled transfer billing event. Not using
// GracePeriod.forBillingEvent() here in order to avoid the actual datastore fetch.
.setGracePeriods(ImmutableSet.of(GracePeriod.create(
GracePeriodStatus.TRANSFER,
transferExpirationTime.plus(Registry.get(getTld()).getTransferGracePeriodLength()),
transferData.getGainingClientId(),
Ref.create(transferData.getServerApproveBillingEvent().key()))));
// Set all remaining transfer properties.
setAutomaticTransferSuccessProperties(builder, transferData);
// Finish projecting to now.
return builder.build().cloneProjectedAtTime(now);
}
// There is no transfer. Do any necessary autorenews.
Builder builder = asBuilder();
if (isBeforeOrAt(registrationExpirationTime, now)) {
// Autorenew by the number of years between the old expiration time and now.
DateTime lastAutorenewTime = leapSafeAddYears(
registrationExpirationTime,
new Interval(registrationExpirationTime, now).toPeriod().getYears());
DateTime newExpirationTime = lastAutorenewTime.plusYears(1);
builder
.setRegistrationExpirationTime(newExpirationTime)
.addGracePeriod(GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
lastAutorenewTime.plus(Registry.get(getTld()).getAutoRenewGracePeriodLength()),
getCurrentSponsorClientId(),
Ref.create(autorenewBillingEvent.key())));
}
// Remove any grace periods that have expired.
DomainResource almostBuilt = builder.build();
builder = almostBuilt.asBuilder();
for (GracePeriod gracePeriod : almostBuilt.getGracePeriods()) {
if (isBeforeOrAt(gracePeriod.getExpirationTime(), now)) {
builder.removeGracePeriod(gracePeriod);
}
}
// Handle common properties like setting or unsetting linked status. This also handles the
// general case of pending transfers for other resource types, but since we've always handled
// a pending transfer by this point that's a no-op for domains.
projectResourceOntoBuilderAtTime(almostBuilt, builder, now);
return builder.build();
}
/** Return what the expiration time would be if the given number of years were added to it. */
public static DateTime extendRegistrationWithCap(
DateTime now, DateTime currentExpirationTime, Integer extendedRegistrationYears) {
// We must cap registration at the max years (aka 10), even if that truncates the last year.
return earliestOf(
leapSafeAddYears(
currentExpirationTime, Optional.fromNullable(extendedRegistrationYears).or(0)),
leapSafeAddYears(now, MAX_REGISTRATION_YEARS));
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link DomainResource}, since it is immutable. */
public static class Builder extends DomainBase.Builder<DomainResource, Builder> {
public Builder() {}
private Builder(DomainResource instance) {
super(instance);
}
@Override
public DomainResource build() {
// A DomainResource has status INACTIVE if there are no nameservers.
if (getInstance().getNameservers().isEmpty()) {
addStatusValue(StatusValue.INACTIVE);
} else { // There are nameservers, so make sure INACTIVE isn't there.
removeStatusValue(StatusValue.INACTIVE);
}
// This must be called after we add or remove INACTIVE, since that affects whether we get OK.
return super.build();
}
public Builder setSubordinateHosts(ImmutableSet<String> subordinateHosts) {
getInstance().subordinateHosts = subordinateHosts;
return thisCastToDerived();
}
public Builder addSubordinateHost(String hostToAdd) {
return setSubordinateHosts(ImmutableSet.copyOf(
union(getInstance().getSubordinateHosts(), hostToAdd)));
}
public Builder removeSubordinateHost(String hostToRemove) {
return setSubordinateHosts(ImmutableSet.copyOf(
difference(getInstance().getSubordinateHosts(), hostToRemove)));
}
public Builder setRegistrationExpirationTime(DateTime registrationExpirationTime) {
getInstance().registrationExpirationTime = registrationExpirationTime;
return this;
}
public Builder setDeletePollMessage(Key<PollMessage.OneTime> deletePollMessage) {
getInstance().deletePollMessage = deletePollMessage;
return this;
}
public Builder setAutorenewBillingEvent(Ref<BillingEvent.Recurring> autorenewBillingEvent) {
getInstance().autorenewBillingEvent = autorenewBillingEvent;
return this;
}
public Builder setAutorenewPollMessage(Ref<PollMessage.Autorenew> autorenewPollMessage) {
getInstance().autorenewPollMessage = autorenewPollMessage;
return this;
}
public Builder setSmdId(String smdId) {
getInstance().smdId = smdId;
return this;
}
public Builder setApplicationTime(DateTime applicationTime) {
getInstance().applicationTime = applicationTime;
return this;
}
public Builder setApplication(Ref<DomainApplication> application) {
getInstance().application = application;
return this;
}
public Builder setGracePeriods(ImmutableSet<GracePeriod> gracePeriods) {
getInstance().gracePeriods = gracePeriods;
return this;
}
public Builder addGracePeriod(GracePeriod gracePeriod) {
getInstance().gracePeriods = union(getInstance().getGracePeriods(), gracePeriod);
return this;
}
public Builder removeGracePeriod(GracePeriod gracePeriod) {
getInstance().gracePeriods = difference(getInstance().getGracePeriods(), gracePeriod);
return this;
}
}
}

View file

@ -0,0 +1,158 @@
// 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 com.google.domain.registry.model.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.billing.BillingEvent;
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Embed;
import org.joda.time.DateTime;
import javax.annotation.Nullable;
/**
* A domain grace period with an expiration time.
* <p>
* When a grace period expires, it is lazily removed from the {@link DomainResource} the next time
* the resource is loaded from the datastore.
*/
@Embed
public class GracePeriod extends ImmutableObject {
/** The type of grace period. */
GracePeriodStatus type;
/** When the grace period ends. */
DateTime expirationTime;
/** The registrar to bill. */
String clientId;
/**
* The one-time billing event corresponding to the action that triggered this grace period, or
* null if not applicable. Not set for autorenew grace periods (which instead use the field
* {@code billingEventRecurring}) or for redemption grace periods (since deletes have no cost).
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
Ref<BillingEvent.OneTime> billingEventOneTime = null;
/**
* The recurring billing event corresponding to the action that triggered this grace period, if
* applicable - i.e. if the action was an autorenew - or null in all other cases.
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
Ref<BillingEvent.Recurring> billingEventRecurring = null;
public GracePeriodStatus getType() {
// NB: We implicitly convert SUNRUSH_ADD to ADD, since they should be functionally equivalent.
return type == GracePeriodStatus.SUNRUSH_ADD ? GracePeriodStatus.ADD : type;
}
public boolean isSunrushAddGracePeriod() {
return type == GracePeriodStatus.SUNRUSH_ADD;
}
public DateTime getExpirationTime() {
return expirationTime;
}
public String getClientId() {
return clientId;
}
/** Returns true if this GracePeriod has an associated BillingEvent; i.e. if it's refundable. */
public boolean hasBillingEvent() {
return billingEventOneTime != null || billingEventRecurring != null;
}
/**
* Returns the one time billing event. The value will only be non-null if the type of this grace
* period is not AUTO_RENEW.
*/
public Ref<BillingEvent.OneTime> getOneTimeBillingEvent() {
return billingEventOneTime;
}
/**
* Returns the recurring billing event. The value will only be non-null if the type of this grace
* period is AUTO_RENEW.
*/
public Ref<BillingEvent.Recurring> getRecurringBillingEvent() {
return billingEventRecurring;
}
private static GracePeriod createInternal(
GracePeriodStatus type,
DateTime expirationTime,
String clientId,
@Nullable Ref<BillingEvent.OneTime> billingEventOneTime,
@Nullable Ref<BillingEvent.Recurring> billingEventRecurring) {
checkArgument((billingEventOneTime == null) || (billingEventRecurring == null),
"A grace period can have at most one billing event");
checkArgument((billingEventRecurring != null) == (GracePeriodStatus.AUTO_RENEW.equals(type)),
"Recurring billing events must be present on (and only on) autorenew grace periods");
GracePeriod instance = new GracePeriod();
instance.type = checkArgumentNotNull(type);
instance.expirationTime = checkArgumentNotNull(expirationTime);
instance.clientId = checkArgumentNotNull(clientId);
instance.billingEventOneTime = billingEventOneTime;
instance.billingEventRecurring = billingEventRecurring;
return instance;
}
/** Create a GracePeriod for an (optional) OneTime billing event.
*
* <p>Normal callers should always use {@link #forBillingEvent} instead, assuming they do not
* need to avoid loading the BillingEvent from datastore. This method should typically be
* called only from test code to explicitly construct GracePeriods.
*/
public static GracePeriod create(
GracePeriodStatus type,
DateTime expirationTime,
String clientId,
@Nullable Ref<BillingEvent.OneTime> billingEventOneTime) {
return createInternal(type, expirationTime, clientId, billingEventOneTime, null);
}
/** Create a GracePeriod for a Recurring billing event. */
public static GracePeriod createForRecurring(
GracePeriodStatus type,
DateTime expirationTime,
String clientId,
Ref<BillingEvent.Recurring> billingEventRecurring) {
checkArgumentNotNull(billingEventRecurring);
return createInternal(type, expirationTime, clientId, null, billingEventRecurring);
}
/** Create a GracePeriod with no billing event. */
public static GracePeriod createWithoutBillingEvent(
GracePeriodStatus type, DateTime expirationTime, String clientId) {
return createInternal(type, expirationTime, clientId, null, null);
}
/** Constructs a GracePeriod of the given type from the provided one-time BillingEvent. */
public static GracePeriod forBillingEvent(
GracePeriodStatus type, BillingEvent.OneTime billingEvent) {
return create(
type, billingEvent.getBillingTime(), billingEvent.getClientId(), Ref.create(billingEvent));
}
}

View file

@ -0,0 +1,59 @@
// 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 com.google.domain.registry.model.domain;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlValue;
/** The "periodType" from {@link "http://tools.ietf.org/html/rfc5731"}. */
@Embed
public class Period extends ImmutableObject {
@XmlAttribute
Unit unit;
@XmlValue
Integer value;
public Unit getUnit() {
return unit;
}
public Integer getValue() {
return value;
}
/** The unit enum. */
public enum Unit {
@XmlEnumValue("y")
YEARS,
@XmlEnumValue("m")
MONTHS,
}
public static Period create(int value, Unit unit) {
Period instance = new Period();
instance.value = value;
instance.unit = unit;
return instance;
}
}

View file

@ -0,0 +1,104 @@
// 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 com.google.domain.registry.model.domain;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.host.HostResource;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import java.io.Serializable;
import javax.xml.bind.annotation.adapters.XmlAdapter;
/**
* A "union" type to represent referenced objects as either a foreign key or as a link to another
* object in the datastore.
* <p>
* This type always marshals as the "foreign key". When it is explicitly storing a foreign key it
* gets the value from its own string field. When it is linked to another object, it gets the value
* from the other object.
* <p>
* When a {@link ReferenceUnion} comes in from Epp, either in an update or a delete, it fills in the
* "foreign key" string field, but as soon as the relevant Flow runs it deletes that field and
* replaces it with a linked {@link Ref} to the object named by that string. We can't do this in a
* {@code XmlJavaTypeAdapter} because failing a lookup is a business logic error, not a failure to
* parse the XML.
*
* @param <T> the type being referenced
*/
@Embed
public class ReferenceUnion<T extends EppResource> extends ImmutableObject implements Serializable {
@Index
Ref<T> linked;
/** This is never persisted, and only ever populated to marshal or unmarshal to or from XML. */
@Ignore
String foreignKey;
public Ref<T> getLinked() {
return linked;
}
public String getForeignKey() {
return foreignKey;
}
/** An adapter that is aware of the union inside {@link ReferenceUnion}. */
public static class Adapter<T extends EppResource>
extends XmlAdapter<String, ReferenceUnion<T>> {
@Override
public ReferenceUnion<T> unmarshal(String foreignKey) throws Exception {
return ReferenceUnion.<T>create(foreignKey);
}
@Override
public String marshal(ReferenceUnion<T> reference) throws Exception {
return reference.getForeignKey() == null
? reference.getLinked().get().getForeignKey()
: reference.getForeignKey();
}
}
/** An adapter for references to contacts. */
static class ContactReferenceUnionAdapter extends ReferenceUnion.Adapter<ContactResource>{}
/** An adapter for references to hosts. */
static class HostReferenceUnionAdapter extends ReferenceUnion.Adapter<HostResource>{}
public static <T extends EppResource> ReferenceUnion<T> create(String foreignKey) {
ReferenceUnion<T> instance = new ReferenceUnion<>();
instance.foreignKey = foreignKey;
return instance;
}
public static <T extends EppResource> ReferenceUnion<T> create(Ref<T> linked) {
ReferenceUnion<T> instance = new ReferenceUnion<>();
instance.linked = linked;
return instance;
}
/** Convenience method. */
public static <T extends EppResource> ReferenceUnion<T> create(T resource) {
return create(Ref.create(resource));
}
}

View file

@ -0,0 +1,85 @@
// 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 com.google.domain.registry.model.domain.allocate;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.domain.launch.LaunchNotice;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import org.joda.time.DateTime;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents an allocate extension that will be present on EPP commands to
* allocate a domain from an application.
* <p>
* This object holds XML data which JAXB will unmarshal from an EPP domain create command extension.
* The XML will have the following enclosing structure:
*
* <pre> {@code
* <epp>
* <command>
* <create>
* <!-- domain create XML data -->
* </create>
* <extension>
* <allocate:create>
* <!-- allocate create XML payload data -->
* </allocate:create>
* </extension>
* </command>
* </epp>
* } </pre>
*
* @see CommandExtension
*/
@XmlRootElement(name = "create")
public class AllocateCreateExtension extends ImmutableObject implements CommandExtension {
/** Holds the ROID of the application that was used to allocate this domain. */
String applicationRoid;
/** The time that the application was created. */
DateTime applicationTime;
/**
* Signed mark identifier for this create. Only present when allocating a domain from a sunrise
* application.
*/
String smdId;
/**
* The claims notice for this create. Only present when allocating a domain from a landrush
* application that matches a pre-registered mark in the TMCH.
*/
LaunchNotice notice;
public String getApplicationRoid() {
return applicationRoid;
}
public DateTime getApplicationTime() {
return applicationTime;
}
public String getSmdId() {
return smdId;
}
public LaunchNotice getNotice() {
return notice;
}
}

View file

@ -0,0 +1,31 @@
// 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.
@XmlSchema(
namespace = "urn:google:params:xml:ns:allocate-1.0",
xmlns = @XmlNs(prefix = "allocate", namespaceURI = "urn:google:params:xml:ns:allocate-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapter(UtcDateTimeAdapter.class)
package com.google.domain.registry.model.domain.allocate;
import com.google.domain.registry.xml.UtcDateTimeAdapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

View file

@ -0,0 +1,86 @@
// 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 com.google.domain.registry.model.domain.fee;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.xml.PeriodAdapter;
import org.joda.time.Period;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** Base class for the fee and credit types. */
@XmlTransient
public class BaseFee extends ImmutableObject {
/** Enum for when a fee is applied. */
public enum AppliedType {
@XmlEnumValue("immediate")
IMMEDIATE,
@XmlEnumValue("delayed")
DELAYED
}
@XmlAttribute
String description;
@XmlAttribute
AppliedType applied;
@XmlAttribute(name = "grace-period")
@XmlJavaTypeAdapter(PeriodAdapter.class)
Period gracePeriod;
@XmlAttribute
Boolean refundable;
@XmlValue
BigDecimal cost;
public String getDescription() {
return description;
}
public AppliedType getApplied() {
return firstNonNull(applied, AppliedType.IMMEDIATE);
}
public Period getGracePeriod() {
return firstNonNull(gracePeriod, Period.ZERO);
}
public Boolean getRefundable() {
return firstNonNull(refundable, true);
}
public BigDecimal getCost() {
return cost;
}
public boolean hasDefaultAttributes() {
return getGracePeriod().equals(Period.ZERO)
&& getApplied().equals(AppliedType.IMMEDIATE)
&& getRefundable();
}
}

View file

@ -0,0 +1,48 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.ImmutableObject;
import org.joda.money.CurrencyUnit;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
/** Base class for general transform commands with fees (create, renew, update, transfer). */
@XmlTransient
public class BaseFeeCommand extends ImmutableObject {
/** The currency of the fee. */
CurrencyUnit currency;
/**
* The magnitude of the fee, in the specified units, with an optional description.
* <p>
* This is a list because a single operation can involve multiple fees.
*/
@XmlElement(name = "fee")
List<Fee> fees;
public CurrencyUnit getCurrency() {
return currency;
}
public List<Fee> getFees() {
return fees;
}
}

View file

@ -0,0 +1,54 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.Buildable.GenericBuilder;
import com.google.domain.registry.model.ImmutableObject;
import org.joda.money.CurrencyUnit;
import java.util.List;
import javax.xml.bind.annotation.XmlTransient;
/** Base class for fee responses on general transform commands (create, update, renew, transfer). */
@XmlTransient
public class BaseFeeCommandResponse extends ImmutableObject {
/** The currency of the fee. */
CurrencyUnit currency;
/**
* The magnitude of the fee, in the specified units, with an optional description.
* <p>
* This is a list because a single operation can involve multiple fees.
*/
List<Fee> fee;
/** Abstract builder for {@link BaseFeeCommandResponse}. */
public abstract static class Builder<T extends BaseFeeCommandResponse, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
public B setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return thisCastToDerived();
}
public B setFee(ImmutableList<Fee> fee) {
getInstance().fee = fee;
return thisCastToDerived();
}
}
}

View file

@ -0,0 +1,52 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.common.base.Optional;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.domain.Period;
import org.joda.money.CurrencyUnit;
import javax.xml.bind.annotation.XmlTransient;
/** Base class for the fee requests on check and info. */
@XmlTransient
public class BaseFeeRequest extends ImmutableObject {
/** The default validity period (if not specified) is 1 year for all operations. */
static final Period DEFAULT_PERIOD = Period.create(1, Period.Unit.YEARS);
/** A three-character ISO4217 currency code. */
CurrencyUnit currency;
/** The command being checked. */
FeeCommandDescriptor command;
/** The period for the command being checked. */
Period period;
public CurrencyUnit getCurrency() {
return currency;
}
public FeeCommandDescriptor getCommand() {
return command;
}
public Period getPeriod() {
return Optional.fromNullable(period).or(DEFAULT_PERIOD);
}
}

View file

@ -0,0 +1,91 @@
// 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 com.google.domain.registry.model.domain.fee;
import static com.google.domain.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.Buildable.GenericBuilder;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.domain.Period;
import org.joda.money.CurrencyUnit;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
/** Base class for the fee responses on check and info. */
@XmlTransient
public class BaseFeeResponse extends ImmutableObject {
/** The currency of the fee. */
CurrencyUnit currency;
/** The command that was checked. */
FeeCommandDescriptor command;
/** The period that was checked. */
Period period;
/**
* The magnitude of the fee, in the specified units, with an optional description.
* <p>
* This is a list because a single operation can involve multiple fees.
*/
List<Fee> fee;
/**
* The type of the fee.
* <p>
* We will use "premium" for fees on premium names, and omit the field otherwise.
*/
@XmlElement(name = "class")
String feeClass;
public String getFeeClass() {
return feeClass;
}
/** Abstract builder for {@link BaseFeeResponse}. */
public abstract static class Builder<T extends BaseFeeResponse, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
public B setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return thisCastToDerived();
}
public B setCommand(FeeCommandDescriptor command) {
getInstance().command = command;
return thisCastToDerived();
}
public B setPeriod(Period period) {
getInstance().period = period;
return thisCastToDerived();
}
public B setFee(Fee... fee) {
// If there are no fees, set the field to null to suppress the 'fee' section in the xml.
getInstance().fee = forceEmptyToNull(ImmutableList.copyOf(fee));
return thisCastToDerived();
}
public B setClass(String feeClass) {
getInstance().feeClass = feeClass;
return thisCastToDerived();
}
}
}

View file

@ -0,0 +1,29 @@
// 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 com.google.domain.registry.model.domain.fee;
import static com.google.common.base.Preconditions.checkNotNull;
import java.math.BigDecimal;
/** A credit, in currency units specified elsewhere in the xml, and with an optional description. */
public class Credit extends BaseFee {
public static Credit create(BigDecimal cost, String description) {
Credit instance = new Credit();
instance.cost = checkNotNull(cost);
instance.description = description;
return instance;
}
}

View file

@ -0,0 +1,29 @@
// 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 com.google.domain.registry.model.domain.fee;
import static com.google.common.base.Preconditions.checkNotNull;
import java.math.BigDecimal;
/** A fee, in currency units specified elsewhere in the xml, and with an optional description. */
public class Fee extends BaseFee {
public static Fee create(BigDecimal cost, String description) {
Fee instance = new Fee();
instance.cost = checkNotNull(cost);
instance.description = description;
return instance;
}
}

View file

@ -0,0 +1,54 @@
// 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 com.google.domain.registry.model.domain.fee;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.domain.Period;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain check commands. */
@XmlRootElement(name = "check")
public class FeeCheckExtension extends ImmutableObject implements CommandExtension {
/** The default validity period (if not specified) is 1 year for all operations. */
static final Period DEFAULT_PERIOD = Period.create(1, Period.Unit.YEARS);
@XmlElement(name = "domain")
Set<DomainCheck> domains;
public ImmutableSet<DomainCheck> getDomains() {
return nullToEmptyImmutableCopy(domains);
}
/** A check request for the fee to perform a given command on a given domain. */
@XmlType(propOrder = {"name", "currency", "command", "period"})
public static class DomainCheck extends BaseFeeRequest {
/** The fully qualified domain name being checked. */
String name;
public String getName() {
return name;
}
}
}

View file

@ -0,0 +1,62 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain check commands.
*/
@XmlRootElement(name = "chkData")
public class FeeCheckResponseExtension extends ImmutableObject implements ResponseExtension {
/** Check responses. */
@XmlElement(name = "cd")
ImmutableList<FeeCheck> feeChecks;
@VisibleForTesting
public ImmutableList<FeeCheck> getChecks() {
return feeChecks;
}
public static FeeCheckResponseExtension create(ImmutableList<FeeCheck> feeChecks) {
FeeCheckResponseExtension instance = new FeeCheckResponseExtension();
instance.feeChecks = feeChecks;
return instance;
}
/** The response for a check on a single resource. */
@XmlType(propOrder = {"name", "currency", "command", "period", "fee", "feeClass"})
public static class FeeCheck extends BaseFeeResponse {
/** The name of the domain that was checked, with an attribute indicating if it is premium. */
String name;
/** A builder for {@link FeeCheck}. */
public static class Builder extends BaseFeeResponse.Builder<FeeCheck, Builder> {
public Builder setName(String name) {
getInstance().name = name;
return this;
}
}
}
}

View file

@ -0,0 +1,67 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.common.base.CharMatcher;
import com.google.domain.registry.model.ImmutableObject;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
/** A command name along with the launch phase and subphase it is to be executed in. */
public class FeeCommandDescriptor extends ImmutableObject {
/** The name of a command that might have an associated fee. */
public enum CommandName {
CREATE,
RENEW,
TRANSFER,
RESTORE,
UNKNOWN
}
@XmlAttribute
String phase;
@XmlAttribute
String subphase;
@XmlValue
String command;
public String getPhase() {
return phase;
}
public String getSubphase() {
return subphase;
}
public String getUnparsedCommandName() {
return command;
}
public CommandName getCommand() {
// Require the xml string to be lowercase.
if (command != null && CharMatcher.javaLowerCase().matchesAllOf(command)) {
try {
return CommandName.valueOf(command.toUpperCase());
} catch (IllegalArgumentException e) {
// Swallow this and return UNKNOWN below because there's no matching CommandName.
}
}
return CommandName.UNKNOWN;
}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain create commands. */
@XmlRootElement(name = "create")
@XmlType(propOrder = {"currency", "fees"})
public class FeeCreateExtension extends BaseFeeCommand implements CommandExtension {}

View file

@ -0,0 +1,33 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain create commands.
*/
@XmlRootElement(name = "creData")
@XmlType(propOrder = {"currency", "fee"})
public class FeeCreateResponseExtension extends BaseFeeCommandResponse
implements ResponseExtension {
/** A builder for {@link FeeCreateResponseExtension}. */
public static class Builder
extends BaseFeeCommandResponse.Builder<FeeCreateResponseExtension, Builder> {}
}

View file

@ -0,0 +1,59 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.Buildable.GenericBuilder;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import org.joda.money.CurrencyUnit;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain create commands.
*/
@XmlRootElement(name = "delData")
public class FeeDeleteResponseExtension extends ImmutableObject implements ResponseExtension {
/** The currency of the credit(s). */
CurrencyUnit currency;
/**
* The magnitude of the credit(s), in the specified units, with an optional description.
* <p>
* This is a list because a single delete can receive multiple credits.
*/
@XmlElement(name = "credit")
List<Credit> credits;
/** Builder for {@link FeeDeleteResponseExtension}. */
public static class Builder extends GenericBuilder<FeeDeleteResponseExtension, Builder> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return thisCastToDerived();
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return thisCastToDerived();
}
}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain info commands. */
@XmlRootElement(name = "info")
@XmlType(propOrder = {"currency", "command", "period"})
public class FeeInfoExtension extends BaseFeeRequest implements CommandExtension {}

View file

@ -0,0 +1,31 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain info commands.
*/
@XmlRootElement(name = "infData")
@XmlType(propOrder = {"currency", "command", "period", "fee", "feeClass"})
public class FeeInfoResponseExtension extends BaseFeeResponse implements ResponseExtension {
/** A builder for {@link FeeInfoResponseExtension}. */
public static class Builder extends BaseFeeResponse.Builder<FeeInfoResponseExtension, Builder> {}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain renew commands. */
@XmlRootElement(name = "renew")
@XmlType(propOrder = {"currency", "fees"})
public class FeeRenewExtension extends BaseFeeCommand implements CommandExtension {}

View file

@ -0,0 +1,33 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain create commands.
*/
@XmlRootElement(name = "renData")
@XmlType(propOrder = {"currency", "fee"})
public class FeeRenewResponseExtension extends BaseFeeCommandResponse
implements ResponseExtension {
/** A builder for {@link FeeRenewResponseExtension}. */
public static class Builder
extends BaseFeeCommandResponse.Builder<FeeRenewResponseExtension, Builder> {}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain transfer requests. */
@XmlRootElement(name = "transfer")
@XmlType(propOrder = {"currency", "fees"})
public class FeeTransferExtension extends BaseFeeCommand implements CommandExtension {}

View file

@ -0,0 +1,33 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain transfer requests.
*/
@XmlRootElement(name = "trnData")
@XmlType(propOrder = {"currency", "fee"})
public class FeeTransferResponseExtension extends BaseFeeCommandResponse
implements ResponseExtension {
/** A builder for {@link FeeTransferResponseExtension}. */
public static class Builder
extends BaseFeeCommandResponse.Builder<FeeTransferResponseExtension, Builder> {}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain update commands. */
@XmlRootElement(name = "update")
@XmlType(propOrder = {"currency", "fees"})
public class FeeUpdateExtension extends BaseFeeCommand implements CommandExtension {}

View file

@ -0,0 +1,33 @@
// 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 com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain update commands.
*/
@XmlRootElement(name = "updData")
@XmlType(propOrder = {"currency", "fee"})
public class FeeUpdateResponseExtension extends BaseFeeCommandResponse
implements ResponseExtension {
/** A builder for {@link FeeUpdateResponseExtension}. */
public static class Builder
extends BaseFeeCommandResponse.Builder<FeeUpdateResponseExtension, Builder> {}
}

View file

@ -0,0 +1,31 @@
// 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.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:fee-0.6",
xmlns = @XmlNs(prefix = "fee", namespaceURI = "urn:ietf:params:xml:ns:fee-0.6"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapter(CurrencyUnitAdapter.class)
package com.google.domain.registry.model.domain.fee;
import com.google.domain.registry.model.translators.CurrencyUnitAdapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

View file

@ -0,0 +1,23 @@
// 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 com.google.domain.registry.model.domain.launch;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
/** Marker interface for EPP extensions which override the EPP notion of id with their own. */
public interface ApplicationIdTargetExtension extends CommandExtension {
/** Get the application id to use as the resource id for commands using this extension. */
String getApplicationId();
}

View file

@ -0,0 +1,58 @@
// 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 com.google.domain.registry.model.domain.launch;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import com.google.domain.registry.model.translators.EnumToAttributeAdapter;
import com.google.domain.registry.model.translators.EnumToAttributeAdapter.EppEnum;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Represents the EPP application status.
* <p>
* These values are never read from a command and only used in responses, so, we don't need to model
* anything we don't output. We don't model the CUSTOM status because we don't use it. This allows
* us to also avoid modeling the "name" attribute which is only used with CUSTOM. We don't model the
* "lang" attribute because we only support English and that's the default.
* <p>
* Given all of this, we can use {@link EnumToAttributeAdapter} to make this code very simple.
*
* @see "http://tools.ietf.org/html/draft-tan-epp-launchphase-11#section-2.3"
*/
@XmlJavaTypeAdapter(EnumToAttributeAdapter.class)
public enum ApplicationStatus implements EppEnum {
ALLOCATED,
INVALID,
PENDING_ALLOCATION,
PENDING_VALIDATION,
REJECTED,
VALIDATED;
@Override
public String getXmlName() {
return UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
}
/**
* Returns true if this status is a final status - that is, it should not transition to any other
* application status after this one.
*/
public boolean isFinalStatus() {
return ALLOCATED.equals(this) || REJECTED.equals(this);
}
}

View file

@ -0,0 +1,83 @@
// 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 com.google.domain.registry.model.domain.launch;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents a launch extension that may be present on EPP domain check
* commands.
* <p>
* This object holds XML data which JAXB will unmarshal from an EPP domain check command extension.
* The XML will have the following enclosing structure:
*
* <pre> {@code
* <epp>
* <command>
* <create>
* <!-- domain check XML data -->
* </create>
* <extension>
* <launch:check>
* <!-- launch check XML payload data -->
* </launch:check>
* </extension>
* </command>
* </epp>
* } </pre>
*
* @see CommandExtension
*/
@XmlRootElement(name = "check")
public class LaunchCheckExtension extends ImmutableObject implements CommandExtension {
/** The default check type is "claims" if not specified. */
private static final CheckType DEFAULT_CHECK_TYPE = CheckType.CLAIMS;
/** Type of domain check being requested. */
public enum CheckType {
/** A check to see if the specified domain names are available to be provisioned. */
@XmlEnumValue("avail")
AVAILABILITY,
/** A check to see if there are matching trademarks on the specified domain names. */
@XmlEnumValue("claims")
CLAIMS;
}
/**
* The launch phase this command is intended to run against. If it does not match the server's
* current launch phase, the command will be rejected.
*/
LaunchPhase phase;
@XmlAttribute
CheckType type;
public CheckType getCheckType() {
return firstNonNull(type, DEFAULT_CHECK_TYPE);
}
public LaunchPhase getPhase() {
return phase;
}
}

View file

@ -0,0 +1,94 @@
// 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 com.google.domain.registry.model.domain.launch;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/**
* An XML data object that represents a launch extension that may be present on the response to EPP
* domain check commands.
*/
@XmlRootElement(name = "chkData")
@XmlType(propOrder = {"phase", "launchChecks"})
public class LaunchCheckResponseExtension extends ImmutableObject implements ResponseExtension {
/** The launch phase that this domain check was run against. */
LaunchPhase phase;
/** Check responses. */
@XmlElement(name = "cd")
ImmutableList<LaunchCheck> launchChecks;
@VisibleForTesting
public LaunchPhase getPhase() {
return phase;
}
@VisibleForTesting
public ImmutableList<LaunchCheck> getChecks() {
return launchChecks;
}
/** The response for a check on a single resource. */
public static class LaunchCheck extends ImmutableObject {
/** An element containing the name and availability of a resource. */
LaunchCheckName name;
/** A key used to generate a Trademark Claims Notice. Only returned on claims checks. */
String claimKey;
public static LaunchCheck create(LaunchCheckName name, String claimKey) {
LaunchCheck instance = new LaunchCheck();
instance.name = name;
instance.claimKey = claimKey;
return instance;
}
}
/** Holds the name and availability of a checked resource. */
public static class LaunchCheckName extends ImmutableObject {
/** Whether the resource is available. */
@XmlAttribute
boolean exists;
/** The name of the resource being checked. */
@XmlValue
String name;
public static LaunchCheckName create(boolean exists, String name) {
LaunchCheckName instance = new LaunchCheckName();
instance.exists = exists;
instance.name = name;
return instance;
}
}
public static LaunchCheckResponseExtension create(
LaunchPhase phase, ImmutableList<LaunchCheck> launchChecks) {
LaunchCheckResponseExtension instance = new LaunchCheckResponseExtension();
instance.phase = phase;
instance.launchChecks = launchChecks;
return instance;
}
}

View file

@ -0,0 +1,117 @@
// 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 com.google.domain.registry.model.domain.launch;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import com.google.domain.registry.model.smd.AbstractSignedMark;
import com.google.domain.registry.model.smd.EncodedSignedMark;
import com.google.domain.registry.model.smd.SignedMark;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents a launch extension that may be present on EPP domain create
* commands.
* <p>
* This object holds XML data which JAXB will unmarshal from an EPP domain create command extension.
* The XML will have the following enclosing structure:
*
* <pre> {@code
* <epp>
* <command>
* <create>
* <!-- domain create XML data -->
* </create>
* <extension>
* <launch:create>
* <!-- launch create XML payload data -->
* </launch:create>
* </extension>
* </command>
* </epp>
* } </pre>
*
* @see CommandExtension
*/
@XmlRootElement(name = "create")
public class LaunchCreateExtension extends LaunchExtension implements CommandExtension {
/** Type of domain creation being requested. */
public enum CreateType {
/**
* A Launch Application refers to a registration made during a launch phase when the server
* accepts multiple applications for the same domain name.
*/
@XmlEnumValue("application")
APPLICATION,
/**
* A Launch Registration refers to a registration made during a launch phase when the server
* uses a "first-come, first-served" model.
*/
@XmlEnumValue("registration")
REGISTRATION;
}
@XmlAttribute
CreateType type;
/**
* A list of signed marks or encoded signed marks which assert the client's ability to register
* the specified domain name. Each one contains both a Mark object with information about its
* claim(s), and an XML signature over that mark object which is cryptographically signed. This is
* used in the "signed mark" validation model.
*/
@XmlElementRefs({
@XmlElementRef(type = EncodedSignedMark.class),
@XmlElementRef(type = SignedMark.class)})
List<AbstractSignedMark> signedMarks;
/**
* A CodeMark is an abstract entity which contains either a secret code or a mark (or both) to
* assert its ability to register a particular mark. It is used in the "code", "mark", and "code
* with mark" validation models, none of which are supported by this codebase at this time. As
* such, it is stored only as an Object to mark its existence, but not further unmarshaled.
*/
List<Object> codeMark;
/** The claims notice for this create, required if creating a domain with a claimed label. */
LaunchNotice notice;
public CreateType getCreateType() {
return type;
}
public ImmutableList<AbstractSignedMark> getSignedMarks() {
return nullToEmptyImmutableCopy(signedMarks);
}
public boolean hasCodeMarks() {
return codeMark != null && !codeMark.isEmpty();
}
public LaunchNotice getNotice() {
return notice;
}
}

View file

@ -0,0 +1,32 @@
// 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 com.google.domain.registry.model.domain.launch;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a launch extension that may be present on the response to EPP
* domain application create commands.
*/
@XmlRootElement(name = "creData")
@XmlType(propOrder = {"phase", "applicationId"})
public class LaunchCreateResponseExtension extends LaunchExtension implements ResponseExtension {
/** Builder for {@link LaunchCreateResponseExtension}. */
public static class Builder
extends LaunchExtension.Builder<LaunchCreateResponseExtension, Builder> {}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.launch;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents a launch extension that may be present on EPP domain delete
* commands.
*/
@XmlRootElement(name = "delete")
public class LaunchDeleteExtension
extends LaunchExtension implements ApplicationIdTargetExtension {}

View file

@ -0,0 +1,58 @@
// 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 com.google.domain.registry.model.domain.launch;
import com.google.domain.registry.model.Buildable.GenericBuilder;
import com.google.domain.registry.model.ImmutableObject;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
/**
* A launch extension which can be passed in to domain update and delete, and also returned from
* domain create.
*/
@XmlTransient
public abstract class LaunchExtension extends ImmutableObject {
/** The launch phase that this domain application was created in. */
LaunchPhase phase;
/** Application ID of the domain application. */
@XmlElement(name = "applicationID")
String applicationId;
public LaunchPhase getPhase() {
return phase;
}
public String getApplicationId() {
return applicationId;
}
/** A builder for constructing {@link LaunchExtension}. */
public static class Builder<T extends LaunchExtension, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
public B setPhase(LaunchPhase phase) {
getInstance().phase = phase;
return thisCastToDerived();
}
public B setApplicationId(String applicationId) {
getInstance().applicationId = applicationId;
return thisCastToDerived();
}
}
}

View file

@ -0,0 +1,35 @@
// 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 com.google.domain.registry.model.domain.launch;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents a launch extension that may be present on EPP domain info
* commands.
*/
@XmlRootElement(name = "info")
public class LaunchInfoExtension
extends LaunchExtension implements ApplicationIdTargetExtension {
/** Whether or not to include mark information in the response. */
@XmlAttribute
Boolean includeMark;
public Boolean getIncludeMark() {
return includeMark;
}
}

View file

@ -0,0 +1,59 @@
// 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 com.google.domain.registry.model.domain.launch;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import com.google.domain.registry.model.mark.Mark;
import com.googlecode.objectify.annotation.Embed;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a launch extension that may be present on the response to EPP
* domain application info commands.
*/
@Embed
@XmlRootElement(name = "infData")
@XmlType(propOrder = { "phase", "applicationId", "applicationStatus", "marks"})
public class LaunchInfoResponseExtension extends LaunchExtension implements ResponseExtension {
/** The current status of this application. */
@XmlElement(name = "status")
ApplicationStatus applicationStatus;
/** The marks associated with this application. */
@XmlElement(name = "mark", namespace = "urn:ietf:params:xml:ns:mark-1.0")
List<Mark> marks;
/** Builder for {@link LaunchInfoResponseExtension}. */
public static class Builder
extends LaunchExtension.Builder<LaunchInfoResponseExtension, Builder> {
public Builder setApplicationStatus(ApplicationStatus applicationStatus) {
getInstance().applicationStatus = applicationStatus;
return this;
}
public Builder setMarks(ImmutableList<Mark> marks) {
getInstance().marks = marks;
return this;
}
}
}

View file

@ -0,0 +1,133 @@
// 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 com.google.domain.registry.model.domain.launch;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.hash.Hashing.crc32;
import static com.google.common.io.BaseEncoding.base16;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import com.google.common.base.CharMatcher;
import com.google.common.base.Optional;
import com.google.common.primitives.Ints;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.condition.IfNull;
import org.joda.time.DateTime;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/** The claims notice id from the claims phase. */
@Embed
@XmlType(propOrder = {"noticeId", "expirationTime", "acceptedTime"})
public class LaunchNotice extends ImmutableObject {
/** An empty instance to use in place of null. */
private static final NoticeIdType EMPTY_NOTICE_ID = new NoticeIdType();
/** An id with a validator-id attribute. */
@Embed
public static class NoticeIdType extends ImmutableObject {
/**
* The Trademark Claims Notice ID from
* {@link "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3"}.
*/
@XmlValue
String tcnId;
/** The identifier of the TMDB provider to use, defaulting to the TMCH. */
@IgnoreSave(IfNull.class)
@XmlAttribute(name = "validatorID")
String validatorId;
public String getTcnId() {
return tcnId;
}
public String getValidatorId() {
// The default value is "tmch".
return Optional.fromNullable(validatorId).or("tmch");
}
}
@XmlElement(name = "noticeID")
NoticeIdType noticeId;
@XmlElement(name = "notAfter")
DateTime expirationTime;
@XmlElement(name = "acceptedDate")
DateTime acceptedTime;
public NoticeIdType getNoticeId() {
return Optional.fromNullable(noticeId).or(EMPTY_NOTICE_ID);
}
public DateTime getExpirationTime() {
return expirationTime;
}
public DateTime getAcceptedTime() {
return acceptedTime;
}
/**
* Validate the checksum of the notice against the domain label.
*
* @throws IllegalArgumentException
* @throws InvalidChecksumException
*/
public void validate(String domainLabel) throws InvalidChecksumException {
// According to http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3, a TCNID
// is always 8 chars of checksum + 19 chars of a decimal notice id. Check the length before
// taking substrings to avoid an IndexOutOfBoundsException.
String tcnId = getNoticeId().getTcnId();
checkArgument(tcnId.length() == 27);
int checksum = Ints.fromByteArray(base16().decode(tcnId.substring(0, 8).toUpperCase()));
String noticeId = tcnId.substring(8);
checkArgument(CharMatcher.inRange('0', '9').matchesAllOf(noticeId));
// The checksum in the first 8 chars must match the crc32 of label + expiration + notice id.
String stringToHash =
domainLabel + MILLISECONDS.toSeconds(getExpirationTime().getMillis()) + noticeId;
int computedChecksum = crc32().hashString(stringToHash, UTF_8).asInt();
if (checksum != computedChecksum) {
throw new InvalidChecksumException();
}
}
/** Thrown from validate() if the checksum is invalid. */
public static class InvalidChecksumException extends Exception {}
public static LaunchNotice create(
String tcnId, String validatorId, DateTime expirationTime, DateTime acceptedTime) {
LaunchNotice instance = new LaunchNotice();
instance.noticeId = new NoticeIdType();
instance.noticeId.tcnId = tcnId;
instance.noticeId.validatorId = "tmch".equals(validatorId) ? null : validatorId;
instance.expirationTime = expirationTime;
instance.acceptedTime = acceptedTime;
return instance;
}
}

View file

@ -0,0 +1,126 @@
// 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 com.google.domain.registry.model.domain.launch;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.domain.registry.util.TypeUtils.getTypesafeEnumMapping;
import static java.util.Objects.hash;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
/**
* The launch phase of the TLD being addressed by this command.
* <p>
* The launch phase refers to the various stages that a TLD goes through before entering general
* availability. The various phases are described below (in order that they usually occur).
*/
@Embed
public class LaunchPhase extends ImmutableObject {
/**
* The phase during which trademark holders can submit registrations or applications with
* trademark information that can be validated by the server.
*/
public static final LaunchPhase SUNRISE = create("sunrise", null);
/**
* A post-Sunrise phase when non-trademark holders are allowed to register domain names with steps
* taken to address a large volume of initial registrations.
*/
public static final LaunchPhase LANDRUSH = create("landrush", null);
/** A combined sunrise/landrush phase. */
public static final LaunchPhase SUNRUSH = create("sunrise", "landrush");
/**
* The Trademark Claims phase, as defined in the TMCH Functional Specification, in which a Claims
* Notice must be displayed to a prospective registrant of a domain name that matches trademarks.
*/
public static final LaunchPhase CLAIMS = create("claims", null);
/** A post-launch phase that is also referred to as "steady state". */
public static final LaunchPhase OPEN = create("open", null);
/** A custom server launch phase that is defined using the "name" attribute. */
public static final LaunchPhase CUSTOM = create("custom", null);
private static final Map<String, LaunchPhase> LAUNCH_PHASES = initEnumMapping();
/**
* Returns a map of the static final fields to their values, case-converted.
*/
private static final ImmutableMap<String, LaunchPhase> initEnumMapping() {
ImmutableMap.Builder<String, LaunchPhase> builder = new ImmutableMap.Builder<>();
for (Entry<String, LaunchPhase> entry : getTypesafeEnumMapping(LaunchPhase.class).entrySet()) {
builder.put(UPPER_UNDERSCORE.to(LOWER_CAMEL, entry.getKey()), entry.getValue());
}
return builder.build();
}
/** Private create function for the typesafe enum pattern. */
public static LaunchPhase create(String phase, String subphase) {
LaunchPhase instance = new LaunchPhase();
instance.phase = phase;
instance.subphase = subphase;
return instance;
}
@XmlValue
String phase;
/**
* Holds the name of a custom phase if the main phase is "custom", or a sub-phase for all other
* values.
*/
@XmlAttribute(name = "name")
String subphase;
public String getPhase() {
return phase;
}
public String getSubphase() {
return subphase;
}
public static LaunchPhase fromValue(String value) {
return LAUNCH_PHASES.get(value);
}
/** A special equals implementation that only considers the string value. */
@Override
public boolean equals(Object other) {
return other instanceof LaunchPhase
&& Objects.equals(phase, ((LaunchPhase) other).phase)
&& Objects.equals(subphase, ((LaunchPhase) other).subphase);
}
/** A special hashCode implementation that only considers the string value. */
@Override
public int hashCode() {
return hash(phase, subphase);
}
}

View file

@ -0,0 +1,25 @@
// 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 com.google.domain.registry.model.domain.launch;
import javax.xml.bind.annotation.XmlRootElement;
/**
* An XML data object that represents a launch extension that may be present on EPP domain update
* commands.
*/
@XmlRootElement(name = "update")
public class LaunchUpdateExtension
extends LaunchExtension implements ApplicationIdTargetExtension {}

View file

@ -0,0 +1,31 @@
// 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.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:launch-1.0",
xmlns = @XmlNs(prefix = "launch", namespaceURI = "urn:ietf:params:xml:ns:launch-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapter(UtcDateTimeAdapter.class)
package com.google.domain.registry.model.domain.launch;
import com.google.domain.registry.xml.UtcDateTimeAdapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

View file

@ -0,0 +1,53 @@
// 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 com.google.domain.registry.model.domain.metadata;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/** A metadata extension that may be present on EPP create/mutate commands. */
@XmlRootElement(name = "metadata")
public class MetadataExtension extends ImmutableObject implements CommandExtension {
/** The reason for the change. */
@XmlElement(name = "reason")
String reason;
/** Whether a change was requested by a registrar. */
@XmlElement(name = "requestedByRegistrar")
boolean requestedByRegistrar;
/**
* Whether a domain is being created for an anchor tenant. This field is only
* relevant for domain creates, and should be omitted for all other operations.
*/
@XmlElement(name = "anchorTenant")
boolean isAnchorTenant;
public String getReason() {
return reason;
}
public boolean getRequestedByRegistrar() {
return requestedByRegistrar;
}
public boolean getIsAnchorTenant() {
return isAnchorTenant;
}
}

View file

@ -0,0 +1,27 @@
// 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.
@XmlSchema(
namespace = "urn:google:params:xml:ns:metadata-1.0",
xmlns = @XmlNs(prefix = "metadata", namespaceURI = "urn:google:params:xml:ns:metadata-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
package com.google.domain.registry.model.domain.metadata;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

View file

@ -0,0 +1,39 @@
// 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.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:domain-1.0",
xmlns = @XmlNs(prefix = "domain", namespaceURI = "urn:ietf:params:xml:ns:domain-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(UtcDateTimeAdapter.class),
@XmlJavaTypeAdapter(ContactReferenceUnionAdapter.class),
@XmlJavaTypeAdapter(HostReferenceUnionAdapter.class),
@XmlJavaTypeAdapter(DateAdapter.class)})
package com.google.domain.registry.model.domain;
import com.google.domain.registry.model.domain.ReferenceUnion.ContactReferenceUnionAdapter;
import com.google.domain.registry.model.domain.ReferenceUnion.HostReferenceUnionAdapter;
import com.google.domain.registry.xml.DateAdapter;
import com.google.domain.registry.xml.UtcDateTimeAdapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;

View file

@ -0,0 +1,104 @@
// 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 com.google.domain.registry.model.domain.rgp;
import com.google.domain.registry.model.translators.EnumToAttributeAdapter;
import com.google.domain.registry.model.translators.EnumToAttributeAdapter.EppEnum;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Represents a Registry Grace Period status, as defined by
* <a href="https://tools.ietf.org/html/rfc3915">RFC 3915</a>.
*
* @see "https://www.icann.org/resources/pages/epp-status-codes-2014-06-16-en"
*/
@XmlJavaTypeAdapter(EnumToAttributeAdapter.class)
public enum GracePeriodStatus implements EppEnum {
/**
* This grace period is provided after the initial registration of a domain name. If the domain
* name is deleted by the registrar during this period, the registry provides a credit to the
* registrar for the cost of the registration.
*/
ADD("addPeriod"),
/**
* This grace period is provided after a domain name registration period expires and is extended
* (renewed) automatically by the registry. If the domain name is deleted by the registrar during
* this period, the registry provides a credit to the registrar for the cost of the renewal.
*/
AUTO_RENEW("autoRenewPeriod"),
/**
* This status value is used to describe a domain for which a <delete> command has been received,
* but the domain has not yet been purged because an opportunity exists to restore the domain and
* abort the deletion process.
*/
REDEMPTION("redemptionPeriod"),
/**
* This grace period is provided after a domain name registration period is explicitly extended
* (renewed) by the registrar. If the domain name is deleted by the registrar during this period,
* the registry provides a credit to the registrar for the cost of the renewal.
*/
RENEW("renewPeriod"),
/**
* This status value is used to describe a domain that has entered the purge processing state
* after completing the redemptionPeriod state. A domain in this status MUST also have the EPP
* pendingDelete status.
*/
PENDING_DELETE("pendingDelete"),
/**
* This status value is used to describe a domain that is in the process of being restored after
* being in the redemptionPeriod state.
*/
PENDING_RESTORE("pendingRestore"),
/**
* This grace period is provided after the allocation of a domain name that was applied for during
* sunrise or landrush. If the domain name is deleted by the registrar during this period, the
* registry provides a credit to the registrar for the cost of the registration. This grace period
* is cancelled when any nameservers are set on the domain, at which point it converts to a
* standard add grace period.
*
* <p>Note that this status shows up as "addPeriod" in XML, which is the same as the add grace
* period. This is done deliberately so as not to break the standard EPP schema.
*/
SUNRUSH_ADD("addPeriod"),
/**
* This grace period is provided after the successful transfer of domain name registration
* sponsorship from one registrar to another registrar. If the domain name is deleted by the new
* sponsoring registrar during this period, the registry provides a credit to the registrar for
* the cost of the transfer.
*/
TRANSFER("transferPeriod");
@XmlAttribute(name = "s")
private final String xmlName;
GracePeriodStatus(String xmlName) {
this.xmlName = xmlName;
}
@Override
public String getXmlName() {
return xmlName;
}
}

View file

@ -0,0 +1,48 @@
// 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 com.google.domain.registry.model.domain.rgp;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
/** The EPP RGP restore command. */
public class RestoreCommand {
/** Restore operation to perform on this domain. */
@XmlEnum
public enum RestoreOp {
@XmlEnumValue("request")
REQUEST,
@XmlEnumValue("report")
REPORT;
}
/** The restore operation. */
@XmlAttribute
RestoreOp op;
/** A marker object that will be non-null if a report was passed. */
Object report;
public RestoreOp getRestoreOp() {
return op;
}
public boolean hasRestoreReport() {
return report != null;
}
}

View file

@ -0,0 +1,35 @@
// 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 com.google.domain.registry.model.domain.rgp;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
/** The EPP registry grace period extension to be returned with domain info commands. */
@XmlRootElement(name = "infData")
public class RgpInfoExtension extends ImmutableObject implements ResponseExtension {
/** Registry grace period statuses for this domain. */
ImmutableSet<GracePeriodStatus> rgpStatus;
public static RgpInfoExtension create(ImmutableSet<GracePeriodStatus> rgpStatus) {
RgpInfoExtension instance = new RgpInfoExtension();
instance.rgpStatus = rgpStatus;
return instance;
}
}

View file

@ -0,0 +1,31 @@
// 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 com.google.domain.registry.model.domain.rgp;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
/** The EPP RGP extension that may be present on domain update commands. */
@XmlRootElement(name = "update")
public class RgpUpdateExtension extends ImmutableObject implements CommandExtension {
RestoreCommand restore;
public RestoreCommand getRestoreCommand() {
return restore;
}
}

View file

@ -0,0 +1,27 @@
// 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.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:rgp-1.0",
xmlns = @XmlNs(prefix = "rgp", namespaceURI = "urn:ietf:params:xml:ns:rgp-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
package com.google.domain.registry.model.domain.rgp;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

View file

@ -0,0 +1,95 @@
// 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 com.google.domain.registry.model.domain.secdns;
import com.google.common.annotations.VisibleForTesting;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Holds the data necessary to construct a single Delegation Signer (DS) record for a domain.
*
* @see <a href="http://tools.ietf.org/html/rfc5910">RFC 5910</a>
* @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a>
*/
@Embed
@XmlType(name = "dsData")
public class DelegationSignerData
extends ImmutableObject implements Comparable<DelegationSignerData> {
/** The identifier for this particular key in the domain. */
int keyTag;
/**
* The algorithm used by this key.
*
* @see <a href="http://tools.ietf.org/html/rfc4034#appendix-A.1">RFC 4034 Appendix A.1</a>
*/
@XmlElement(name = "alg")
int algorithm;
/**
* The algorithm used to generate the digest.
*
* @see <a href="http://tools.ietf.org/html/rfc4034#appendix-A.2">RFC 4034 Appendix A.2</a>
*/
int digestType;
/**
* The hexBinary digest of the public key.
*
* @see <a href="http://tools.ietf.org/html/rfc4034#section-5.1.4">RFC 4034 Section 5.1.4</a>
*/
@XmlJavaTypeAdapter(HexBinaryAdapter.class)
byte[] digest;
public int getKeyTag() {
return keyTag;
}
public int getAlgorithm() {
return algorithm;
}
public int getDigestType() {
return digestType;
}
public byte[] getDigest() {
return digest;
}
@VisibleForTesting
public static DelegationSignerData create(
int keyTag, int algorithm, int digestType, byte[] digest) {
DelegationSignerData instance = new DelegationSignerData();
instance.keyTag = keyTag;
instance.algorithm = algorithm;
instance.digestType = digestType;
instance.digest = digest;
return instance;
}
@Override
public int compareTo(DelegationSignerData other) {
return Integer.compare(getKeyTag(), other.getKeyTag());
}
}

View file

@ -0,0 +1,49 @@
// 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 com.google.domain.registry.model.domain.secdns;
import static com.google.domain.registry.util.CollectionUtils.nullSafeImmutableCopy;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import java.util.Set;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/** The EPP secDNS extension that may be present on domain create commands. */
@XmlRootElement(name = "create")
@XmlType(propOrder = {"maxSigLife", "dsData"})
public class SecDnsCreateExtension extends ImmutableObject implements CommandExtension {
/**
* Time in seconds until the signature should expire.
* <p>
* We do not support expirations, but we need this field to be able to return appropriate errors.
*/
Long maxSigLife;
/** Signatures for this domain. */
Set<DelegationSignerData> dsData;
public Long getMaxSigLife() {
return maxSigLife;
}
public ImmutableSet<DelegationSignerData> getDsData() {
return nullSafeImmutableCopy(dsData);
}
}

View file

@ -0,0 +1,38 @@
// 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 com.google.domain.registry.model.domain.secdns;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import com.googlecode.objectify.annotation.Embed;
import javax.xml.bind.annotation.XmlRootElement;
/** The EPP secDNS extension to be returned with domain info commands. */
@XmlRootElement(name = "infData")
@Embed
public class SecDnsInfoExtension extends ImmutableObject implements ResponseExtension {
/** Signatures for this domain. */
ImmutableSet<DelegationSignerData> dsData;
public static SecDnsInfoExtension create(ImmutableSet<DelegationSignerData> dsData) {
SecDnsInfoExtension instance = new SecDnsInfoExtension();
instance.dsData = dsData;
return instance;
}
}

View file

@ -0,0 +1,106 @@
// 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 com.google.domain.registry.model.domain.secdns;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
import java.util.Set;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
/** The EPP secDNS extension that may be present on domain update commands. */
@XmlRootElement(name = "update")
@XmlType(propOrder = {"remove", "add", "change"})
public class SecDnsUpdateExtension extends ImmutableObject implements CommandExtension {
/**
* Specifies whether this update is urgent.
* <p>
* We don't support urgent updates but we need this to be present to provide appropriate error
* messages if a client requests it.
*/
@XmlAttribute
Boolean urgent;
/** Allows removing some or all delegations. */
@XmlElement(name = "rem")
Remove remove;
/** Allows adding new delegations. */
Add add;
/** Would allow changing maxSigLife except that we don't support it. */
@XmlElement(name = "chg")
Change change;
public Boolean getUrgent() {
return urgent;
}
public Remove getRemove() {
return remove;
}
public Add getAdd() {
return add;
}
public Change getChange() {
return change;
}
@XmlTransient
abstract static class AddRemoveBase extends ImmutableObject {
/** Delegations to add or remove. */
Set<DelegationSignerData> dsData;
public ImmutableSet<DelegationSignerData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
}
/** The inner add type on the update extension. */
public static class Add extends AddRemoveBase {}
/** The inner remove type on the update extension. */
@XmlType(propOrder = {"all", "dsData"})
public static class Remove extends AddRemoveBase {
/** Whether to remove all delegations. */
Boolean all;
public Boolean getAll() {
return all;
}
}
/** The inner change type on the update extension, though we don't actually support changes. */
public static class Change extends ImmutableObject {
/**
* Time in seconds until the signature should expire.
* <p>
* We do not support expirations, but we need this field to be able to return appropriate
* errors.
*/
Long maxSigLife;
}
}

View file

@ -0,0 +1,27 @@
// 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.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:secDNS-1.1",
xmlns = @XmlNs(prefix = "secDNS", namespaceURI = "urn:ietf:params:xml:ns:secDNS-1.1"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
package com.google.domain.registry.model.domain.secdns;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

View file

@ -0,0 +1,133 @@
// 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 com.google.domain.registry.model.eppcommon;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.Buildable;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.JsonMapBuilder;
import com.google.domain.registry.model.Jsonifiable;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Container for generic street address.
* <p>
* This is the "addrType" type from {@link "http://tools.ietf.org/html/rfc5733"}. It also matches
* the "addrType" type from {@link "http://tools.ietf.org/html/draft-lozano-tmch-smd"}.
*
* @see com.google.domain.registry.model.contact.ContactAddress
* @see com.google.domain.registry.model.mark.MarkAddress
* @see com.google.domain.registry.model.registrar.RegistrarAddress
*/
@XmlTransient
public class Address extends ImmutableObject implements Jsonifiable {
/** The schema validation will enforce that this has 3 lines at most. */
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
List<String> street;
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String city;
@XmlElement(name = "sp")
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String state;
@XmlElement(name = "pc")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String zip;
@XmlElement(name = "cc")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String countryCode;
public ImmutableList<String> getStreet() {
return nullToEmptyImmutableCopy(street);
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public String getZip() {
return zip;
}
public String getCountryCode() {
return countryCode;
}
@Override
public Map<String, Object> toJsonMap() {
return new JsonMapBuilder()
.putListOfStrings("street", street)
.put("city", city)
.put("state", state)
.put("zip", zip)
.put("countryCode", countryCode)
.build();
}
/** A builder for constructing {@link Address}. */
@VisibleForTesting
public static class Builder<T extends Address> extends Buildable.Builder<T> {
public Builder<T> setStreet(ImmutableList<String> street) {
checkArgument(
street == null || (!street.isEmpty() && street.size() <= 3),
"Street address must have [1-3] lines: %s", street);
getInstance().street = street;
return this;
}
public Builder<T> setCity(String city) {
getInstance().city = city;
return this;
}
public Builder<T> setState(String state) {
getInstance().state = state;
return this;
}
public Builder<T> setZip(String zip) {
getInstance().zip = zip;
return this;
}
public Builder<T> setCountryCode(String countryCode) {
checkArgument(
countryCode == null || countryCode.length() == 2,
"Country code should be a 2 character string");
getInstance().countryCode = countryCode;
return this;
}
}
}

View file

@ -0,0 +1,90 @@
// 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 com.google.domain.registry.model.eppcommon;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.ImmutableObject;
import com.googlecode.objectify.annotation.Embed;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* The "authInfoType" complex type.
* <p>
* RFCs 5731 and 5732 define this almost identically up to the namespace.
*/
@XmlTransient
public abstract class AuthInfo extends ImmutableObject {
/**
* Verify that the authorization info is valid for the given resource in the given tld.
*
* @throws BadAuthInfoException if this authorization info is invalid for this resource
*/
public abstract void verifyAuthorizedFor(EppResource eppResource) throws BadAuthInfoException;
protected PasswordAuth pw;
public PasswordAuth getPw() {
return pw;
}
/** The "pwAuthInfoType" complex type. */
@Embed
@XmlType(namespace = "urn:ietf:params:xml:ns:eppcom-1.0")
public static class PasswordAuth extends ImmutableObject {
@XmlValue
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String value;
@XmlAttribute(name = "roid")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String repoId;
public String getValue() {
return value;
}
public String getRepoId() {
return repoId;
}
public static PasswordAuth create(String value, String repoId) {
PasswordAuth instance = new PasswordAuth();
instance.value = value;
instance.repoId = repoId;
return instance;
}
public static PasswordAuth create(String value) {
return create(value, null);
}
}
/** Returns the repoId for the contact this auth info is associated with. */
protected String getRepoId() {
return pw.getRepoId();
}
/** Exception to throw when an auth info can't be verified. */
public static class BadAuthInfoException extends Exception {}
}

Some files were not shown because too many files have changed in this diff Show more