mirror of
https://github.com/google/nomulus.git
synced 2025-07-01 16:53:35 +02:00
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:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
35
java/google/registry/model/AbstractFieldExposer.java
Normal file
35
java/google/registry/model/AbstractFieldExposer.java
Normal 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);
|
||||
}
|
95
java/google/registry/model/BUILD
Normal file
95
java/google/registry/model/BUILD
Normal 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",
|
||||
],
|
||||
)
|
41
java/google/registry/model/BackupGroupRoot.java
Normal file
41
java/google/registry/model/BackupGroupRoot.java
Normal 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;
|
||||
}
|
||||
}
|
108
java/google/registry/model/Buildable.java
Normal file
108
java/google/registry/model/Buildable.java
Normal 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);
|
||||
}
|
||||
}
|
40
java/google/registry/model/CreateAutoTimestamp.java
Normal file
40
java/google/registry/model/CreateAutoTimestamp.java
Normal 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;
|
||||
}
|
||||
}
|
129
java/google/registry/model/EntityClasses.java
Normal file
129
java/google/registry/model/EntityClasses.java
Normal 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() {}
|
||||
}
|
324
java/google/registry/model/EppResource.java
Normal file
324
java/google/registry/model/EppResource.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
407
java/google/registry/model/EppResourceUtils.java
Normal file
407
java/google/registry/model/EppResourceUtils.java
Normal 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() {}
|
||||
}
|
167
java/google/registry/model/ImmutableObject.java
Normal file
167
java/google/registry/model/ImmutableObject.java
Normal 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);
|
||||
}
|
||||
}
|
91
java/google/registry/model/JsonMapBuilder.java
Normal file
91
java/google/registry/model/JsonMapBuilder.java
Normal 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;
|
||||
}
|
||||
}
|
29
java/google/registry/model/Jsonifiable.java
Normal file
29
java/google/registry/model/Jsonifiable.java
Normal 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();
|
||||
}
|
296
java/google/registry/model/ModelUtils.java
Normal file
296
java/google/registry/model/ModelUtils.java
Normal 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();
|
||||
}
|
||||
}
|
65
java/google/registry/model/RoidSuffixes.java
Normal file
65
java/google/registry/model/RoidSuffixes.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
75
java/google/registry/model/SchemaVersion.java
Normal file
75
java/google/registry/model/SchemaVersion.java
Normal 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() {}
|
||||
}
|
43
java/google/registry/model/UpdateAutoTimestamp.java
Normal file
43
java/google/registry/model/UpdateAutoTimestamp.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
47
java/google/registry/model/annotations/NotBackedUp.java
Normal file
47
java/google/registry/model/annotations/NotBackedUp.java
Normal 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
|
||||
}
|
||||
}
|
32
java/google/registry/model/annotations/VirtualEntity.java
Normal file
32
java/google/registry/model/annotations/VirtualEntity.java
Normal 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 {}
|
560
java/google/registry/model/billing/BillingEvent.java
Normal file
560
java/google/registry/model/billing/BillingEvent.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
212
java/google/registry/model/billing/RegistrarBillingEntry.java
Normal file
212
java/google/registry/model/billing/RegistrarBillingEntry.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
100
java/google/registry/model/billing/RegistrarBillingUtils.java
Normal file
100
java/google/registry/model/billing/RegistrarBillingUtils.java
Normal 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() {}
|
||||
}
|
216
java/google/registry/model/billing/RegistrarCredit.java
Normal file
216
java/google/registry/model/billing/RegistrarCredit.java
Normal 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);
|
||||
}
|
||||
}
|
253
java/google/registry/model/billing/RegistrarCreditBalance.java
Normal file
253
java/google/registry/model/billing/RegistrarCreditBalance.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
35
java/google/registry/model/common/CrossTldSingleton.java
Normal file
35
java/google/registry/model/common/CrossTldSingleton.java
Normal 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();
|
||||
}
|
47
java/google/registry/model/common/EntityGroupRoot.java
Normal file
47
java/google/registry/model/common/EntityGroupRoot.java
Normal 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");
|
||||
}
|
||||
}
|
78
java/google/registry/model/common/GaeUserIdConverter.java
Normal file
78
java/google/registry/model/common/GaeUserIdConverter.java
Normal 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();
|
||||
}});
|
||||
}
|
||||
}
|
||||
}
|
56
java/google/registry/model/common/PersistedRangeLong.java
Normal file
56
java/google/registry/model/common/PersistedRangeLong.java
Normal 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;
|
||||
}
|
||||
}
|
93
java/google/registry/model/common/TimeOfYear.java
Normal file
93
java/google/registry/model/common/TimeOfYear.java
Normal 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)));
|
||||
}
|
||||
}
|
189
java/google/registry/model/common/TimedTransitionProperty.java
Normal file
189
java/google/registry/model/common/TimedTransitionProperty.java
Normal 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();
|
||||
}
|
||||
}
|
37
java/google/registry/model/contact/ContactAddress.java
Normal file
37
java/google/registry/model/contact/ContactAddress.java
Normal 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> {}
|
||||
}
|
51
java/google/registry/model/contact/ContactAuthInfo.java
Normal file
51
java/google/registry/model/contact/ContactAuthInfo.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
239
java/google/registry/model/contact/ContactCommand.java
Normal file
239
java/google/registry/model/contact/ContactCommand.java
Normal 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;
|
||||
|
||||
/** Contact’s voice number. */
|
||||
ContactPhoneNumber voice;
|
||||
|
||||
/** Contact’s fax number. */
|
||||
ContactPhoneNumber fax;
|
||||
|
||||
/** Contact’s 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
java/google/registry/model/contact/ContactPhoneNumber.java
Normal file
35
java/google/registry/model/contact/ContactPhoneNumber.java
Normal 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> {}
|
||||
}
|
260
java/google/registry/model/contact/ContactResource.java
Normal file
260
java/google/registry/model/contact/ContactResource.java
Normal 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;
|
||||
|
||||
/** Contact’s voice number. Personal info; cleared by wipeOut(). */
|
||||
@IgnoreSave(IfNull.class)
|
||||
ContactPhoneNumber voice;
|
||||
|
||||
/** Contact’s fax number. Personal info; cleared by wipeOut(). */
|
||||
@IgnoreSave(IfNull.class)
|
||||
ContactPhoneNumber fax;
|
||||
|
||||
/** Contact’s 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();
|
||||
}
|
||||
}
|
||||
}
|
136
java/google/registry/model/contact/Disclose.java
Normal file
136
java/google/registry/model/contact/Disclose.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
122
java/google/registry/model/contact/PostalInfo.java
Normal file
122
java/google/registry/model/contact/PostalInfo.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
31
java/google/registry/model/contact/package-info.java
Normal file
31
java/google/registry/model/contact/package-info.java
Normal 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;
|
||||
|
73
java/google/registry/model/domain/DesignatedContact.java
Normal file
73
java/google/registry/model/domain/DesignatedContact.java
Normal 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;
|
||||
}
|
||||
}
|
173
java/google/registry/model/domain/DomainApplication.java
Normal file
173
java/google/registry/model/domain/DomainApplication.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
64
java/google/registry/model/domain/DomainAuthInfo.java
Normal file
64
java/google/registry/model/domain/DomainAuthInfo.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
294
java/google/registry/model/domain/DomainBase.java
Normal file
294
java/google/registry/model/domain/DomainBase.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
462
java/google/registry/model/domain/DomainCommand.java
Normal file
462
java/google/registry/model/domain/DomainCommand.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
41
java/google/registry/model/domain/DomainRenewData.java
Normal file
41
java/google/registry/model/domain/DomainRenewData.java
Normal 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;
|
||||
}
|
||||
}
|
414
java/google/registry/model/domain/DomainResource.java
Normal file
414
java/google/registry/model/domain/DomainResource.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
158
java/google/registry/model/domain/GracePeriod.java
Normal file
158
java/google/registry/model/domain/GracePeriod.java
Normal 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));
|
||||
}
|
||||
}
|
59
java/google/registry/model/domain/Period.java
Normal file
59
java/google/registry/model/domain/Period.java
Normal 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;
|
||||
}
|
||||
}
|
104
java/google/registry/model/domain/ReferenceUnion.java
Normal file
104
java/google/registry/model/domain/ReferenceUnion.java
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
31
java/google/registry/model/domain/allocate/package-info.java
Normal file
31
java/google/registry/model/domain/allocate/package-info.java
Normal 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;
|
||||
|
86
java/google/registry/model/domain/fee/BaseFee.java
Normal file
86
java/google/registry/model/domain/fee/BaseFee.java
Normal 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();
|
||||
}
|
||||
}
|
48
java/google/registry/model/domain/fee/BaseFeeCommand.java
Normal file
48
java/google/registry/model/domain/fee/BaseFeeCommand.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
52
java/google/registry/model/domain/fee/BaseFeeRequest.java
Normal file
52
java/google/registry/model/domain/fee/BaseFeeRequest.java
Normal 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);
|
||||
}
|
||||
}
|
91
java/google/registry/model/domain/fee/BaseFeeResponse.java
Normal file
91
java/google/registry/model/domain/fee/BaseFeeResponse.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
29
java/google/registry/model/domain/fee/Credit.java
Normal file
29
java/google/registry/model/domain/fee/Credit.java
Normal 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;
|
||||
}
|
||||
}
|
29
java/google/registry/model/domain/fee/Fee.java
Normal file
29
java/google/registry/model/domain/fee/Fee.java
Normal 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;
|
||||
}
|
||||
}
|
54
java/google/registry/model/domain/fee/FeeCheckExtension.java
Normal file
54
java/google/registry/model/domain/fee/FeeCheckExtension.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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> {}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
25
java/google/registry/model/domain/fee/FeeInfoExtension.java
Normal file
25
java/google/registry/model/domain/fee/FeeInfoExtension.java
Normal 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 {}
|
|
@ -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> {}
|
||||
}
|
25
java/google/registry/model/domain/fee/FeeRenewExtension.java
Normal file
25
java/google/registry/model/domain/fee/FeeRenewExtension.java
Normal 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 {}
|
|
@ -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> {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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> {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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> {}
|
||||
}
|
31
java/google/registry/model/domain/fee/package-info.java
Normal file
31
java/google/registry/model/domain/fee/package-info.java
Normal 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;
|
||||
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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> {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
133
java/google/registry/model/domain/launch/LaunchNotice.java
Normal file
133
java/google/registry/model/domain/launch/LaunchNotice.java
Normal 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;
|
||||
}
|
||||
}
|
126
java/google/registry/model/domain/launch/LaunchPhase.java
Normal file
126
java/google/registry/model/domain/launch/LaunchPhase.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 {}
|
31
java/google/registry/model/domain/launch/package-info.java
Normal file
31
java/google/registry/model/domain/launch/package-info.java
Normal 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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
27
java/google/registry/model/domain/metadata/package-info.java
Normal file
27
java/google/registry/model/domain/metadata/package-info.java
Normal 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;
|
||||
|
39
java/google/registry/model/domain/package-info.java
Normal file
39
java/google/registry/model/domain/package-info.java
Normal 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;
|
||||
|
104
java/google/registry/model/domain/rgp/GracePeriodStatus.java
Normal file
104
java/google/registry/model/domain/rgp/GracePeriodStatus.java
Normal 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;
|
||||
}
|
||||
}
|
48
java/google/registry/model/domain/rgp/RestoreCommand.java
Normal file
48
java/google/registry/model/domain/rgp/RestoreCommand.java
Normal 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;
|
||||
}
|
||||
}
|
35
java/google/registry/model/domain/rgp/RgpInfoExtension.java
Normal file
35
java/google/registry/model/domain/rgp/RgpInfoExtension.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
27
java/google/registry/model/domain/rgp/package-info.java
Normal file
27
java/google/registry/model/domain/rgp/package-info.java
Normal 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;
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
27
java/google/registry/model/domain/secdns/package-info.java
Normal file
27
java/google/registry/model/domain/secdns/package-info.java
Normal 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;
|
||||
|
133
java/google/registry/model/eppcommon/Address.java
Normal file
133
java/google/registry/model/eppcommon/Address.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
90
java/google/registry/model/eppcommon/AuthInfo.java
Normal file
90
java/google/registry/model/eppcommon/AuthInfo.java
Normal 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
Loading…
Add table
Add a link
Reference in a new issue