google-nomulus/java/google/registry/flows/ResourceTransferRequestFlow.java
Justine Tunney 5012893c1d 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.
2016-05-13 18:55:08 -04:00

220 lines
9.2 KiB
Java

// 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.flows;
import static com.google.domain.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse;
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.CollectionUtils.union;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
import com.google.domain.registry.flows.EppException.ObjectPendingTransferException;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import com.google.domain.registry.model.eppoutput.EppOutput;
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
import com.google.domain.registry.model.poll.PollMessage;
import com.google.domain.registry.model.transfer.TransferData;
import com.google.domain.registry.model.transfer.TransferData.TransferServerApproveEntity;
import com.google.domain.registry.model.transfer.TransferStatus;
import com.googlecode.objectify.Key;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.util.Set;
/**
* An EPP flow that requests a transfer on a resource.
*
* @param <R> the resource type being manipulated
* @param <C> the command type, marshalled directly from the epp xml
*/
public abstract class ResourceTransferRequestFlow
<R extends EppResource, C extends SingleResourceCommand> extends ResourceTransferFlow<R, C> {
private static final Set<StatusValue> TRANSFER_DISALLOWED_STATUSES = ImmutableSet.of(
StatusValue.CLIENT_TRANSFER_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_TRANSFER_PROHIBITED);
private DateTime transferExpirationTime;
/** Helper class to identify the two clients. */
protected abstract class Client {
public abstract String getId();
}
/** The gaining client. */
protected Client gainingClient = new Client() {
@Override
public String getId() {
return getClientId();
}};
/** The losing client. */
protected Client losingClient = new Client() {
@Override
public String getId() {
return existingResource.getCurrentSponsorClientId();
}};
@Override
protected final void initResourceCreateOrMutateFlow() {
initResourceTransferRequestFlow();
}
protected abstract Duration getAutomaticTransferLength();
@Override
protected final void verifyMutationAllowed() throws EppException {
// Verify that the resource does not already have a pending transfer.
if (TransferStatus.PENDING.equals(existingResource.getTransferData().getTransferStatus())) {
throw new AlreadyPendingTransferException(targetId);
}
// Verify that this client doesn't already sponsor this resource.
if (gainingClient.getId().equals(losingClient.getId())) {
throw new ObjectAlreadySponsoredException();
}
if (command.getAuthInfo() == null) {
throw new MissingTransferRequestAuthInfoException();
}
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
verifyTransferRequestIsAllowed();
}
private TransferData.Builder createTransferDataBuilder(TransferStatus transferStatus) {
TransferData.Builder builder = new TransferData.Builder()
.setGainingClientId(gainingClient.getId())
.setTransferRequestTime(now)
.setLosingClientId(losingClient.getId())
.setPendingTransferExpirationTime(transferExpirationTime)
.setTransferRequestTrid(trid)
.setTransferStatus(transferStatus);
setTransferDataProperties(builder);
return builder;
}
private PollMessage createPollMessage(
Client client, TransferStatus transferStatus, DateTime eventTime) {
ImmutableList.Builder<ResponseData> responseData = new ImmutableList.Builder<>();
responseData.add(createTransferResponse(
existingResource, createTransferDataBuilder(transferStatus).build(), now));
if (client.getId().equals(gainingClient.getId())) {
responseData.add(createPendingTransferNotificationResponse(
existingResource, trid, true, now));
}
return new PollMessage.OneTime.Builder()
.setClientId(client.getId())
.setEventTime(eventTime)
.setMsg(transferStatus.getMessage())
.setResponseData(responseData.build())
.setParent(historyEntry)
.build();
}
@Override
@SuppressWarnings("unchecked")
protected final R createOrMutateResource() {
// Figure out transfer expiration time once we've verified that the existingResource does in
// fact exist (otherwise we won't know which TLD to get this figure off of).
transferExpirationTime = now.plus(getAutomaticTransferLength());
// When a transfer is requested, a poll message is created to notify the losing registrar.
PollMessage requestPollMessage = createPollMessage(losingClient, TransferStatus.PENDING, now);
// If the transfer is server approved, this message will be sent to the gaining registrar. */
PollMessage serverApproveGainingPollMessage =
createPollMessage(gainingClient, TransferStatus.SERVER_APPROVED, transferExpirationTime);
// If the transfer is server approved, this message will be sent to the losing registrar. */
PollMessage serverApproveLosingPollMessage =
createPollMessage(losingClient, TransferStatus.SERVER_APPROVED, transferExpirationTime);
ofy().save().entities(
requestPollMessage, serverApproveGainingPollMessage, serverApproveLosingPollMessage);
return (R) existingResource.asBuilder()
.setTransferData(createTransferDataBuilder(TransferStatus.PENDING)
.setServerApproveEntities(union(
getTransferServerApproveEntities(),
Key.create(serverApproveGainingPollMessage),
Key.create(serverApproveLosingPollMessage)))
.build())
.addStatusValue(StatusValue.PENDING_TRANSFER)
.build();
}
/** Subclasses can override this to do further initialization. */
protected void initResourceTransferRequestFlow() {}
/**
* Subclasses can override this to return the keys of any entities that need to be deleted if the
* transfer ends in any state other than SERVER_APPROVED.
*/
protected Set<Key<? extends TransferServerApproveEntity>> getTransferServerApproveEntities() {
return ImmutableSet.of();
}
/** Check resource-specific invariants before allowing the transfer request to proceed. */
@SuppressWarnings("unused")
protected void verifyTransferRequestIsAllowed() throws EppException {}
/** Subclasses can override this to modify fields on the transfer data builder. */
protected void setTransferDataProperties(
@SuppressWarnings("unused") TransferData.Builder builder) {}
@Override
protected final EppOutput getOutput() throws EppException {
return createOutput(
SuccessWithActionPending,
createTransferResponse(newResource, newResource.getTransferData(), now),
getTransferResponseExtensions());
}
/** Subclasses can override this to return response extensions. */
protected ImmutableList<? extends ResponseExtension> getTransferResponseExtensions() {
return null;
}
@Override
protected final Set<StatusValue> getDisallowedStatuses() {
return TRANSFER_DISALLOWED_STATUSES;
}
/** Authorization info is required to request a transfer. */
public static class MissingTransferRequestAuthInfoException extends AuthorizationErrorException {
public MissingTransferRequestAuthInfoException() {
super("Authorization info is required to request a transfer");
}
}
/** Registrar already sponsors the object of this transfer request. */
public static class ObjectAlreadySponsoredException extends CommandUseErrorException {
public ObjectAlreadySponsoredException() {
super("Registrar already sponsors the object of this transfer request");
}
}
/** The resource is already pending transfer. */
public static class AlreadyPendingTransferException extends ObjectPendingTransferException {
public AlreadyPendingTransferException(String targetId) {
super(targetId);
}
}
}