// Copyright 2018 The Nomulus 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.export.datastore; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.api.client.json.GenericJson; import com.google.api.client.util.Key; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import google.registry.export.datastore.DatastoreAdmin.Get; import google.registry.util.Clock; import java.util.List; import java.util.Optional; import javax.annotation.Nullable; import org.joda.time.DateTime; import org.joda.time.Duration; /** * Model object that describes the details of an export or import operation in Cloud Datastore. * *

{@link Operation} instances are parsed from the JSON payload in Datastore response messages. */ public class Operation extends GenericJson { private static final String STATE_SUCCESS = "SUCCESSFUL"; private static final String STATE_PROCESSING = "PROCESSING"; @Key private String name; @Key private Metadata metadata; @Key private boolean done; /** For JSON deserialization. */ public Operation() {} /** Returns the name of this operation, which may be used in a {@link Get} request. */ public String getName() { checkState(name != null, "Name must not be null."); return name; } public boolean isExport() { return !isNullOrEmpty(getExportFolderUrl()); } public boolean isImport() { return !isNullOrEmpty(getMetadata().getInputUrl()); } public boolean isDone() { return done; } private String getState() { return getMetadata().getCommonMetadata().getState(); } public boolean isSuccessful() { return getState().equals(STATE_SUCCESS); } public boolean isProcessing() { return getState().equals(STATE_PROCESSING); } /** * Returns the elapsed time since starting if this operation is still running, or the total * running time if this operation has completed. */ public Duration getRunningTime(Clock clock) { return new Duration( getStartTime(), getMetadata().getCommonMetadata().getEndTime().orElse(clock.nowUtc())); } public DateTime getStartTime() { return getMetadata().getCommonMetadata().getStartTime(); } public ImmutableSet getKinds() { return ImmutableSet.copyOf(getMetadata().getEntityFilter().getKinds()); } /** * Returns the URL to the GCS folder that holds the exported data. This folder is created by * Datastore and is under the {@code outputUrlPrefix} set to {@linkplain * DatastoreAdmin#export(String, java.util.Collection) the export request}. * * @throws IllegalStateException if this is not an export operation */ public String getExportFolderUrl() { return getMetadata().getOutputUrlPrefix(); } /** * Returns the last segment of the {@linkplain #getExportFolderUrl() export folder URL} which can * be used as unique identifier of this export operation. This is a better ID than the {@linkplain * #getName() operation name}, which is opaque. * * @throws IllegalStateException if this is not an export operation */ public String getExportId() { String exportFolderUrl = getExportFolderUrl(); return exportFolderUrl.substring(exportFolderUrl.lastIndexOf('/') + 1); } public String getProgress() { StringBuilder result = new StringBuilder(); Progress progress = getMetadata().getProgressBytes(); if (progress != null) { result.append( String.format(" [%s/%s bytes]", progress.workCompleted, progress.workEstimated)); } progress = getMetadata().getProgressEntities(); if (progress != null) { result.append( String.format(" [%s/%s entities]", progress.workCompleted, progress.workEstimated)); } if (result.length() == 0) { return "Progress: N/A"; } return "Progress:" + result; } private Metadata getMetadata() { checkState(metadata != null, "Response metadata missing."); return metadata; } /** Models the common metadata properties of all operations. */ public static class CommonMetadata extends GenericJson { @Key private String startTime; @Key @Nullable private String endTime; @Key private String operationType; @Key private String state; public CommonMetadata() {} String getOperationType() { checkState(!isNullOrEmpty(operationType), "operationType may not be null or empty"); return operationType; } String getState() { checkState(!isNullOrEmpty(state), "state may not be null or empty"); return state; } DateTime getStartTime() { checkState(startTime != null, "StartTime missing."); return DateTime.parse(startTime); } Optional getEndTime() { return Optional.ofNullable(endTime).map(DateTime::parse); } } /** Models the metadata of a Cloud Datatore export or import operation. */ public static class Metadata extends GenericJson { @Key("common") private CommonMetadata commonMetadata; @Key private Progress progressEntities; @Key private Progress progressBytes; @Key private EntityFilter entityFilter; @Key private String inputUrl; @Key private String outputUrlPrefix; public Metadata() {} CommonMetadata getCommonMetadata() { checkState(commonMetadata != null, "CommonMetadata field is null."); return commonMetadata; } public Progress getProgressEntities() { return progressEntities; } public Progress getProgressBytes() { return progressBytes; } public EntityFilter getEntityFilter() { return entityFilter; } public String getInputUrl() { return checkUrls().inputUrl; } public String getOutputUrlPrefix() { return checkUrls().outputUrlPrefix; } Metadata checkUrls() { checkState( isNullOrEmpty(inputUrl) || isNullOrEmpty(outputUrlPrefix), "inputUrl and outputUrlPrefix must not be both present"); checkState( !isNullOrEmpty(inputUrl) || !isNullOrEmpty(outputUrlPrefix), "inputUrl and outputUrlPrefix must not be both missing"); return this; } } /** Progress of an export or import operation. */ public static class Progress extends GenericJson { @Key private long workCompleted; @Key private long workEstimated; public Progress() {} long getWorkCompleted() { return workCompleted; } public long getWorkEstimated() { return workEstimated; } } /** List of {@link Operation Operations}. */ public static class OperationList extends GenericJson { @Key private List operations; /** For JSON deserialization. */ public OperationList() {} ImmutableList toList() { return ImmutableList.copyOf(operations); } } }