1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.core; 17 18 import java.util.Arrays; 19 import java.util.HashMap; 20 import java.util.Map; 21 import java.util.Optional; 22 import java.util.function.BiConsumer; 23 import java.util.function.Function; 24 import java.util.function.Supplier; 25 import software.amazon.awssdk.annotations.SdkProtectedApi; 26 import software.amazon.awssdk.core.protocol.MarshallLocation; 27 import software.amazon.awssdk.core.protocol.MarshallingType; 28 import software.amazon.awssdk.core.traits.DefaultValueTrait; 29 import software.amazon.awssdk.core.traits.LocationTrait; 30 import software.amazon.awssdk.core.traits.Trait; 31 32 /** 33 * Metadata about a member in an {@link SdkPojo}. Contains information about how to marshall/unmarshall. 34 * 35 * @param <TypeT> Java Type of member. 36 */ 37 @SdkProtectedApi 38 public final class SdkField<TypeT> { 39 private final String memberName; 40 private final MarshallingType<? super TypeT> marshallingType; 41 private final MarshallLocation location; 42 private final String locationName; 43 private final String unmarshallLocationName; 44 private final Supplier<SdkPojo> constructor; 45 private final BiConsumer<Object, TypeT> setter; 46 private final Function<Object, TypeT> getter; 47 private final Map<Class<? extends Trait>, Trait> traits; 48 SdkField(Builder<TypeT> builder)49 private SdkField(Builder<TypeT> builder) { 50 this.memberName = builder.memberName; 51 this.marshallingType = builder.marshallingType; 52 this.traits = new HashMap<>(builder.traits); 53 this.constructor = builder.constructor; 54 this.setter = builder.setter; 55 this.getter = builder.getter; 56 57 // Eagerly dereference location trait since it's so commonly used. 58 LocationTrait locationTrait = getTrait(LocationTrait.class); 59 this.location = locationTrait.location(); 60 this.locationName = locationTrait.locationName(); 61 this.unmarshallLocationName = locationTrait.unmarshallLocationName(); 62 } 63 memberName()64 public String memberName() { 65 return memberName; 66 } 67 68 /** 69 * @return MarshallingType of member. Used primarily for marshaller/unmarshaller lookups. 70 */ marshallingType()71 public MarshallingType<? super TypeT> marshallingType() { 72 return marshallingType; 73 } 74 75 /** 76 * @return Location the member should be marshalled into (i.e. headers/query/path/payload). 77 */ location()78 public MarshallLocation location() { 79 return location; 80 } 81 82 /** 83 * @return The location name to use when marshalling. I.E. the field name of the JSON document, or the header name, etc. 84 */ locationName()85 public String locationName() { 86 return locationName; 87 } 88 89 /** 90 * @return The location name to use when unmarshalling. This is only needed for AWS/Query or EC2 services. All 91 * other services should use {@link #locationName} for both marshalling and unmarshalling. 92 */ unmarshallLocationName()93 public String unmarshallLocationName() { 94 return unmarshallLocationName; 95 } 96 constructor()97 public Supplier<SdkPojo> constructor() { 98 return constructor; 99 } 100 101 /** 102 * Gets the trait of the specified class if available. 103 * 104 * @param clzz Trait class to get. 105 * @param <T> Type of trait. 106 * @return Trait instance or null if trait is not present. 107 */ 108 @SuppressWarnings("unchecked") getTrait(Class<T> clzz)109 public <T extends Trait> T getTrait(Class<T> clzz) { 110 return (T) traits.get(clzz); 111 } 112 113 /** 114 * Gets the trait of the specified class if available. 115 * 116 * @param clzz Trait class to get. 117 * @param <T> Type of trait. 118 * @return Optional of trait instance. 119 */ 120 @SuppressWarnings("unchecked") getOptionalTrait(Class<T> clzz)121 public <T extends Trait> Optional<T> getOptionalTrait(Class<T> clzz) { 122 return Optional.ofNullable((T) traits.get(clzz)); 123 } 124 125 /** 126 * Gets the trait of the specified class, or throw {@link IllegalStateException} if not available. 127 * 128 * @param clzz Trait class to get. 129 * @param <T> Type of trait. 130 * @return Trait instance. 131 * @throws IllegalStateException if trait is not present. 132 */ 133 @SuppressWarnings("unchecked") getRequiredTrait(Class<T> clzz)134 public <T extends Trait> T getRequiredTrait(Class<T> clzz) throws IllegalStateException { 135 T trait = (T) traits.get(clzz); 136 if (trait == null) { 137 throw new IllegalStateException(memberName + " member is missing " + clzz.getSimpleName()); 138 } 139 return trait; 140 } 141 142 /** 143 * Checks if a given {@link Trait} is present on the field. 144 * 145 * @param clzz Trait class to check. 146 * @return True if trait is present, false if not. 147 */ containsTrait(Class<? extends Trait> clzz)148 public boolean containsTrait(Class<? extends Trait> clzz) { 149 return traits.containsKey(clzz); 150 } 151 152 /** 153 * Retrieves the current value of 'this' field from the given POJO. Uses the getter passed into the {@link Builder}. 154 * 155 * @param pojo POJO to retrieve value from. 156 * @return Current value of 'this' field in the POJO. 157 */ get(Object pojo)158 private TypeT get(Object pojo) { 159 return getter.apply(pojo); 160 } 161 162 /** 163 * Retrieves the current value of 'this' field from the given POJO. Uses the getter passed into the {@link Builder}. If the 164 * current value is null this method will look for the {@link DefaultValueTrait} on the field and attempt to resolve a default 165 * value. If the {@link DefaultValueTrait} is not present this just returns null. 166 * 167 * @param pojo POJO to retrieve value from. 168 * @return Current value of 'this' field in the POJO or default value if current value is null. 169 */ getValueOrDefault(Object pojo)170 public TypeT getValueOrDefault(Object pojo) { 171 TypeT val = this.get(pojo); 172 DefaultValueTrait trait = getTrait(DefaultValueTrait.class); 173 return (trait == null ? val : (TypeT) trait.resolveValue(val)); 174 } 175 176 /** 177 * Sets the given value on the POJO via the setter passed into the {@link Builder}. 178 * 179 * @param pojo POJO containing field to set. 180 * @param val Value of field. 181 */ 182 @SuppressWarnings("unchecked") set(Object pojo, Object val)183 public void set(Object pojo, Object val) { 184 setter.accept(pojo, (TypeT) val); 185 } 186 187 /** 188 * Creates a new instance of {@link Builder} bound to the specified type. 189 * 190 * @param marshallingType Type of field. 191 * @param <TypeT> Type of field. Must be a subtype of the {@link MarshallingType} type param. 192 * @return New builder instance. 193 */ builder(MarshallingType<? super TypeT> marshallingType)194 public static <TypeT> Builder<TypeT> builder(MarshallingType<? super TypeT> marshallingType) { 195 return new Builder<>(marshallingType); 196 } 197 198 /** 199 * Builder for {@link SdkField}. 200 * 201 * @param <TypeT> Java type of field. 202 */ 203 public static final class Builder<TypeT> { 204 205 private final MarshallingType<? super TypeT> marshallingType; 206 private String memberName; 207 private Supplier<SdkPojo> constructor; 208 private BiConsumer<Object, TypeT> setter; 209 private Function<Object, TypeT> getter; 210 private final Map<Class<? extends Trait>, Trait> traits = new HashMap<>(); 211 Builder(MarshallingType<? super TypeT> marshallingType)212 private Builder(MarshallingType<? super TypeT> marshallingType) { 213 this.marshallingType = marshallingType; 214 } 215 memberName(String memberName)216 public Builder<TypeT> memberName(String memberName) { 217 this.memberName = memberName; 218 return this; 219 } 220 221 /** 222 * Sets a {@link Supplier} which will create a new <b>MUTABLE</b> instance of the POJO. I.E. this will 223 * create the Builder for a given POJO and not the immutable POJO itself. 224 * 225 * @param constructor Supplier method to create the mutable POJO. 226 * @return This object for method chaining. 227 */ constructor(Supplier<SdkPojo> constructor)228 public Builder<TypeT> constructor(Supplier<SdkPojo> constructor) { 229 this.constructor = constructor; 230 return this; 231 } 232 233 /** 234 * Sets the {@link BiConsumer} which will accept an object and a value and set that value on the appropriate 235 * member of the object. This requires a <b>MUTABLE</b> pojo so thus this setter will be on the Builder 236 * for the given POJO. 237 * 238 * @param setter Setter method. 239 * @return This object for method chaining. 240 */ setter(BiConsumer<Object, TypeT> setter)241 public Builder<TypeT> setter(BiConsumer<Object, TypeT> setter) { 242 this.setter = setter; 243 return this; 244 } 245 246 /** 247 * Sets the {@link Function} that will accept an object and return the current value of 'this' field on that object. 248 * This will typically be a getter on the immutable representation of the POJO and is used mostly during marshalling. 249 * 250 * @param getter Getter method. 251 * @return This object for method chaining. 252 */ getter(Function<Object, TypeT> getter)253 public Builder<TypeT> getter(Function<Object, TypeT> getter) { 254 this.getter = getter; 255 return this; 256 } 257 258 /** 259 * Attaches one or more traits to the {@link SdkField}. Traits can have additional metadata and behavior that 260 * influence how a field is marshalled/unmarshalled. 261 * 262 * @param traits Traits to attach. 263 * @return This object for method chaining. 264 */ traits(Trait... traits)265 public Builder<TypeT> traits(Trait... traits) { 266 Arrays.stream(traits).forEach(t -> this.traits.put(t.getClass(), t)); 267 return this; 268 } 269 270 /** 271 * @return An immutable {@link SdkField}. 272 */ build()273 public SdkField<TypeT> build() { 274 return new SdkField<>(this); 275 } 276 } 277 } 278