All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fleetpin.graphql.database.manager.dynamo.TableUtil Maven / Gradle / Ivy

There is a newer version: 3.0.3
Show newest version
/*
 * 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.fleetpin.graphql.database.manager.dynamo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BinaryNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fleetpin.graphql.database.manager.Table;
import com.fleetpin.graphql.database.manager.annotations.GlobalIndex;
import com.fleetpin.graphql.database.manager.annotations.SecondaryIndex;
import com.fleetpin.graphql.database.manager.util.BackupItem;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Spliterators;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList;
import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

public class TableUtil {

	static String getSecondaryGlobal(Table entity) {
		for (var method : entity.getClass().getMethods()) {
			if (method.isAnnotationPresent(GlobalIndex.class)) {
				try {
					var secondary = method.invoke(entity);
					if (secondary instanceof Optional) {
						secondary = ((Optional) secondary).orElse(null);
					}
					return (String) secondary;
				} catch (ReflectiveOperationException e) {
					throw new RuntimeException(e);
				}
			}
		}
		return null;
	}

	static String getSecondaryOrganisation(Table entity) {
		for (var method : entity.getClass().getMethods()) {
			if (method.isAnnotationPresent(SecondaryIndex.class)) {
				try {
					var secondary = method.invoke(entity);
					if (secondary instanceof Optional) {
						secondary = ((Optional) secondary).orElse(null);
					}
					return (String) secondary;
				} catch (ReflectiveOperationException e) {
					throw new RuntimeException(e);
				}
			}
		}
		return null;
	}

	public static Map toAttributes(ObjectMapper mapper, Object entity) {
		Map entries = new HashMap<>();
		ObjectNode tree = mapper.valueToTree(entity);

		Iterator> fields = tree.fields();
		fields.forEachRemaining(entry -> {
			Entry field = entry;
			AttributeValue attribute = toAttribute(field.getValue());
			if (attribute != null) {
				entries.put(field.getKey(), attribute);
			}
		});
		return entries;
	}

	static Map toAttributes(ObjectMapper mapper, BackupItem entity) {
		Map entries = new HashMap<>();
		//Handle links specially, so remove here
		var entityItem = entity.getItem();
		LinkedHashMap links = mapper.convertValue(entityItem.get("links"), new TypeReference<>() {});

		if (links != null) {
			entityItem.remove("links");
			Map linkMap = new HashMap<>();
			links.forEach((key, value) -> {
				linkMap.put(key, AttributeValue.builder().ss(value).build());
			});
			entries.put("links", AttributeValue.builder().m(linkMap).build());
		}

		if (entity.isHashed()) {
			entries.put("hashed", AttributeValue.builder().bool(true).build());
		}
		if (entity.getParallelHash() != null) {
			entries.put("parallelHash", AttributeValue.builder().s(entity.getParallelHash()).build());
		}

		ObjectNode tree = mapper.valueToTree(entityItem);

		Iterator> fields = tree.fields();
		fields.forEachRemaining(entry -> {
			Entry field = entry;
			AttributeValue attribute;
			attribute = toAttribute(field.getValue());
			if (attribute != null) {
				entries.put(field.getKey(), attribute);
			}
		});

		return entries;
	}

	public static AttributeValue toAttribute(JsonNode value) {
		switch (value.getNodeType()) {
			case NUMBER:
				return AttributeValue.builder().n(value.asText()).build();
			case BINARY:
				try {
					return AttributeValue.builder().b(SdkBytes.fromByteArray(value.binaryValue())).build();
				} catch (IOException e) {
					throw new UncheckedIOException(e);
				}
			case BOOLEAN:
				return AttributeValue.builder().bool(value.asBoolean()).build();
			case STRING:
				String v = value.asText();
				if (v.isEmpty()) {
					return null;
				}
				return AttributeValue.builder().s(v).build();
			case ARRAY:
				return processArray(value);
			case OBJECT:
				ObjectNode tree = (ObjectNode) value;
				Map entries = new HashMap<>();
				Iterator> fields = tree.fields();
				fields.forEachRemaining(entry -> {
					Entry field = entry;
					entries.put(field.getKey(), toAttribute(field.getValue()));
				});
				return AttributeValue.builder().m(entries).build();
			case NULL:
				return AttributeValue.builder().nul(true).build();
			default:
				throw new RuntimeException("unknown type " + value.getNodeType());
		}
	}

	private static AttributeValue processArray(JsonNode value) {
		return basicArray(value);
	}

	private static AttributeValue basicArray(JsonNode value) {
		List array = stream(value).map(TableUtil::toAttribute).collect(Collectors.toList());
		return AttributeValue.builder().l(array).build();
	}

	private static Stream stream(JsonNode array) {
		return StreamSupport.stream(Spliterators.spliteratorUnknownSize(array.iterator(), 0), false);
	}

	public static  T convertTo(ObjectMapper mapper, AttributeValue attributeValue, Class type) {
		if (attributeValue == null) {
			return null;
		}
		try {
			return mapper.treeToValue(toJson(mapper, attributeValue), type);
		} catch (JsonProcessingException e) {
			throw new UncheckedIOException(e);
		}
	}

	public static  T convertTo(ObjectMapper mapper, Map item, Class type) {
		try {
			ObjectNode objNode = mapper.createObjectNode();
			item.forEach((key, v) -> {
				objNode.set(key, toJson(mapper, v));
			});
			return mapper.treeToValue(objNode, type);
		} catch (JsonProcessingException e) {
			throw new UncheckedIOException(e);
		}
	}

	public static JsonNode toJson(ObjectMapper mapper, AttributeValue value) {
		if (value.bool() != null) {
			return BooleanNode.valueOf(value.bool());
		}
		if (value.nul() != null && value.nul()) {
			return NullNode.instance;
		}
		if (value.b() != null) {
			return BinaryNode.valueOf(value.b().asByteArray());
		}
		if (value.n() != null) {
			double v = Double.parseDouble(value.n());
			if (Math.floor(v) == v && value.n().indexOf('.') == -1 && Long.MAX_VALUE > v && Long.MIN_VALUE < v) {
				return LongNode.valueOf(Long.parseLong(value.n()));
			}
			return DoubleNode.valueOf(v);
		}
		if (value.s() != null) {
			return TextNode.valueOf(value.s());
		}

		Object defArray = DefaultSdkAutoConstructList.getInstance();
		Object defMap = DefaultSdkAutoConstructMap.getInstance();
		if (value.bs() != defArray) {
			ArrayNode arrayNode = mapper.createArrayNode();
			for (SdkBytes b : value.bs()) {
				arrayNode.add(BinaryNode.valueOf(b.asByteArray()));
			}
			return arrayNode;
		}
		if (value.l() != defArray) {
			ArrayNode arrayNode = mapper.createArrayNode();
			for (AttributeValue l : value.l()) {
				arrayNode.add(toJson(mapper, l));
			}
			return arrayNode;
		}

		if (value.ns() != defArray) {
			ArrayNode arrayNode = mapper.createArrayNode();
			for (String s : value.ns()) {
				arrayNode.add(TextNode.valueOf(s));
			}
			return arrayNode;
		}
		if (value.ss() != defArray) {
			ArrayNode arrayNode = mapper.createArrayNode();
			for (String s : value.ss()) {
				arrayNode.add(TextNode.valueOf(s));
			}
			return arrayNode;
		}
		if (value.m() != defMap) {
			ObjectNode objNode = mapper.createObjectNode();
			if (value.m().isEmpty()) {
				return NullNode.instance;
			}
			value
				.m()
				.forEach((key, v) -> {
					objNode.set(key, toJson(mapper, v));
				});
			return objNode;
		}
		throw new RuntimeException("Unsupported type " + value);
	}

	static  CompletableFuture> all(List> collect) {
		return CompletableFuture
			.allOf(collect.toArray(CompletableFuture[]::new))
			.thenApply(__ ->
				collect
					.stream()
					.map(m -> {
						try {
							return m.get();
						} catch (InterruptedException | ExecutionException e) {
							throw new RuntimeException(e);
						}
					})
					.collect(Collectors.toList())
			);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy