no.ssb.lds.api.persistence.flattened.FlattenedDocument Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of linked-data-store-persistence-provider-api Show documentation
Show all versions of linked-data-store-persistence-provider-api Show documentation
LinkedDataStore Persistence Provider API
package no.ssb.lds.api.persistence.flattened;
import no.ssb.lds.api.persistence.DocumentKey;
import no.ssb.lds.api.persistence.streaming.Fragment;
import no.ssb.lds.api.persistence.streaming.FragmentType;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import static java.util.Optional.ofNullable;
public class FlattenedDocument {
private final DocumentKey key;
private final Map leafNodesByPath;
private final boolean deleted;
public FlattenedDocument(DocumentKey key, Map leafNodesByPath, boolean deleted) {
this.key = key;
this.leafNodesByPath = leafNodesByPath;
this.deleted = deleted;
}
public DocumentKey key() {
return key;
}
public Map leafNodesByPath() {
return leafNodesByPath;
}
public FlattenedDocumentLeafNode leaf(String path) {
return leafNodesByPath.get(path);
}
public boolean deleted() {
return deleted;
}
public boolean contains(String path, String value) {
return ofNullable(leafNodesByPath.get(path)).map(leaf -> leaf.value()).map(v -> value.equals(v)).orElse(Boolean.FALSE);
}
@Override
public String toString() {
return "FlattenedDocument{" +
"key=" + key +
", fragmentByPath=" + leafNodesByPath +
", deleted=" + deleted +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FlattenedDocument document = (FlattenedDocument) o;
return deleted == document.deleted &&
Objects.equals(key, document.key) &&
Objects.equals(leafNodesByPath, document.leafNodesByPath);
}
@Override
public int hashCode() {
return Objects.hash(key, leafNodesByPath, deleted);
}
Iterator fragmentIterator() {
// TODO Iterable directly rather than creating temporary collection?
List allDocumentFragments = new LinkedList<>();
for (Map.Entry entry : leafNodesByPath.entrySet()) {
Iterator fragmentIterator = entry.getValue().fragmentIterator();
while (fragmentIterator.hasNext()) {
Fragment fragment = fragmentIterator.next();
allDocumentFragments.add(fragment);
}
}
return allDocumentFragments.iterator();
}
static FlattenedDocument decodeDocument(DocumentKey documentKey, Map> fragmentsByPath, int fragmentValueCapacityBytes) {
TreeMap leafNodesByPath = new TreeMap<>();
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
CharBuffer out = CharBuffer.allocate(256);
boolean deleted = false;
for (Map.Entry> entry : fragmentsByPath.entrySet()) {
String path = entry.getKey();
List fragments = entry.getValue();
if (fragments.isEmpty()) {
throw new IllegalStateException("No fragments for path: " + path);
}
FragmentType fragmentType = fragments.get(0).fragmentType();
if (FragmentType.STRING == fragmentType) {
StringBuilder value = new StringBuilder();
decoder.reset();
out.clear();
ByteBuffer in = null;
for (Fragment fragment : fragments) {
if (fragment.deleteMarker()) {
return new FlattenedDocument(documentKey, Collections.emptyMap(), true);
}
in = ByteBuffer.wrap(fragment.value());
CoderResult coderResult = decoder.decode(in, out, false);
throwRuntimeExceptionIfError(coderResult);
while (coderResult.isOverflow()) {
// drain out buffer
value.append(out.flip());
out.clear();
coderResult = decoder.decode(in, out, false);
throwRuntimeExceptionIfError(coderResult);
}
// underflow but possibly more fragments in leaf-node
}
// underflow and all fragments decoded
CoderResult endOfInputCoderResult = decoder.decode(in, out, true);
throwRuntimeExceptionIfError(endOfInputCoderResult);
CoderResult flushCoderResult = decoder.flush(out);
throwRuntimeExceptionIfError(flushCoderResult);
value.append(out.flip());
leafNodesByPath.put(path, new FlattenedDocumentLeafNode(documentKey, path, FragmentType.STRING, value.toString(), fragmentValueCapacityBytes));
} else if (FragmentType.NUMERIC == fragmentType) {
byte[] value = fragments.get(0).value();
leafNodesByPath.put(path, new FlattenedDocumentLeafNode(documentKey, path, FragmentType.NUMERIC, new String(value, StandardCharsets.UTF_8), fragmentValueCapacityBytes));
} else if (FragmentType.BOOLEAN == fragmentType) {
byte[] byteValue = fragments.get(0).value();
String value = (byteValue[0] == (byte) 1) ? "true" : "false";
leafNodesByPath.put(path, new FlattenedDocumentLeafNode(documentKey, path, FragmentType.BOOLEAN, value, fragmentValueCapacityBytes));
} else if (FragmentType.NULL == fragmentType) {
leafNodesByPath.put(path, new FlattenedDocumentLeafNode(documentKey, path, FragmentType.NULL, null, fragmentValueCapacityBytes));
} else if (FragmentType.EMPTY_ARRAY == fragmentType) {
leafNodesByPath.put(path, new FlattenedDocumentLeafNode(documentKey, path, FragmentType.EMPTY_ARRAY, null, fragmentValueCapacityBytes));
} else if (FragmentType.EMPTY_OBJECT == fragmentType) {
leafNodesByPath.put(path, new FlattenedDocumentLeafNode(documentKey, path, FragmentType.EMPTY_OBJECT, null, fragmentValueCapacityBytes));
} else if (FragmentType.DELETED == fragmentType) {
deleted = true;
} else {
throw new IllegalStateException("Unknown FragmentType: " + fragmentType);
}
}
return new FlattenedDocument(documentKey, leafNodesByPath, deleted);
}
private static void throwRuntimeExceptionIfError(CoderResult coderResult) {
if (coderResult.isError()) {
try {
coderResult.throwException();
throw new IllegalStateException();
} catch (CharacterCodingException e) {
throw new RuntimeException(e);
}
}
}
}