/*
 * Copyright 2017 Google, Inc.
 *
 * 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.netflix.spinnaker.kork.artifacts.model;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.netflix.spinnaker.kork.annotations.FieldsAreNullableByDefault;
import com.netflix.spinnaker.kork.annotations.MethodsReturnNonnullByDefault;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
@EqualsAndHashCode
@FieldsAreNullableByDefault
@JsonDeserialize(builder = Artifact.ArtifactBuilder.class)
// Use camelCase regardless of the ObjectMapper configuration. (Detailed comment in ArtifactTest.)
@JsonNaming
public final class Artifact {
  private String type;
  private boolean customKind;
  private String name;
  private String version;
  private String location;
  private String reference;
  @Nonnull private Map<String, Object> metadata;
  private String artifactAccount;
  private String provenance;
  private String uuid;

  @Builder(toBuilder = true)
  private Artifact(
      String type,
      boolean customKind,
      String name,
      String version,
      String location,
      String reference,
      Map<String, Object> metadata,
      String artifactAccount,
      String provenance,
      String uuid) {
    this.type = type;
    this.customKind = customKind;
    this.name = name;
    this.version = version;
    this.location = location;
    this.reference = reference;
    // Shallow copy the metadata map so changes to the input map after creating the artifact
    // don't affect its metadata.
    this.metadata = Optional.ofNullable(metadata).map(HashMap::new).orElseGet(HashMap::new);
    this.artifactAccount = artifactAccount;
    this.provenance = provenance;
    this.uuid = uuid;
  }

  // All setters for this class are deprecated. In general, artifacts are passed around the pipeline
  // context and are liberally serialized and deserialized, which makes mutating them fraught with
  // peril (because you might or might not actually be operating on a copy of the artifact you
  // actually want to mutate). In order to remove this cause of subtle bugs, this class will soon
  // become immutable and these setters will be removed.

  // The encouraged pattern for adding a field to an artifact is:
  // Artifact newArtifact = artifact.toBuilder().artifactAccount("my-account").build()

  @Deprecated
  public void setType(String type) {
    this.type = type;
  }

  @Deprecated
  public void setCustomKind(boolean customKind) {
    this.customKind = customKind;
  }

  @Deprecated
  public void setName(String name) {
    this.name = name;
  }

  @Deprecated
  public void setVersion(String version) {
    this.version = version;
  }

  @Deprecated
  public void setLocation(String location) {
    this.location = location;
  }

  @Deprecated
  public void setReference(String reference) {
    this.reference = reference;
  }

  @Deprecated
  public void setMetadata(Map<String, Object> metadata) {
    this.metadata = Optional.ofNullable(metadata).orElseGet(HashMap::new);
  }

  /**
   * This function is deprecated in favor of using {@link Artifact#getMetadata(String)} to get the
   * particular key of interest.
   *
   * <p>The reason is that we would like the metadata to be (at least shallowly) immutable, and it
   * is much easier to safely enforce that by avoiding giving callers access to the raw map in the
   * first place.
   */
  @Deprecated
  @Nonnull
  public Map<String, Object> getMetadata() {
    return metadata;
  }

  @Nullable
  public Object getMetadata(String key) {
    return metadata.get(key);
  }

  @Deprecated
  public void setArtifactAccount(String artifactAccount) {
    this.artifactAccount = artifactAccount;
  }

  @Deprecated
  public void setProvenance(String provenance) {
    this.provenance = provenance;
  }

  @Deprecated
  public void setUuid(String uuid) {
    this.uuid = uuid;
  }

  @JsonIgnoreProperties("kind")
  @JsonPOJOBuilder(withPrefix = "")
  @MethodsReturnNonnullByDefault
  @JsonNaming
  public static class ArtifactBuilder {
    @Nonnull private Map<String, Object> metadata = new HashMap<>();

    public ArtifactBuilder metadata(@Nullable Map<String, Object> metadata) {
      this.metadata = Optional.ofNullable(metadata).orElseGet(HashMap::new);
      return this;
    }

    // Add extra, unknown data to the metadata map:
    @JsonAnySetter
    public ArtifactBuilder putMetadata(String key, Object value) {
      metadata.put(key, value);
      return this;
    }
  }
}

@@ -1620,24 +1620,30 @@
 %7B%0A  private 
+final 
 String type;
@@ -1653,16 +1653,22 @@
 private 
+final 
 boolean 
@@ -1685,24 +1685,30 @@
 ;%0A  private 
+final 
 String name;
@@ -1714,24 +1714,30 @@
 ;%0A  private 
+final 
 String versi
@@ -1746,24 +1746,30 @@
 ;%0A  private 
+final 
 String locat
@@ -1779,24 +1779,30 @@
 ;%0A  private 
+final 
 String refer
@@ -1818,32 +1818,38 @@
 Nonnull private 
+final 
 Map%3CString, Obje
@@ -1868,24 +1868,30 @@
 ;%0A  private 
+final 
 String artif
@@ -1908,24 +1908,30 @@
 ;%0A  private 
+final 
 String prove
@@ -1947,16 +1947,22 @@
 private 
+final 
 String u
@@ -2760,2149 +2760,88 @@
 %0A%0A  
-// All setters for this class are deprecated. In general, artifacts are passed around the pipeline%0A  // context and are liberally serialized and deserialized, which makes mutating them fraught with%0A  // peril (because you might or might not actually be operating on a copy of the artifact you%0A  // actually want to mutate). In order to remove this cause of subtle bugs, this class will soon%0A  // become immutable and these setters will be removed.%0A%0A  // The encouraged pattern for adding a field to an artifact is:%0A  // Artifact newArtifact = artifact.toBuilder().artifactAccount(%22my-account%22).build()%0A%0A  @Deprecated%0A  public void setType(String type) %7B%0A    this.type = type;%0A  %7D%0A%0A  @Deprecated%0A  public void setCustomKind(boolean customKind) %7B%0A    this.customKind = customKind;%0A  %7D%0A%0A  @Deprecated%0A  public void setName(String name) %7B%0A    this.name = name;%0A  %7D%0A%0A  @Deprecated%0A  public void setVersion(String version) %7B%0A    this.version = version;%0A  %7D%0A%0A  @Deprecated%0A  public void setLocation(String location) %7B%0A    this.location = location;%0A  %7D%0A%0A  @Deprecated%0A  public void setReference(String reference) %7B%0A    this.reference = reference;%0A  %7D%0A%0A  @Deprecated%0A  public void setMetadata(Map%3CString, Object%3E metadata) %7B%0A    this.metadata = Optional.ofNullable(metadata).orElseGet(HashMap::new);%0A  %7D%0A%0A  /**%0A   * This function is deprecated in favor of using %7B@link Artifact#getMetadata(String)%7D to get the%0A   * particular key of interest.%0A   *%0A   * %3Cp%3EThe reason is that we would like the metadata to be (at least shallowly) immutable, and it%0A   * is much easier to safely enforce that by avoiding giving callers access to the raw map in the%0A   * first place.%0A   */%0A  @Deprecated%0A  @Nonnull%0A  public Map%3CString, Object%3E getMetadata() %7B%0A    return metadata;%0A  %7D%0A%0A  @Nullable%0A  public Object getMetadata(String key) %7B%0A    return metadata.get(key);%0A  %7D%0A%0A  @Deprecated%0A  public void setArtifactAccount(String artifactAccount) %7B%0A    this.artifactAccount = artifactAccount;%0A  %7D%0A%0A  @Deprecated%0A  public void setProvenance(String provenance) %7B%0A    this.provenance = provenance;%0A  %7D%0A%0A  @Deprecated%0A  public void setUuid(String uuid) %7B%0A    this.uuid = uuid
+@Nullable%0A  public Object getMetadata(String key) %7B%0A    return metadata.get(key)
 ;%0A  

Make fields final (#701)