1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package org.socialsignin.spring.data.dynamodb.core;
17  
18  import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
19  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
20  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.FailedBatch;
21  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
22  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel;
23  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
24  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
25  import com.amazonaws.services.dynamodbv2.datamodeling.KeyPair;
26  import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedQueryList;
27  import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList;
28  import com.amazonaws.services.dynamodbv2.model.QueryRequest;
29  import com.amazonaws.services.dynamodbv2.model.QueryResult;
30  import com.amazonaws.services.dynamodbv2.model.Select;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.socialsignin.spring.data.dynamodb.mapping.event.AfterDeleteEvent;
34  import org.socialsignin.spring.data.dynamodb.mapping.event.AfterLoadEvent;
35  import org.socialsignin.spring.data.dynamodb.mapping.event.AfterQueryEvent;
36  import org.socialsignin.spring.data.dynamodb.mapping.event.AfterSaveEvent;
37  import org.socialsignin.spring.data.dynamodb.mapping.event.AfterScanEvent;
38  import org.socialsignin.spring.data.dynamodb.mapping.event.BeforeDeleteEvent;
39  import org.socialsignin.spring.data.dynamodb.mapping.event.BeforeSaveEvent;
40  import org.socialsignin.spring.data.dynamodb.mapping.event.DynamoDBMappingEvent;
41  import org.springframework.beans.BeansException;
42  import org.springframework.context.ApplicationContext;
43  import org.springframework.context.ApplicationContextAware;
44  import org.springframework.context.ApplicationEventPublisher;
45  import org.springframework.lang.Nullable;
46  import org.springframework.util.Assert;
47  
48  import java.util.List;
49  import java.util.Map;
50  import java.util.function.Function;
51  import java.util.stream.Collectors;
52  
53  public class DynamoDBTemplate implements DynamoDBOperations, ApplicationContextAware {
54  	private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBTemplate.class);
55  	private final DynamoDBMapper dynamoDBMapper;
56  	private final AmazonDynamoDB amazonDynamoDB;
57  	private final DynamoDBMapperConfig dynamoDBMapperConfig;
58  	private ApplicationEventPublisher eventPublisher;
59  
60  	
61  
62  
63  
64  
65  
66  
67  
68  
69  	public DynamoDBTemplate(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig dynamoDBMapperConfig) {
70  		this(amazonDynamoDB, dynamoDBMapperConfig, null);
71  	}
72  
73  	
74  
75  
76  
77  
78  
79  
80  
81  	public DynamoDBTemplate(AmazonDynamoDB amazonDynamoDB, DynamoDBMapper dynamoDBMapper) {
82  		this(amazonDynamoDB, null, dynamoDBMapper);
83  	}
84  
85  	
86  
87  
88  
89  
90  
91  
92  	public DynamoDBTemplate(AmazonDynamoDB amazonDynamoDB) {
93  		this(amazonDynamoDB, null, null);
94  	}
95  
96  	
97  
98  
99  
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 	public DynamoDBTemplate(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig dynamoDBMapperConfig,
111 			DynamoDBMapper dynamoDBMapper) {
112 		Assert.notNull(amazonDynamoDB, "amazonDynamoDB must not be null!");
113 		this.amazonDynamoDB = amazonDynamoDB;
114 
115 		if (dynamoDBMapperConfig == null) {
116 			this.dynamoDBMapperConfig = DynamoDBMapperConfig.DEFAULT;
117 		} else {
118 
119 			
120 			
121 			
122 			
123 			
124 			
125 			
126 			
127 			DynamoDBMapperConfig.Builder emptyBuilder = DynamoDBMapperConfig.builder(); 
128 
129 			if (dynamoDBMapperConfig.getConversionSchema() == null) {
130 				LOGGER.warn(
131 						"No ConversionSchema set in the provided dynamoDBMapperConfig! Merging with DynamoDBMapperConfig.DEFAULT - Please see https://git.io/DynamoDBMapperConfig");
132 				
133 				emptyBuilder.withConversionSchema(DynamoDBMapperConfig.DEFAULT.getConversionSchema());
134 			}
135 
136 			if (dynamoDBMapperConfig.getTypeConverterFactory() == null) {
137 				LOGGER.warn(
138 						"No TypeConverterFactory set in the provided dynamoDBMapperConfig! Merging with DynamoDBMapperConfig.DEFAULT - Please see https://git.io/DynamoDBMapperConfig");
139 				
140 				emptyBuilder.withTypeConverterFactory(DynamoDBMapperConfig.DEFAULT.getTypeConverterFactory());
141 			}
142 
143 			
144 			this.dynamoDBMapperConfig = new DynamoDBMapperConfig(dynamoDBMapperConfig, emptyBuilder.build());
145 		}
146 
147 		if (dynamoDBMapper == null) {
148 			this.dynamoDBMapper = new DynamoDBMapper(amazonDynamoDB, dynamoDBMapperConfig);
149 		} else {
150 			this.dynamoDBMapper = dynamoDBMapper;
151 		}
152 	}
153 
154 	@Override
155 	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
156 		this.eventPublisher = applicationContext;
157 	}
158 
159 	@Override
160 	public <T> int count(Class<T> domainClass, DynamoDBQueryExpression<T> queryExpression) {
161 		return dynamoDBMapper.count(domainClass, queryExpression);
162 	}
163 
164 	@Override
165 	public <T> PaginatedQueryList<T> query(Class<T> domainClass, DynamoDBQueryExpression<T> queryExpression) {
166 		PaginatedQueryList<T> results = dynamoDBMapper.query(domainClass, queryExpression);
167 		maybeEmitEvent(results, AfterQueryEvent::new);
168 		return results;
169 	}
170 
171 	@Override
172 	public <T> int count(Class<T> domainClass, DynamoDBScanExpression scanExpression) {
173 		return dynamoDBMapper.count(domainClass, scanExpression);
174 	}
175 
176 	@Override
177 	public <T> T load(Class<T> domainClass, Object hashKey, Object rangeKey) {
178 		T entity = dynamoDBMapper.load(domainClass, hashKey, rangeKey);
179 		maybeEmitEvent(entity, AfterLoadEvent::new);
180 
181 		return entity;
182 	}
183 
184 	@Override
185 	public <T> T load(Class<T> domainClass, Object hashKey) {
186 		T entity = dynamoDBMapper.load(domainClass, hashKey);
187 		maybeEmitEvent(entity, AfterLoadEvent::new);
188 
189 		return entity;
190 	}
191 
192 	@Override
193 	public <T> PaginatedScanList<T> scan(Class<T> domainClass, DynamoDBScanExpression scanExpression) {
194 		PaginatedScanList<T> results = dynamoDBMapper.scan(domainClass, scanExpression);
195 		maybeEmitEvent(results, AfterScanEvent::new);
196 		return results;
197 	}
198 
199 	@SuppressWarnings("unchecked")
200 	@Override
201 	public <T> List<T> batchLoad(Map<Class<?>, List<KeyPair>> itemsToGet) {
202 		return dynamoDBMapper.batchLoad(itemsToGet).values().stream().flatMap(v -> v.stream()).map(e -> (T) e)
203 				.map(entity -> {
204 					maybeEmitEvent(entity, AfterLoadEvent::new);
205 					return entity;
206 				}).collect(Collectors.toList());
207 	}
208 
209 	@Override
210 	public <T> T save(T entity) {
211 		maybeEmitEvent(entity, BeforeSaveEvent::new);
212 		dynamoDBMapper.save(entity);
213 		maybeEmitEvent(entity, AfterSaveEvent::new);
214 		return entity;
215 
216 	}
217 
218 	@Override
219 	public List<FailedBatch> batchSave(Iterable<?> entities) {
220 		entities.forEach(it -> maybeEmitEvent(it, BeforeSaveEvent::new));
221 
222 		List<FailedBatch> result = dynamoDBMapper.batchSave(entities);
223 
224 		entities.forEach(it -> maybeEmitEvent(it, AfterSaveEvent::new));
225 		return result;
226 	}
227 
228 	@Override
229 	public <T> T delete(T entity) {
230 		maybeEmitEvent(entity, BeforeDeleteEvent::new);
231 		dynamoDBMapper.delete(entity);
232 		maybeEmitEvent(entity, AfterDeleteEvent::new);
233 		return entity;
234 	}
235 
236 	@Override
237 	public List<FailedBatch> batchDelete(Iterable<?> entities) {
238 		entities.forEach(it -> maybeEmitEvent(it, BeforeDeleteEvent::new));
239 
240 		List<FailedBatch> result = dynamoDBMapper.batchDelete(entities);
241 
242 		entities.forEach(it -> maybeEmitEvent(it, AfterDeleteEvent::new));
243 		return result;
244 	}
245 
246 	@Override
247 	public <T> PaginatedQueryList<T> query(Class<T> clazz, QueryRequest queryRequest) {
248 		QueryResult queryResult = amazonDynamoDB.query(queryRequest);
249 		return new PaginatedQueryList<T>(dynamoDBMapper, clazz, amazonDynamoDB, queryRequest, queryResult,
250 				dynamoDBMapperConfig.getPaginationLoadingStrategy(), dynamoDBMapperConfig);
251 	}
252 
253 	@Override
254 	public <T> int count(Class<T> clazz, QueryRequest mutableQueryRequest) {
255 		mutableQueryRequest.setSelect(Select.COUNT);
256 
257 		
258 		int count = 0;
259 		QueryResult queryResult = null;
260 		do {
261 			queryResult = amazonDynamoDB.query(mutableQueryRequest);
262 			count += queryResult.getCount();
263 			mutableQueryRequest.setExclusiveStartKey(queryResult.getLastEvaluatedKey());
264 		} while (queryResult.getLastEvaluatedKey() != null);
265 
266 		return count;
267 	}
268 
269 	@Override
270 	public <T> String getOverriddenTableName(Class<T> domainClass, String tableName) {
271 		if (dynamoDBMapperConfig.getTableNameOverride() != null) {
272 			if (dynamoDBMapperConfig.getTableNameOverride().getTableName() != null) {
273 				tableName = dynamoDBMapperConfig.getTableNameOverride().getTableName();
274 			} else {
275 				tableName = dynamoDBMapperConfig.getTableNameOverride().getTableNamePrefix() + tableName;
276 			}
277 		} else if (dynamoDBMapperConfig.getTableNameResolver() != null) {
278 			tableName = dynamoDBMapperConfig.getTableNameResolver().getTableName(domainClass, dynamoDBMapperConfig);
279 		}
280 
281 		return tableName;
282 	}
283 
284 	
285 
286 
287 	@Override
288 	public <T> DynamoDBMapperTableModel<T> getTableModel(Class<T> domainClass) {
289 		return dynamoDBMapper.getTableModel(domainClass, dynamoDBMapperConfig);
290 	}
291 
292 	protected <T> void maybeEmitEvent(@Nullable T source, Function<T, DynamoDBMappingEvent<T>> factory) {
293 		if (eventPublisher != null) {
294 			if (source != null) {
295 				DynamoDBMappingEvent<T> event = factory.apply(source);
296 
297 				eventPublisher.publishEvent(event);
298 			}
299 		}
300 
301 	}
302 }