diff --git a/java/google/registry/model/EppResource.java b/java/google/registry/model/EppResource.java
index 40d17203a..69c0eb5af 100644
--- a/java/google/registry/model/EppResource.java
+++ b/java/google/registry/model/EppResource.java
@@ -14,6 +14,7 @@
package google.registry.model;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.util.CollectionUtils.difference;
@@ -211,6 +212,19 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable,
super(instance);
}
+ /**
+ * Set the time this resource was created.
+ *
+ *
Note: This can only be used if the creation time hasn't already been set, which it is in
+ * normal EPP flows.
+ */
+ public B setCreationTime(DateTime creationTime) {
+ checkState(getInstance().creationTime.timestamp == null,
+ "creationTime can only be set once for EppResource.");
+ getInstance().creationTime = CreateAutoTimestamp.create(creationTime);
+ return thisCastToDerived();
+ }
+
/** Set the time this resource was created. Should only be used in tests. */
@VisibleForTesting
public B setCreationTimeForTest(DateTime creationTime) {
diff --git a/java/google/registry/rde/XjcToContactResourceConverter.java b/java/google/registry/rde/XjcToContactResourceConverter.java
new file mode 100644
index 000000000..6fbf97cdf
--- /dev/null
+++ b/java/google/registry/rde/XjcToContactResourceConverter.java
@@ -0,0 +1,196 @@
+// 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.
+
+// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.rde;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import google.registry.model.contact.ContactAddress;
+import google.registry.model.contact.ContactPhoneNumber;
+import google.registry.model.contact.ContactResource;
+import google.registry.model.contact.Disclose;
+import google.registry.model.contact.Disclose.PostalInfoChoice;
+import google.registry.model.contact.PostalInfo;
+import google.registry.model.eppcommon.StatusValue;
+import google.registry.model.transfer.TransferData;
+import google.registry.model.transfer.TransferStatus;
+import google.registry.util.XmlToEnumMapper;
+import google.registry.xjc.contact.XjcContactAddrType;
+import google.registry.xjc.contact.XjcContactDiscloseType;
+import google.registry.xjc.contact.XjcContactE164Type;
+import google.registry.xjc.contact.XjcContactIntLocType;
+import google.registry.xjc.contact.XjcContactPostalInfoEnumType;
+import google.registry.xjc.contact.XjcContactPostalInfoType;
+import google.registry.xjc.contact.XjcContactStatusType;
+import google.registry.xjc.rdecontact.XjcRdeContact;
+import google.registry.xjc.rdecontact.XjcRdeContactTransferDataType;
+
+import javax.annotation.Nullable;
+
+/** Utility class that converts an {@link XjcRdeContact} into a {@link ContactResource}. */
+final class XjcToContactResourceConverter {
+
+ private static final XmlToEnumMapper POSTAL_INFO_TYPE_MAPPER =
+ XmlToEnumMapper.create(PostalInfo.Type.values());
+ private static final XmlToEnumMapper TRANSFER_STATUS_MAPPER =
+ XmlToEnumMapper.create(TransferStatus.values());
+
+ private static final Function choiceConverter =
+ new Function() {
+ @Override
+ public PostalInfoChoice apply(XjcContactIntLocType choice) {
+ return convertPostalInfoChoice(choice);
+ }
+ };
+
+ private static final Function STATUS_CONVERTER =
+ new Function() {
+ @Override
+ public StatusValue apply(XjcContactStatusType status) {
+ return convertStatusValue(status);
+ }
+
+ };
+
+ /** Converts {@link XjcRdeContact} to {@link ContactResource}. */
+ static ContactResource convertContact(XjcRdeContact contact) {
+ return new ContactResource.Builder()
+ .setRepoId(contact.getRoid())
+ .setStatusValues(
+ ImmutableSet.copyOf(Iterables.transform(contact.getStatuses(), STATUS_CONVERTER)))
+ .setLocalizedPostalInfo(
+ getPostalInfoOfType(contact.getPostalInfos(), XjcContactPostalInfoEnumType.LOC))
+ .setInternationalizedPostalInfo(
+ getPostalInfoOfType(contact.getPostalInfos(), XjcContactPostalInfoEnumType.INT))
+ .setContactId(contact.getId())
+ .setCurrentSponsorClientId(contact.getClID())
+ .setCreationClientId(contact.getCrRr() == null ? null : contact.getCrRr().getValue())
+ .setLastEppUpdateClientId(contact.getUpRr() == null ? null : contact.getUpRr().getValue())
+ .setCreationTime(contact.getCrDate())
+ .setLastEppUpdateTime(contact.getUpDate())
+ .setLastTransferTime(contact.getTrDate())
+ .setVoiceNumber(convertPhoneNumber(contact.getVoice()))
+ .setFaxNumber(convertPhoneNumber(contact.getFax()))
+ .setEmailAddress(contact.getEmail())
+ .setDisclose(convertDisclose(contact.getDisclose()))
+ .setTransferData(convertTransferData(contact.getTrnData()))
+ .build();
+ }
+
+ /**
+ * Extracts a {@link PostalInfo} from an {@link Iterable} of {@link XjcContactPostalInfoEnumType}.
+ */
+ @Nullable
+ private static PostalInfo getPostalInfoOfType(
+ Iterable postalInfos, XjcContactPostalInfoEnumType type) {
+ for (XjcContactPostalInfoType postalInfo : postalInfos) {
+ if (postalInfo.getType() == type) {
+ return convertPostalInfo(postalInfo);
+ }
+ }
+ return null;
+ }
+
+ /** Converts {@link XjcRdeContactTransferDataType} to {@link TransferData}. */
+ private static TransferData convertTransferData(
+ @Nullable XjcRdeContactTransferDataType transferData) {
+ if (transferData == null) {
+ return TransferData.EMPTY;
+ }
+ return new TransferData.Builder()
+ .setTransferStatus(TRANSFER_STATUS_MAPPER.xmlToEnum(transferData.getTrStatus().value()))
+ .setGainingClientId(transferData.getReRr().getValue())
+ .setLosingClientId(transferData.getAcRr().getValue())
+ .setTransferRequestTime(transferData.getReDate())
+ .setPendingTransferExpirationTime(transferData.getAcDate())
+ .build();
+ }
+
+ /** Converts {@link XjcContactAddrType} to {@link ContactAddress}. */
+ private static ContactAddress convertAddress(XjcContactAddrType address) {
+ return new ContactAddress.Builder()
+ .setStreet(ImmutableList.copyOf(address.getStreets()))
+ .setCity(address.getCity())
+ .setState(address.getSp())
+ .setZip(address.getPc())
+ .setCountryCode(address.getCc())
+ .build();
+ }
+
+ /** Converts {@link XjcContactDiscloseType} to {@link Disclose}. */
+ @Nullable
+ private static Disclose convertDisclose(@Nullable XjcContactDiscloseType disclose) {
+ if (disclose == null) {
+ return null;
+ }
+ return new Disclose.Builder()
+ .setFlag(disclose.isFlag())
+ .setNames(ImmutableList.copyOf(Lists.transform(disclose.getNames(), choiceConverter)))
+ .setOrgs(ImmutableList.copyOf(Lists.transform(disclose.getOrgs(), choiceConverter)))
+ .setAddrs(ImmutableList.copyOf(Lists.transform(disclose.getAddrs(), choiceConverter)))
+ .build();
+ }
+
+ /** Converts {@link XjcContactE164Type} to {@link ContactPhoneNumber}. */
+ @Nullable
+ private static ContactPhoneNumber convertPhoneNumber(@Nullable XjcContactE164Type phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+ return new ContactPhoneNumber.Builder()
+ .setPhoneNumber(phoneNumber.getValue())
+ .setExtension(phoneNumber.getX())
+ .build();
+ }
+
+ /** Converts {@link PostalInfoChoice} to {@link XjcContactIntLocType}. */
+ private static PostalInfoChoice convertPostalInfoChoice(XjcContactIntLocType choice) {
+ return PostalInfoChoice.create(POSTAL_INFO_TYPE_MAPPER.xmlToEnum(choice.getType().value()));
+ }
+
+ /** Converts {@link XjcContactPostalInfoType} to {@link PostalInfo}. */
+ private static PostalInfo convertPostalInfo(XjcContactPostalInfoType postalInfo) {
+ return new PostalInfo.Builder()
+ .setName(postalInfo.getName())
+ .setOrg(postalInfo.getOrg())
+ .setAddress(convertAddress(postalInfo.getAddr()))
+ .setType(POSTAL_INFO_TYPE_MAPPER.xmlToEnum(postalInfo.getType().value()))
+ .build();
+ }
+
+ /** Converts {@link XjcContactStatusType} to {@link StatusValue}. */
+ private static StatusValue convertStatusValue(XjcContactStatusType statusType) {
+ return StatusValue.fromXmlName(statusType.getS().value());
+ }
+
+ private XjcToContactResourceConverter() {}
+}
diff --git a/java/google/registry/util/XmlToEnumMapper.java b/java/google/registry/util/XmlToEnumMapper.java
new file mode 100644
index 000000000..26fe32695
--- /dev/null
+++ b/java/google/registry/util/XmlToEnumMapper.java
@@ -0,0 +1,55 @@
+// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.util;
+
+import com.google.common.collect.ImmutableMap;
+
+import javax.xml.bind.annotation.XmlEnumValue;
+
+/** Efficient lookup from xml enums to java enums */
+public final class XmlToEnumMapper> {
+
+ private final ImmutableMap map;
+
+ /** Look up T from the {@link XmlEnumValue} */
+ public T xmlToEnum(String value) {
+ return map.get(value);
+ }
+
+ /**
+ * Creates a new {@link XmlToEnumMapper} from xml value to enum value.
+ */
+ public static > XmlToEnumMapper create(T[] enumValues) {
+ return new XmlToEnumMapper(enumValues);
+ }
+
+ private XmlToEnumMapper(T[] enumValues) {
+ ImmutableMap.Builder mapBuilder = new ImmutableMap.Builder<>();
+ for (T value : enumValues) {
+ try {
+ String xmlName =
+ value
+ .getDeclaringClass()
+ .getField(value.name())
+ .getAnnotation(XmlEnumValue.class)
+ .value();
+ mapBuilder = mapBuilder.put(xmlName, value);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ map = mapBuilder.build();
+ }
+}
diff --git a/javatests/google/registry/rde/XjcToContactResourceConverterTest.java b/javatests/google/registry/rde/XjcToContactResourceConverterTest.java
new file mode 100644
index 000000000..5df0482bd
--- /dev/null
+++ b/javatests/google/registry/rde/XjcToContactResourceConverterTest.java
@@ -0,0 +1,187 @@
+// 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.
+
+// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.rde;
+
+import static com.google.common.truth.Truth.assertThat;
+import static google.registry.testing.DatastoreHelper.createTld;
+
+import com.google.common.io.ByteSource;
+
+import google.registry.model.contact.ContactResource;
+import google.registry.model.contact.PostalInfo;
+import google.registry.model.eppcommon.StatusValue;
+import google.registry.model.transfer.TransferData;
+import google.registry.model.transfer.TransferStatus;
+import google.registry.testing.AppEngineRule;
+import google.registry.xjc.XjcXmlTransformer;
+import google.registry.xjc.rdecontact.XjcRdeContact;
+import google.registry.xjc.rdecontact.XjcRdeContactElement;
+
+import org.joda.time.DateTime;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stax.StAXSource;
+import javax.xml.transform.stream.StreamResult;
+
+@RunWith(JUnit4.class)
+public class XjcToContactResourceConverterTest {
+
+ private static final ByteSource CONTACT_XML = RdeTestData.get("contact_fragment.xml");
+
+ @Rule
+ public final AppEngineRule appEngine = AppEngineRule.builder()
+ .withDatastore()
+ .build();
+
+ @Before
+ public void before() throws Exception {
+ createTld("xn--q9jyb4c");
+ }
+
+ @Test
+ public void testConvertContact() throws Exception {
+ XjcRdeContact contact = getContact();
+ ContactResource resource = XjcToContactResourceConverter.convertContact(contact);
+ assertThat(resource.getContactId()).isEqualTo("love-id");
+ assertThat(resource.getRepoId()).isEqualTo("2-ROID");
+ assertThat(resource.getStatusValues())
+ .containsExactly(
+ StatusValue.CLIENT_DELETE_PROHIBITED,
+ StatusValue.SERVER_UPDATE_PROHIBITED);
+
+ assertThat(resource.getInternationalizedPostalInfo()).isNotNull();
+ PostalInfo postalInfo = resource.getInternationalizedPostalInfo();
+ assertThat(postalInfo.getName()).isEqualTo("Dipsy Doodle");
+ assertThat(postalInfo.getOrg()).isEqualTo("Charleston Road Registry Incorporated");
+ assertThat(postalInfo.getAddress().getStreet()).hasSize(2);
+ assertThat(postalInfo.getAddress().getStreet().get(0)).isEqualTo("123 Charleston Road");
+ assertThat(postalInfo.getAddress().getStreet().get(1)).isEqualTo("Suite 123");
+ assertThat(postalInfo.getAddress().getState()).isEqualTo("CA");
+ assertThat(postalInfo.getAddress().getZip()).isEqualTo("31337");
+ assertThat(postalInfo.getAddress().getCountryCode()).isEqualTo("US");
+
+ assertThat(resource.getLocalizedPostalInfo()).isNull();
+
+ assertThat(resource.getVoiceNumber()).isNotNull();
+ assertThat(resource.getVoiceNumber().getPhoneNumber()).isEqualTo("+1.2126660000");
+ assertThat(resource.getVoiceNumber().getExtension()).isEqualTo("123");
+
+ assertThat(resource.getFaxNumber()).isNotNull();
+ assertThat(resource.getFaxNumber().getPhoneNumber()).isEqualTo("+1.2126660001");
+ assertThat(resource.getFaxNumber().getExtension()).isNull();
+
+ assertThat(resource.getEmailAddress()).isEqualTo("justine@crr.com");
+ assertThat(resource.getCurrentSponsorClientId()).isEqualTo("TheRegistrar");
+ assertThat(resource.getCreationClientId()).isEqualTo("NewRegistrar");
+ assertThat(resource.getCreationTime()).isEqualTo(DateTime.parse("1900-01-01TZ"));
+ assertThat(resource.getLastEppUpdateClientId()).isEqualTo("TheRegistrar");
+ assertThat(resource.getLastEppUpdateTime()).isEqualTo(DateTime.parse("1930-04-20TZ"));
+ assertThat(resource.getLastTransferTime()).isEqualTo(DateTime.parse("1925-04-20TZ"));
+
+ assertThat(resource.getTransferData()).isNotNull();
+ assertThat(resource.getTransferData().getTransferStatus())
+ .isEqualTo(TransferStatus.SERVER_APPROVED);
+ assertThat(resource.getTransferData().getGainingClientId()).isEqualTo("TheRegistrar");
+ assertThat(resource.getTransferData().getTransferRequestTime())
+ .isEqualTo(DateTime.parse("1925-04-19TZ"));
+ assertThat(resource.getTransferData().getLosingClientId()).isEqualTo("NewRegistrar");
+ assertThat(resource.getTransferData().getPendingTransferExpirationTime())
+ .isEqualTo(DateTime.parse("1925-04-21TZ"));
+
+ assertThat(resource.getDisclose()).isNotNull();
+ assertThat(resource.getDisclose().getFlag()).isTrue();
+ assertThat(resource.getDisclose().getAddrs()).hasSize(1);
+ assertThat(resource.getDisclose().getAddrs().get(0).getType())
+ .isEqualTo(PostalInfo.Type.INTERNATIONALIZED);
+ assertThat(resource.getDisclose().getNames()).hasSize(1);
+ assertThat(resource.getDisclose().getNames().get(0).getType())
+ .isEqualTo(PostalInfo.Type.INTERNATIONALIZED);
+ assertThat(resource.getDisclose().getOrgs()).isEmpty();
+ }
+
+ @Test
+ public void testConvertContact_absentVoiceAndFaxNumbers() throws Exception {
+ XjcRdeContact contact = getContact();
+ contact.setVoice(null);
+ contact.setFax(null);
+ ContactResource resource = XjcToContactResourceConverter.convertContact(contact);
+ assertThat(resource.getVoiceNumber()).isNull();
+ assertThat(resource.getFaxNumber()).isNull();
+ }
+
+ @Test
+ public void testConvertContact_absentDisclose() throws Exception {
+ XjcRdeContact contact = getContact();
+ contact.setDisclose(null);
+ ContactResource resource = XjcToContactResourceConverter.convertContact(contact);
+ assertThat(resource.getDisclose()).isNull();
+ }
+
+ @Test
+ public void testConvertContact_absentTransferData() throws Exception {
+ XjcRdeContact contact = getContact();
+ contact.setTrDate(null);
+ contact.setTrnData(null);
+ ContactResource resource = XjcToContactResourceConverter.convertContact(contact);
+ assertThat(resource.getLastTransferTime()).isNull();
+ assertThat(resource.getTransferData()).isSameAs(TransferData.EMPTY);
+ }
+
+ private XjcRdeContact getContact() throws Exception {
+ InputStream in = null;
+ try {
+ in = CONTACT_XML.openBufferedStream();
+ XMLInputFactory factory = XMLInputFactory.newInstance();
+ XMLStreamReader reader = factory.createXMLStreamReader(in);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer t = tf.newTransformer();
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ t.transform(new StAXSource(reader), new StreamResult(bout));
+ ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
+ XjcRdeContactElement element = XjcXmlTransformer.unmarshal(XjcRdeContactElement.class, bin);
+ return element.getValue();
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ }
+}
diff --git a/javatests/google/registry/rde/testdata/contact_fragment.xml b/javatests/google/registry/rde/testdata/contact_fragment.xml
new file mode 100644
index 000000000..be8a13f07
--- /dev/null
+++ b/javatests/google/registry/rde/testdata/contact_fragment.xml
@@ -0,0 +1,40 @@
+
+ love-id
+ 2-ROID
+
+
+
+ Dipsy Doodle
+ Charleston Road Registry Incorporated
+
+ 123 Charleston Road
+ Suite 123
+ Mountain View
+ CA
+ 31337
+ US
+
+
+ +1.2126660000
+ +1.2126660001
+ justine@crr.com
+ TheRegistrar
+ NewRegistrar
+ 1900-01-01T00:00:00Z
+ TheRegistrar
+ 1930-04-20T00:00:00Z
+ 1925-04-20T00:00:00Z
+
+ serverApproved
+ TheRegistrar
+ 1925-04-19T00:00:00Z
+ NewRegistrar
+ 1925-04-21T00:00:00Z
+
+
+
+
+
+