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 + + + + + +