1 /* 2 * Copyright 2016 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.cloud.dns; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.api.core.ApiFunction; 22 import com.google.api.services.dns.model.Change; 23 import com.google.cloud.StringEnumType; 24 import com.google.cloud.StringEnumValue; 25 import com.google.common.base.Function; 26 import com.google.common.base.MoreObjects; 27 import com.google.common.collect.ImmutableList; 28 import com.google.common.collect.Lists; 29 import java.io.Serializable; 30 import java.util.LinkedList; 31 import java.util.List; 32 import java.util.Objects; 33 import org.threeten.bp.Instant; 34 import org.threeten.bp.ZoneOffset; 35 import org.threeten.bp.format.DateTimeFormatter; 36 37 /** 38 * A class representing an atomic update to a collection of {@link RecordSet}s within a {@code 39 * Zone}. 40 * 41 * @see <a href="https://cloud.google.com/dns/api/v1/changes">Google Cloud DNS documentation</a> 42 */ 43 public class ChangeRequestInfo implements Serializable { 44 45 static final Function<Change, ChangeRequestInfo> FROM_PB_FUNCTION = 46 new Function<Change, ChangeRequestInfo>() { 47 @Override 48 public ChangeRequestInfo apply(Change pb) { 49 return ChangeRequestInfo.fromPb(pb); 50 } 51 }; 52 private static final long serialVersionUID = -6029143477639439169L; 53 private final List<RecordSet> additions; 54 private final List<RecordSet> deletions; 55 private final String generatedId; 56 private final Long startTimeMillis; 57 private final ChangeRequestInfo.Status status; 58 59 /** 60 * This enumerates the possible states of a change request. 61 * 62 * @see <a href="https://cloud.google.com/dns/api/v1/changes#resource">Google Cloud DNS 63 * documentation</a> 64 */ 65 public static final class Status extends StringEnumValue { 66 private static final long serialVersionUID = -294992980062438246L; 67 68 private static final ApiFunction<String, Status> CONSTRUCTOR = 69 new ApiFunction<String, Status>() { 70 @Override 71 public Status apply(String constant) { 72 return new Status(constant); 73 } 74 }; 75 76 private static final StringEnumType<Status> type = 77 new StringEnumType(Status.class, CONSTRUCTOR); 78 79 public static final Status PENDING = type.createAndRegister("PENDING"); 80 public static final Status DONE = type.createAndRegister("DONE"); 81 Status(String constant)82 private Status(String constant) { 83 super(constant); 84 } 85 86 /** 87 * Get the Status for the given String constant, and throw an exception if the constant is not 88 * recognized. 89 */ valueOfStrict(String constant)90 public static Status valueOfStrict(String constant) { 91 return type.valueOfStrict(constant); 92 } 93 94 /** Get the Status for the given String constant, and allow unrecognized values. */ valueOf(String constant)95 public static Status valueOf(String constant) { 96 return type.valueOf(constant); 97 } 98 99 /** Return the known values for Status. */ values()100 public static Status[] values() { 101 return type.values(); 102 } 103 } 104 105 /** A builder for {@code ChangeRequestInfo}. */ 106 public abstract static class Builder { 107 108 /** 109 * Sets a collection of {@link RecordSet}s which are to be added to the zone upon executing this 110 * {@code ChangeRequestInfo}. 111 */ setAdditions(List<RecordSet> additions)112 public abstract Builder setAdditions(List<RecordSet> additions); 113 114 /** 115 * Sets a collection of {@link RecordSet}s which are to be deleted from the zone upon executing 116 * this {@code ChangeRequestInfo}. 117 */ setDeletions(List<RecordSet> deletions)118 public abstract Builder setDeletions(List<RecordSet> deletions); 119 120 /** 121 * Adds a {@link RecordSet} to be <strong>added</strong> to the zone upon executing this {@code 122 * ChangeRequestInfo}. 123 */ add(RecordSet recordSet)124 public abstract Builder add(RecordSet recordSet); 125 126 /** 127 * Adds a {@link RecordSet} to be <strong>deleted</strong> to the zone upon executing this 128 * {@code ChangeRequestInfo}. 129 */ delete(RecordSet recordSet)130 public abstract Builder delete(RecordSet recordSet); 131 132 /** 133 * Clears the collection of {@link RecordSet}s which are to be added to the zone upon executing 134 * this {@code ChangeRequestInfo}. 135 */ clearAdditions()136 public abstract Builder clearAdditions(); 137 138 /** 139 * Clears the collection of {@link RecordSet}s which are to be deleted from the zone upon 140 * executing this {@code ChangeRequestInfo}. 141 */ clearDeletions()142 public abstract Builder clearDeletions(); 143 144 /** 145 * Removes a single {@link RecordSet} from the collection of records to be 146 * <strong>added</strong> to the zone upon executing this {@code ChangeRequestInfo}. 147 */ removeAddition(RecordSet recordSet)148 public abstract Builder removeAddition(RecordSet recordSet); 149 150 /** 151 * Removes a single {@link RecordSet} from the collection of records to be 152 * <strong>deleted</strong> from the zone upon executing this {@code ChangeRequestInfo}. 153 */ removeDeletion(RecordSet recordSet)154 public abstract Builder removeDeletion(RecordSet recordSet); 155 156 /** Associates a service-generated id to this {@code ChangeRequestInfo}. */ setGeneratedId(String generatedId)157 abstract Builder setGeneratedId(String generatedId); 158 159 /** Sets the time when this change request was started by a server. */ setStartTime(long startTimeMillis)160 abstract Builder setStartTime(long startTimeMillis); 161 162 /** Sets the current status of this {@code ChangeRequest}. */ setStatus(ChangeRequest.Status status)163 abstract Builder setStatus(ChangeRequest.Status status); 164 165 /** 166 * Creates a {@code ChangeRequestInfo} instance populated by the values associated with this 167 * builder. 168 */ build()169 public abstract ChangeRequestInfo build(); 170 } 171 172 static class BuilderImpl extends Builder { 173 private List<RecordSet> additions; 174 private List<RecordSet> deletions; 175 private String generatedId; 176 private Long startTimeMillis; 177 private ChangeRequestInfo.Status status; 178 BuilderImpl()179 BuilderImpl() { 180 this.additions = new LinkedList<>(); 181 this.deletions = new LinkedList<>(); 182 } 183 BuilderImpl(ChangeRequestInfo info)184 BuilderImpl(ChangeRequestInfo info) { 185 this.additions = Lists.newLinkedList(info.getAdditions()); 186 this.deletions = Lists.newLinkedList(info.getDeletions()); 187 this.generatedId = info.generatedId; 188 this.startTimeMillis = info.startTimeMillis; 189 this.status = info.status; 190 } 191 192 @Override setAdditions(List<RecordSet> additions)193 public Builder setAdditions(List<RecordSet> additions) { 194 this.additions = Lists.newLinkedList(checkNotNull(additions)); 195 return this; 196 } 197 198 @Override setDeletions(List<RecordSet> deletions)199 public Builder setDeletions(List<RecordSet> deletions) { 200 this.deletions = Lists.newLinkedList(checkNotNull(deletions)); 201 return this; 202 } 203 204 @Override add(RecordSet recordSet)205 public Builder add(RecordSet recordSet) { 206 this.additions.add(checkNotNull(recordSet)); 207 return this; 208 } 209 210 @Override delete(RecordSet recordSet)211 public Builder delete(RecordSet recordSet) { 212 this.deletions.add(checkNotNull(recordSet)); 213 return this; 214 } 215 216 @Override clearAdditions()217 public Builder clearAdditions() { 218 this.additions.clear(); 219 return this; 220 } 221 222 @Override clearDeletions()223 public Builder clearDeletions() { 224 this.deletions.clear(); 225 return this; 226 } 227 228 @Override removeAddition(RecordSet recordSet)229 public Builder removeAddition(RecordSet recordSet) { 230 this.additions.remove(recordSet); 231 return this; 232 } 233 234 @Override removeDeletion(RecordSet recordSet)235 public Builder removeDeletion(RecordSet recordSet) { 236 this.deletions.remove(recordSet); 237 return this; 238 } 239 240 @Override build()241 public ChangeRequestInfo build() { 242 return new ChangeRequestInfo(this); 243 } 244 245 @Override setGeneratedId(String generatedId)246 Builder setGeneratedId(String generatedId) { 247 this.generatedId = checkNotNull(generatedId); 248 return this; 249 } 250 251 @Override setStartTime(long startTimeMillis)252 Builder setStartTime(long startTimeMillis) { 253 this.startTimeMillis = startTimeMillis; 254 return this; 255 } 256 257 @Override setStatus(ChangeRequestInfo.Status status)258 Builder setStatus(ChangeRequestInfo.Status status) { 259 this.status = checkNotNull(status); 260 return this; 261 } 262 } 263 ChangeRequestInfo(BuilderImpl builder)264 ChangeRequestInfo(BuilderImpl builder) { 265 this.additions = ImmutableList.copyOf(builder.additions); 266 this.deletions = ImmutableList.copyOf(builder.deletions); 267 this.generatedId = builder.generatedId; 268 this.startTimeMillis = builder.startTimeMillis; 269 this.status = builder.status; 270 } 271 272 /** Returns an empty builder for the {@code ChangeRequestInfo} class. */ newBuilder()273 public static Builder newBuilder() { 274 return new BuilderImpl(); 275 } 276 277 /** Creates a builder populated with values of this {@code ChangeRequestInfo}. */ toBuilder()278 public Builder toBuilder() { 279 return new BuilderImpl(this); 280 } 281 282 /** 283 * Returns the list of {@link RecordSet}s to be added to the zone upon submitting this change 284 * request. 285 */ getAdditions()286 public List<RecordSet> getAdditions() { 287 return additions; 288 } 289 290 /** 291 * Returns the list of {@link RecordSet}s to be deleted from the zone upon submitting this change 292 * request. 293 */ getDeletions()294 public List<RecordSet> getDeletions() { 295 return deletions; 296 } 297 298 /** Returns the service-generated id for this change request. */ getGeneratedId()299 public String getGeneratedId() { 300 return generatedId; 301 } 302 303 /** Returns the time when this change request was started by the server. */ getStartTimeMillis()304 public Long getStartTimeMillis() { 305 return startTimeMillis; 306 } 307 308 /** 309 * Returns the status of this change request. If the change request has not been applied yet, the 310 * status is {@code PENDING}. 311 */ status()312 public ChangeRequestInfo.Status status() { 313 return getStatus(); 314 } 315 316 /** 317 * Returns the status of this change request. If the change request has not been applied yet, the 318 * status is {@code PENDING}. 319 */ getStatus()320 public ChangeRequestInfo.Status getStatus() { 321 return status; 322 } 323 toPb()324 Change toPb() { 325 Change pb = new Change(); 326 // set id 327 if (getGeneratedId() != null) { 328 pb.setId(getGeneratedId()); 329 } 330 // set timestamp 331 if (getStartTimeMillis() != null) { 332 pb.setStartTime( 333 DateTimeFormatter.ISO_DATE_TIME 334 .withZone(ZoneOffset.UTC) 335 .format(Instant.ofEpochMilli(getStartTimeMillis()))); 336 } 337 // set status 338 if (status() != null) { 339 pb.setStatus(status().name().toLowerCase()); 340 } 341 // set a list of additions 342 pb.setAdditions(Lists.transform(getAdditions(), RecordSet.TO_PB_FUNCTION)); 343 // set a list of deletions 344 pb.setDeletions(Lists.transform(getDeletions(), RecordSet.TO_PB_FUNCTION)); 345 return pb; 346 } 347 fromPb(Change pb)348 static ChangeRequestInfo fromPb(Change pb) { 349 Builder builder = newBuilder(); 350 if (pb.getId() != null) { 351 builder.setGeneratedId(pb.getId()); 352 } 353 if (pb.getStartTime() != null) { 354 builder.setStartTime( 355 DateTimeFormatter.ISO_DATE_TIME.parse(pb.getStartTime(), Instant.FROM).toEpochMilli()); 356 } 357 if (pb.getStatus() != null) { 358 // we are assuming that status indicated in pb is a lower case version of the enum name 359 builder.setStatus(ChangeRequest.Status.valueOf(pb.getStatus().toUpperCase())); 360 } 361 if (pb.getDeletions() != null) { 362 builder.setDeletions(Lists.transform(pb.getDeletions(), RecordSet.FROM_PB_FUNCTION)); 363 } 364 if (pb.getAdditions() != null) { 365 builder.setAdditions(Lists.transform(pb.getAdditions(), RecordSet.FROM_PB_FUNCTION)); 366 } 367 return builder.build(); 368 } 369 370 @Override equals(Object obj)371 public boolean equals(Object obj) { 372 return obj == this 373 || obj != null 374 && obj.getClass().equals(ChangeRequestInfo.class) 375 && toPb().equals(((ChangeRequestInfo) obj).toPb()); 376 } 377 378 @Override hashCode()379 public int hashCode() { 380 return Objects.hash(additions, deletions, generatedId, startTimeMillis, status); 381 } 382 383 @Override toString()384 public String toString() { 385 return MoreObjects.toStringHelper(this) 386 .add("additions", additions) 387 .add("deletions", deletions) 388 .add("generatedId", generatedId) 389 .add("startTimeMillis", startTimeMillis) 390 .add("status", status) 391 .toString(); 392 } 393 } 394