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
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
49 private SdkField(Builder<TypeT> builder) {
50 this.marshallingType = builder.marshallingType;
51 this.traits = new HashMap<>(builder.traits);
52 this.constructor = builder.constructor;
53 this.setter = builder.setter;
54 this.getter = builder.getter;
55
56 // Eagerly dereference location trait since it's so commonly used.
57 LocationTrait locationTrait = getTrait(LocationTrait.class);
58 this.location = locationTrait.location();
59 this.locationName = locationTrait.locationName();
60 this.unmarshallLocationName = locationTrait.unmarshallLocationName();
61 }
62
63 /**
64 * @return MarshallingType of member. Used primarily for marshaller/unmarshaller lookups.
65 */
66 public MarshallingType<? super TypeT> marshallingType() {
67 return marshallingType;
68 }
69
70 /**
71 * @return Location the member should be marshalled into (i.e. headers/query/path/payload).
72 */
73 public MarshallLocation location() {
74 return location;
75 }
76
77 /**
78 * @return The location name to use when marshalling. I.E. the field name of the JSON document, or the header name, etc.
79 */
80 public String locationName() {
81 return locationName;
82 }
83
84 /**
85 * @return The location name to use when unmarshalling. This is only needed for AWS/Query or EC2 services. All
86 * other services should use {@link #locationName} for both marshalling and unmarshalling.
87 */
88 public String unmarshallLocationName() {
89 return unmarshallLocationName;
90 }
91
92 public Supplier<SdkPojo> constructor() {
93 return constructor;
94 }
95
96 /**
97 * Gets the trait of the specified class if available.
98 *
99 * @param clzz Trait class to get.
100 * @param <T> Type of trait.
101 * @return Trait instance or null if trait is not present.
102 */
103 @SuppressWarnings("unchecked")
104 public <T extends Trait> T getTrait(Class<T> clzz) {
105 return (T) traits.get(clzz);
106 }
107
108 /**
109 * Gets the trait of the specified class if available.
110 *
111 * @param clzz Trait class to get.
112 * @param <T> Type of trait.
113 * @return Optional of trait instance.
114 */
115 @SuppressWarnings("unchecked")
116 public <T extends Trait> Optional<T> getOptionalTrait(Class<T> clzz) {
117 return Optional.ofNullable((T) traits.get(clzz));
118 }
119
120 /**
121 * Checks if a given {@link Trait} is present on the field.
122 *
123 * @param clzz Trait class to check.
124 * @return True if trait is present, false if not.
125 */
126 public boolean containsTrait(Class<? extends Trait> clzz) {
127 return traits.containsKey(clzz);
128 }
129
130 /**
131 * Retrieves the current value of 'this' field from the given POJO. Uses the getter passed into the {@link Builder}.
132 *
133 * @param pojo POJO to retrieve value from.
134 * @return Current value of 'this' field in the POJO.
135 */
136 private TypeT get(Object pojo) {
137 return getter.apply(pojo);
138 }
139
140 /**
141 * Retrieves the current value of 'this' field from the given POJO. Uses the getter passed into the {@link Builder}. If the
142 * current value is null this method will look for the {@link DefaultValueTrait} on the field and attempt to resolve a default
143 * value. If the {@link DefaultValueTrait} is not present this just returns null.
144 *
145 * @param pojo POJO to retrieve value from.
146 * @return Current value of 'this' field in the POJO or default value if current value is null.
147 */
148 public TypeT getValueOrDefault(Object pojo) {
149 TypeT val = this.get(pojo);
150 DefaultValueTrait trait = getTrait(DefaultValueTrait.class);
151 return (trait == null ? val : (TypeT) trait.resolveValue(val));
152 }
153
154 /**
155 * Sets the given value on the POJO via the setter passed into the {@link Builder}.
156 *
157 * @param pojo POJO containing field to set.
158 * @param val Value of field.
159 */
160 @SuppressWarnings("unchecked")
161 public void set(Object pojo, Object val) {
162 setter.accept(pojo, (TypeT) val);
163 }
164
165 /**
166 * Creates a new instance of {@link Builder} bound to the specified type.
167 *
168 * @param marshallingType Type of field.
169 * @param <TypeT> Type of field. Must be a subtype of the {@link MarshallingType} type param.
170 * @return New builder instance.
171 */
172 public static <TypeT> Builder<TypeT> builder(MarshallingType<? super TypeT> marshallingType) {
173 return new Builder<>(marshallingType);
174 }
175
176 /**
177 * Builder for {@link SdkField}.
178 *
179 * @param <TypeT> Java type of field.
180 */
181 public static final class Builder<TypeT> {
182
183 private final MarshallingType<? super TypeT> marshallingType;
184 private Supplier<SdkPojo> constructor;
185 private BiConsumer<Object, TypeT> setter;
186 private Function<Object, TypeT> getter;
187 private final Map<Class<? extends Trait>, Trait> traits = new HashMap<>();
188
189 private Builder(MarshallingType<? super TypeT> marshallingType) {
190 this.marshallingType = marshallingType;
191 }
192
193 /**
194 * Sets a {@link Supplier} which will create a new <b>MUTABLE</b> instance of the POJO. I.E. this will
195 * create the Builder for a given POJO and not the immutable POJO itself.
196 *
197 * @param constructor Supplier method to create the mutable POJO.
198 * @return This object for method chaining.
199 */
200 public Builder<TypeT> constructor(Supplier<SdkPojo> constructor) {
201 this.constructor = constructor;
202 return this;
203 }
204
205 /**
206 * Sets the {@link BiConsumer} which will accept an object and a value and set that value on the appropriate
207 * member of the object. This requires a <b>MUTABLE</b> pojo so thus this setter will be on the Builder
208 * for the given POJO.
209 *
210 * @param setter Setter method.
211 * @return This object for method chaining.
212 */
213 public Builder<TypeT> setter(BiConsumer<Object, TypeT> setter) {
214 this.setter = setter;
215 return this;
216 }
217
218 /**
219 * Sets the {@link Function} that will accept an object and return the current value of 'this' field on that object.
220 * This will typically be a getter on the immutable representation of the POJO and is used mostly during marshalling.
221 *
222 * @param getter Getter method.
223 * @return This object for method chaining.
224 */
225 public Builder<TypeT> getter(Function<Object, TypeT> getter) {
226 this.getter = getter;
227 return this;
228 }
229
230 /**
231 * Attaches one or more traits to the {@link SdkField}. Traits can have additional metadata and behavior that
232 * influence how a field is marshalled/unmarshalled.
233 *
234 * @param traits Traits to attach.
235 * @return This object for method chaining.
236 */
237 public Builder<TypeT> traits(Trait... traits) {
238 Arrays.stream(traits).forEach(t -> this.traits.put(t.getClass(), t));
239 return this;
240 }
241
242 /**
243 * @return An immutable {@link SdkField}.
244 */
245 public SdkField<TypeT> build() {
246 return new SdkField<>(this);
247 }
248 }
249 }
250