org.modeshape.jcr.cache.document.DocumentTranslator Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* 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 org.modeshape.jcr.cache.document;
import java.math.BigDecimal;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import org.infinispan.schematic.DocumentFactory;
import org.infinispan.schematic.Schematic;
import org.infinispan.schematic.SchematicEntry;
import org.infinispan.schematic.document.Binary;
import org.infinispan.schematic.document.Document;
import org.infinispan.schematic.document.Document.Field;
import org.infinispan.schematic.document.EditableArray;
import org.infinispan.schematic.document.EditableDocument;
import org.infinispan.schematic.document.Null;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.text.NoOpEncoder;
import org.modeshape.common.text.TextDecoder;
import org.modeshape.common.text.TextEncoder;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.JcrLexicon;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.cache.CachedNode.ReferenceType;
import org.modeshape.jcr.cache.ChildReference;
import org.modeshape.jcr.cache.ChildReferences;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.document.SessionNode.ChangedAdditionalParents;
import org.modeshape.jcr.cache.document.SessionNode.ChangedChildren;
import org.modeshape.jcr.cache.document.SessionNode.Insertions;
import org.modeshape.jcr.cache.document.SessionNode.ReferrerChanges;
import org.modeshape.jcr.value.BinaryFactory;
import org.modeshape.jcr.value.BinaryKey;
import org.modeshape.jcr.value.DateTimeFactory;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.Path.Segment;
import org.modeshape.jcr.value.PathFactory;
import org.modeshape.jcr.value.Property;
import org.modeshape.jcr.value.PropertyFactory;
import org.modeshape.jcr.value.Reference;
import org.modeshape.jcr.value.ReferenceFactory;
import org.modeshape.jcr.value.ValueFactories;
import org.modeshape.jcr.value.ValueFactory;
import org.modeshape.jcr.value.basic.NodeKeyReference;
import org.modeshape.jcr.value.binary.BinaryStoreException;
import org.modeshape.jcr.value.binary.EmptyBinaryValue;
import org.modeshape.jcr.value.binary.ExternalBinaryValue;
import org.modeshape.jcr.value.binary.InMemoryBinaryValue;
/**
* A utility class that encapsulates all the logic for reading from and writing to {@link Document} instances.
*/
public class DocumentTranslator implements DocumentConstants {
private final DocumentStore documentStore;
private final AtomicLong largeStringSize = new AtomicLong();
private final ExecutionContext context;
private final PropertyFactory propertyFactory;
private final ValueFactories factories;
private final PathFactory paths;
private final NameFactory names;
private final DateTimeFactory dates;
private final BinaryFactory binaries;
private final ValueFactory longs;
private final ValueFactory doubles;
private final ValueFactory uris;
private final ValueFactory decimals;
private final ValueFactory strings;
private final ReferenceFactory refs;
private final ReferenceFactory weakrefs;
private final ReferenceFactory simplerefs;
private final TextEncoder encoder = NoOpEncoder.getInstance();
private final TextDecoder decoder = NoOpEncoder.getInstance();
public DocumentTranslator( ExecutionContext context,
DocumentStore documentStore,
long largeStringSize ) {
this.documentStore = documentStore;
this.largeStringSize.set(largeStringSize);
this.context = context;
this.propertyFactory = this.context.getPropertyFactory();
this.factories = this.context.getValueFactories();
this.paths = this.factories.getPathFactory();
this.names = this.factories.getNameFactory();
this.dates = this.factories.getDateFactory();
this.binaries = this.factories.getBinaryFactory();
this.longs = this.factories.getLongFactory();
this.doubles = this.factories.getDoubleFactory();
this.uris = this.factories.getUriFactory();
this.decimals = this.factories.getDecimalFactory();
this.refs = this.factories.getReferenceFactory();
this.weakrefs = this.factories.getWeakReferenceFactory();
this.simplerefs = this.factories.getSimpleReferenceFactory();
this.strings = this.factories.getStringFactory();
assert this.largeStringSize.get() >= 0;
}
public DocumentTranslator withLargeStringSize( long largeStringSize ) {
return new DocumentTranslator(context, documentStore, largeStringSize);
}
public final ValueFactory getStringFactory() {
return strings;
}
public final NameFactory getNameFactory() {
return names;
}
public final ReferenceFactory getReferenceFactory() {
return this.refs;
}
public final PropertyFactory getPropertyFactory() {
return propertyFactory;
}
void setMinimumStringLengthForBinaryStorage( long largeValueSize ) {
assert largeValueSize > -1;
this.largeStringSize.set(largeValueSize);
}
/**
* Obtain the preferred {@link NodeKey key} for the parent of this node. Because a node can be used in more than once place,
* it may technically have more than one parent. Therefore, in such cases this method prefers the parent that is in the
* {@code primaryWorkspaceKey} and, if there is no such parent, the parent that is in the {@code secondaryWorkspaceKey}.
*
* @param document the document for the node; may not be null
* @param primaryWorkspaceKey the key for the workspace in which the parent should preferably exist; may be null
* @param secondaryWorkspaceKey the key for the workspace in which the parent should exist if not in the primary workspace;
* may be null
* @return the key representing the preferred parent, or null if the document contains no parent reference or if the parent
* reference(s) do not have the specified workspace keys
*/
public NodeKey getParentKey( Document document,
String primaryWorkspaceKey,
String secondaryWorkspaceKey ) {
Object value = document.get(PARENT);
return keyFrom(value, primaryWorkspaceKey, secondaryWorkspaceKey);
}
public Set getAdditionalParentKeys( Document document ) {
Object value = document.get(PARENT);
if (value instanceof String) {
// The first key is the primary parent, so there are no more additional parents ...
return Collections.emptySet();
}
if (value instanceof List>) {
List> values = (List>)value;
if (values.size() == 1) {
// The first key is the primary parent, so there are no more additional parents ...
return Collections.emptySet();
}
Set keys = new LinkedHashSet();
Iterator> iter = values.iterator();
// skip the first parent, since it's the primary parent ...
iter.next();
while (iter.hasNext()) {
Object v = iter.next();
if (v == null) {
continue;
}
String key = (String)v;
keys.add(new NodeKey(key));
}
return keys;
}
return Collections.emptySet();
}
private final NodeKey keyFrom( Object value,
String primaryWorkspaceKey,
String secondaryWorkspaceKey ) {
if (value instanceof String) {
return new NodeKey((String)value);
}
if (value instanceof List>) {
List> values = (List>)value;
if (values.size() == 1) {
return keyFrom(values.get(0), primaryWorkspaceKey, secondaryWorkspaceKey);
}
NodeKey keyWithSecondaryWorkspaceKey = null;
for (Object v : values) {
if (v == null) {
continue;
}
NodeKey key = new NodeKey((String)v);
if (key.getWorkspaceKey().equals(primaryWorkspaceKey)) {
return key;
}
if (keyWithSecondaryWorkspaceKey == null && secondaryWorkspaceKey != null
&& key.getWorkspaceKey().equals(secondaryWorkspaceKey)) {
keyWithSecondaryWorkspaceKey = key;
}
}
return keyWithSecondaryWorkspaceKey;
}
return null;
}
public void getProperties( Document document,
Map result ) {
// Get the properties container ...
Document properties = document.getDocument(PROPERTIES);
if (properties != null) {
// For all namespaces ...
for (Field nsField : properties.fields()) {
String namespaceUri = nsField.getName();
Document nsDoc = nsField.getValueAsDocument();
// Get all the properties in the namespace ...
for (Field propField : nsDoc.fields()) {
String localName = propField.getName();
Name propertyName = names.create(namespaceUri, localName);
if (!result.containsKey(propertyName)) {
Object fieldValue = propField.getValue();
Property property = propertyFor(propertyName, fieldValue);
result.put(propertyName, property);
}
}
}
}
}
public int countProperties( Document document ) {
// Get the properties container ...
Document properties = document.getDocument(PROPERTIES);
if (properties == null) {
return 0;
}
int count = 0;
for (Field nsField : properties.fields()) {
Document urlProps = nsField.getValueAsDocument();
if (urlProps != null) {
for (Field propField : urlProps.fields()) {
if (!Null.matches(propField.getValue())) {
++count;
}
}
}
}
return count;
}
public boolean hasProperties( Document document ) {
// Get the properties container ...
Document properties = document.getDocument(PROPERTIES);
if (properties == null) {
return false;
}
for (Field nsField : properties.fields()) {
Document urlProps = nsField.getValueAsDocument();
if (urlProps != null) {
for (Field propField : urlProps.fields()) {
if (!Null.matches(propField.getValue())) {
return true;
}
}
}
}
return false;
}
public boolean hasProperty( Document document,
Name propertyName ) {
// Get the properties container ...
Document properties = document.getDocument(PROPERTIES);
if (properties == null) {
return false;
}
// Get the namespace container for the property name's namespace ...
Document urlProps = properties.getDocument(propertyName.getNamespaceUri());
if (urlProps == null) {
return false;
}
// Get the property ...
Object fieldValue = urlProps.get(propertyName.getLocalName());
return !Null.matches(fieldValue);
}
public Property getProperty( Document document,
String propertyName ) {
return getProperty(document, names.create(propertyName));
}
public Property getProperty( Document document,
Name propertyName ) {
// Get the properties container ...
Document properties = document.getDocument(PROPERTIES);
if (properties == null) {
return null;
}
// Get the namespace container for the property name's namespace ...
Document urlProps = properties.getDocument(propertyName.getNamespaceUri());
if (urlProps == null) {
return null;
}
// Get the property ...
Object fieldValue = urlProps.get(propertyName.getLocalName());
return fieldValue == null ? null : propertyFor(propertyName, fieldValue);
}
public Name getPrimaryType( Document document ) {
return names.create(getProperty(document, JcrLexicon.PRIMARY_TYPE).getFirstValue());
}
public String getPrimaryTypeName( Document document ) {
return strings.create(getProperty(document, JcrLexicon.PRIMARY_TYPE).getFirstValue());
}
public Set getMixinTypes( Document document ) {
Property prop = getProperty(document, JcrLexicon.MIXIN_TYPES);
if (prop == null || prop.size() == 0) return Collections.emptySet();
if (prop.size() == 1) {
Name name = names.create(prop.getFirstValue());
return Collections.singleton(name);
}
Set result = new HashSet();
for (Object value : prop) {
Name name = names.create(value);
result.add(name);
}
return result;
}
public Set getMixinTypeNames( Document document ) {
Property prop = getProperty(document, JcrLexicon.MIXIN_TYPES);
if (prop == null || prop.size() == 0) return Collections.emptySet();
if (prop.size() == 1) {
String name = strings.create(prop.getFirstValue());
return Collections.singleton(name);
}
Set result = new HashSet();
for (Object value : prop) {
String name = strings.create(value);
result.add(name);
}
return result;
}
protected Property propertyFor( Name propertyName,
Object fieldValue ) {
Object value = valueFromDocument(fieldValue);
if (value instanceof List>) {
List> values = (List>)value;
return propertyFactory.create(propertyName, values);
}
return propertyFactory.create(propertyName, value);
}
public void setProperty( EditableDocument document,
Property property,
Set unusedBinaryKeys,
Set usedBinaryKeys) {
// Get or create the properties container ...
EditableDocument properties = document.getDocument(PROPERTIES);
if (properties == null) {
properties = document.setDocument(PROPERTIES);
}
// Get or create the namespace container for the property name's namespace ...
Name propertyName = property.getName();
String namespaceUri = propertyName.getNamespaceUri();
EditableDocument urlProps = properties.getDocument(namespaceUri);
if (urlProps == null) {
urlProps = properties.setDocument(namespaceUri);
}
// Get the old value ...
String localName = propertyName.getLocalName();
Object oldValue = urlProps.get(localName);
decrementBinaryReferenceCount(oldValue, unusedBinaryKeys, usedBinaryKeys);
// Now set the property ...
if (property.isEmpty()) {
urlProps.setArray(localName);
} else if (property.isMultiple()) {
EditableArray values = Schematic.newArray(property.size());
for (Object v : property) {
values.add(valueToDocument(v, unusedBinaryKeys, usedBinaryKeys));
}
urlProps.setArray(localName, values);
} else {
assert property.isSingle();
Object value = valueToDocument(property.getFirstValue(), unusedBinaryKeys, usedBinaryKeys);
if (value == null) {
urlProps.remove(localName);
} else {
urlProps.set(localName, value);
}
}
}
public Property removeProperty( EditableDocument document,
Name propertyName,
Set unusedBinaryKeys,
Set usedBinaryKeys) {
// Get the properties container if it exists ...
EditableDocument properties = document.getDocument(PROPERTIES);
if (properties == null) {
// Doesn't contain the property ...
return null;
}
// Get the namespace container for the property name's namespace ...
String namespaceUri = propertyName.getNamespaceUri();
EditableDocument urlProps = properties.getDocument(namespaceUri);
if (urlProps == null) {
// Doesn't contain the property ...
return null;
}
// Now remove the property ...
String localName = propertyName.getLocalName();
Object fieldValue = urlProps.remove(localName);
// We're removing a reference to a binary value, and we need to decrement the reference count ...
decrementBinaryReferenceCount(fieldValue, unusedBinaryKeys, usedBinaryKeys);
// Now remove the namespace if empty ...
if (urlProps.isEmpty()) {
properties.remove(namespaceUri);
}
return fieldValue == null ? null : propertyFor(propertyName, fieldValue);
}
public void addPropertyValues( EditableDocument document,
Name propertyName,
boolean isMultiple,
Collection> values,
Set unusedBinaryKeys,
Set usedBinaryKeys) {
assert values != null;
int numValues = values.size();
if (numValues == 0) {
return;
}
// Get or create the properties container ...
EditableDocument properties = document.getDocument(PROPERTIES);
if (properties == null) {
properties = document.setDocument(PROPERTIES);
}
// Get or create the namespace container for the property name's namespace ...
String namespaceUri = propertyName.getNamespaceUri();
EditableDocument urlProps = properties.getDocument(namespaceUri);
if (urlProps == null) {
urlProps = properties.setDocument(namespaceUri);
}
// Now add the value to the property ...
String localName = propertyName.getLocalName();
Object propValue = urlProps.get(localName);
if (propValue == null) {
// We have to create the property ...
if (isMultiple || numValues > 1) {
EditableArray array = Schematic.newArray(numValues);
for (Object value : values) {
array.addValue(valueToDocument(value, unusedBinaryKeys, usedBinaryKeys));
}
urlProps.setArray(localName, array);
} else {
urlProps.set(localName, valueToDocument(values.iterator().next(), unusedBinaryKeys, usedBinaryKeys));
}
} else if (propValue instanceof List>) {
// Decrement the reference count of any binary references ...
decrementBinaryReferenceCount(propValue, unusedBinaryKeys, usedBinaryKeys);
// There's an existing property with multiple values ...
EditableArray array = urlProps.getArray(localName);
for (Object value : values) {
value = valueToDocument(value, unusedBinaryKeys, usedBinaryKeys);
array.addValueIfAbsent(value);
}
} else {
// Decrement the reference count of any binary references ...
decrementBinaryReferenceCount(propValue, unusedBinaryKeys, usedBinaryKeys);
// There's just a single value ...
if (numValues == 1) {
Object value = valueToDocument(values.iterator().next(), unusedBinaryKeys, usedBinaryKeys);
if (!value.equals(propValue)) {
// But the existing value is different, so we have to change to an array ...
EditableArray array = Schematic.newArray(value, propValue);
urlProps.setArray(localName, array);
}
} else {
EditableArray array = Schematic.newArray(numValues);
for (Object value : values) {
value = valueToDocument(value, unusedBinaryKeys, usedBinaryKeys);
if (!value.equals(propValue)) {
array.addValue(value);
}
}
assert !array.isEmpty();
urlProps.setArray(localName, array);
}
}
}
public void removePropertyValues( EditableDocument document,
Name propertyName,
Collection> values,
Set unusedBinaryKeys,
Set usedBinaryKeys) {
assert values != null;
int numValues = values.size();
if (numValues == 0) {
return;
}
// Get the properties container if it exists ...
EditableDocument properties = document.getDocument(PROPERTIES);
if (properties == null) {
// Doesn't contain the property ...
return;
}
// Get the namespace container for the property name's namespace ...
String namespaceUri = propertyName.getNamespaceUri();
EditableDocument urlProps = properties.getDocument(namespaceUri);
if (urlProps == null) {
// Doesn't contain the property ...
return;
}
// Now add the value to the property ...
String localName = propertyName.getLocalName();
Object propValue = urlProps.get(localName);
decrementBinaryReferenceCount(propValue, unusedBinaryKeys, usedBinaryKeys);
if (propValue instanceof List>) {
// There's an existing property with multiple values ...
EditableArray array = urlProps.getArray(localName);
for (Object value : values) {
value = valueToDocument(value, null, null);
array.remove(value);
}
} else if (propValue != null) {
// There's just a single value ...
for (Object value : values) {
value = valueToDocument(value, unusedBinaryKeys, usedBinaryKeys);
if (value.equals(propValue)) {
// And the value matches, so remove the field ...
urlProps.remove(localName);
break;
}
}
}
// Now remove the namespace if empty ...
if (urlProps.isEmpty()) {
properties.remove(namespaceUri);
}
}
public void setParents( EditableDocument document,
NodeKey parent,
NodeKey oldParent,
ChangedAdditionalParents additionalParents ) {
Object existingParent = document.get(PARENT);
if (existingParent == null) {
if (parent != null) {
if (additionalParents == null || additionalParents.isEmpty()) {
document.setString(PARENT, parent.toString());
} else {
EditableArray parents = Schematic.newArray(additionalParents.additionCount() + 1);
parents.add(parent.toString());
for (NodeKey added : additionalParents.getAdditions()) {
parents.add(added.toString());
}
document.set(PARENT, parents);
}
} else if (additionalParents != null && !additionalParents.isEmpty()) {
EditableArray parents = Schematic.newArray(additionalParents.additionCount());
for (NodeKey added : additionalParents.getAdditions()) {
parents.add(added.toString());
}
document.set(PARENT, parents);
}
return;
}
if (existingParent instanceof List>) {
// Remove the old parent and add the new parent ...
EditableArray parents = document.getArray(PARENT);
if (parent != null && !parent.equals(oldParent)) {
parents.addStringIfAbsent(parent.toString());
if (oldParent != null) {
parents.remove((Object)oldParent.toString());
}
}
if (additionalParents != null) {
for (NodeKey removedParent : additionalParents.getRemovals()) {
// When the primary parent is removed and changed to one of the additional parents, then the additional
// parent is removed. Therefore, we only want to remove it if it does not equal the new parent ...
if (!removedParent.equals(parent)) {
parents.remove((Object)removedParent.toString()); // remove by value (not by name)
}
}
for (NodeKey added : additionalParents.getAdditions()) {
parents.addStringIfAbsent(added.toString());
}
}
} else if (existingParent instanceof String) {
String existing = (String)existingParent;
if (parent != null && (additionalParents == null || additionalParents.isEmpty())) {
String oldParentStr = oldParent != null ? oldParent.toString() : null;
if (existing.equals(oldParentStr)) {
// Just replace the
document.set(PARENT, parent.toString());
} else {
// Add it ...
EditableArray parents = Schematic.newArray(2);
parents.add(existing);
parents.add(parent.toString());
document.set(PARENT, parents);
}
} else {
// Just replace the existing 'parent' field ...
int totalNumber = additionalParents.additionCount() - additionalParents.removalCount() + 1;
assert totalNumber >= 0;
EditableArray parents = Schematic.newArray(totalNumber);
if (parent != null && !existingParent.equals(parent.toString())) {
parents.add(parent.toString());
} else {
parents.add(existingParent);
}
for (NodeKey removed : additionalParents.getRemovals()) {
parents.remove((Object)removed.toString());
}
for (NodeKey added : additionalParents.getAdditions()) {
parents.add(added.toString());
}
document.set(PARENT, parents);
}
}
}
public void setKey( EditableDocument document,
NodeKey key ) {
assert document.getString(KEY) == null;
document.setString(KEY, key.toString());
}
public void setKey( EditableDocument document,
String key ) {
assert key != null;
document.setString(KEY, key);
}
public String getKey( Document document ) {
return document.getString(KEY);
}
public void changeChildren( EditableDocument document,
ChangedChildren changedChildren,
ChildReferences appended ) {
assert !(changedChildren == null && appended == null);
// Get the total number of children and the number of children in this block ...
ChildReferencesInfo info = getChildReferencesInfo(document);
long newTotalSize = 0L;
EditableDocument doc = document;
EditableDocument lastDoc = document;
String lastDocKey = null;
if (changedChildren != null && !changedChildren.isEmpty()) {
Map insertionsByBeforeKey = changedChildren.getInsertionsByBeforeKey();
// Handle removals and renames ...
Set removals = changedChildren.getRemovals();
Map newNames = changedChildren.getNewNames();
while (doc != null) {
// we need to clean up projections
if (isFederatedDocument(doc) && !removals.isEmpty()) {
Set removalsStrings = new HashSet();
for (NodeKey key : removals) {
// only when we're dealing with a foreign key do we need to do this
if (!key.toString().startsWith(documentStore.getLocalSourceKey())) {
removalsStrings.add(key.toString());
}
}
removeFederatedSegments(doc, removalsStrings);
}
// Change the existing children ...
long blockCount = insertChildren(doc, insertionsByBeforeKey, removals, newNames);
newTotalSize += blockCount;
// Look at the 'childrenInfo' document for info about the next block of children ...
SchematicEntry nextEntry = null;
ChildReferencesInfo docInfo = doc == document ? info : getChildReferencesInfo(doc);
if (docInfo != null && docInfo.nextKey != null) {
// The children are segmented, so get the next block of children ...
nextEntry = documentStore.get(docInfo.nextKey);
}
if (nextEntry != null) {
assert docInfo != null && docInfo.nextKey != null;
// There is more than one block, so update the block size ...
doc.getDocument(CHILDREN_INFO).setNumber(BLOCK_SIZE, blockCount);
doc = documentStore.edit(docInfo.nextKey, true);
lastDoc = doc;
assert docInfo != null;
lastDocKey = docInfo.nextKey;
} else {
if (doc == document && doc.containsField(CHILDREN_INFO)) {
// This is still the first document, so there shouldn't be a block size ...
EditableDocument childInfo = doc.getDocument(CHILDREN_INFO);
childInfo.remove(BLOCK_SIZE);
childInfo.set(COUNT, newTotalSize);
}
doc = null;
}
}
} else {
// We're not inserting or removing children, so we've not modified the number of children ...
newTotalSize = info != null ? info.totalSize : 0L;
}
if (appended != null && appended.size() != 0) {
String lastKey = info != null ? info.lastKey : null;
if (lastKey != null && !lastKey.equals(lastDocKey)) {
// Find the last document ...
lastDoc = documentStore.edit(lastKey, true);
} else {
lastKey = null;
}
// Just append the new children to the end of the last document; we can use an asynchronous process
// to adjust/optimize the number of children in each block ...
EditableArray lastChildren = lastDoc.getOrCreateArray(CHILDREN);
for (ChildReference ref : appended) {
lastChildren.add(fromChildReference(ref));
}
if (lastDoc != document) {
// We've written to at least one other document, so update the block size ...
EditableDocument lastDocInfo = lastDoc.getOrCreateDocument(CHILDREN_INFO);
lastDocInfo.setNumber(BLOCK_SIZE, lastChildren.size());
}
// And update the total size and last block on the starting document ...
EditableDocument childInfo = document.getOrCreateDocument(CHILDREN_INFO);
newTotalSize += appended.size();
childInfo.setNumber(COUNT, newTotalSize);
// And if needed the reference to the last block ...
if (lastKey != null) {
childInfo.setString(LAST_BLOCK, lastKey);
}
}
}
protected long insertChildren( EditableDocument document,
Map insertionsByBeforeKey,
Set removals,
Map newNames ) {
List> children = document.getArray(CHILDREN);
LinkedHashSet newChildren = new LinkedHashSet();
if (children != null) {
// process existing children
for (Object value : children) {
ChildReference ref = childReferenceFrom(value);
if (ref == null) {
continue;
}
NodeKey childKey = ref.getKey();
// Are nodes inserted before this node?
Insertions insertions = insertionsByBeforeKey.remove(childKey);
if (insertions != null) {
for (ChildReference inserted : insertions.inserted()) {
newChildren.add(inserted);
}
}
if (removals.remove(childKey)) {
// The node is removed ...
} else {
// The node remains ...
Name newName = newNames.get(childKey);
if (newName != null) {
// But has been renamed ...
ref = ref.with(newName, 1);
}
newChildren.add(ref);
}
}
}
if (!insertionsByBeforeKey.isEmpty()) {
// there are transient insertions (due to reordering of transient nodes) that have to be inserted in a correct order
// if any reorderings involved existing children, they would have already been removed by the previous block
// note that these insertions have to be added as child because *they do not appear* in the appended list
LinkedList toBeInsertedInOrder = new LinkedList();
for (Insertions insertion : insertionsByBeforeKey.values()) {
// process the remaining insertions-before, which indicate transient & reordered children (reordering removes
// children
// from the appended list
for (ChildReference activeReference : insertion.inserted()) {
if (toBeInsertedInOrder.contains(activeReference)) {
// the current reference is already in the list
continue;
}
Insertions insertionsBeforeActive = insertionsByBeforeKey.get(activeReference.getKey());
if (insertionsBeforeActive == null) {
toBeInsertedInOrder.addFirst(activeReference);
continue;
}
for (ChildReference referenceBeforeActive : insertionsBeforeActive.inserted()) {
if (!toBeInsertedInOrder.contains(referenceBeforeActive)) {
toBeInsertedInOrder.add(referenceBeforeActive);
}
}
toBeInsertedInOrder.add(activeReference);
}
}
newChildren.addAll(toBeInsertedInOrder);
}
EditableArray newChildrenArray = Schematic.newArray(newChildren.size());
for (ChildReference childReference : newChildren) {
newChildrenArray.add(fromChildReference(childReference));
}
document.set(CHILDREN, newChildrenArray);
return newChildren.size();
}
public ChildReferences getChildReferences( WorkspaceCache cache,
Document document ) {
boolean hasChildren = document.containsField(CHILDREN);
boolean hasFederatedSegments = document.containsField(FEDERATED_SEGMENTS);
if (!hasChildren && !hasFederatedSegments) {
return ImmutableChildReferences.EMPTY_CHILD_REFERENCES;
}
ChildReferences internalChildRefs = hasChildren ? ImmutableChildReferences.create(this, document, CHILDREN) : ImmutableChildReferences.EMPTY_CHILD_REFERENCES;
ChildReferences externalChildRefs = hasFederatedSegments ? ImmutableChildReferences.create(this, document,
FEDERATED_SEGMENTS) : ImmutableChildReferences.EMPTY_CHILD_REFERENCES;
// Now look at the 'childrenInfo' document for info about the next block of children ...
ChildReferencesInfo info = getChildReferencesInfo(document);
if (!hasChildren) {
return ImmutableChildReferences.create(externalChildRefs, info, cache);
} else if (!hasFederatedSegments) {
return ImmutableChildReferences.create(internalChildRefs, info, cache);
} else {
return ImmutableChildReferences.create(internalChildRefs, info, externalChildRefs, cache);
}
}
/**
* Reads the children of the given block and returns a {@link ChildReferences} instance.
*
* @param block a {@code non-null} {@link Document} representing a block of children
* @return a {@code non-null} child references instance
*/
public ChildReferences getChildReferencesFromBlock( Document block ) {
if (!block.containsField(CHILDREN)) {
return ImmutableChildReferences.EMPTY_CHILD_REFERENCES;
}
return ImmutableChildReferences.create(this, block, CHILDREN);
}
public ChildReferencesInfo getChildReferencesInfo( Document document ) {
// Now look at the 'childrenInfo' document for info about the next block ...
Document childrenInfo = document.getDocument(CHILDREN_INFO);
if (childrenInfo != null) {
long totalSize = childrenInfo.getLong(COUNT, 0L);
long blockSize = childrenInfo.getLong(BLOCK_SIZE, 0L);
String nextBlockKey = childrenInfo.getString(NEXT_BLOCK);
String lastBlockKey = childrenInfo.getString(LAST_BLOCK, nextBlockKey);
return new ChildReferencesInfo(totalSize, blockSize, nextBlockKey, lastBlockKey);
}
return null;
}
@Immutable
public static class ChildReferencesInfo {
public final long totalSize;
public final long blockSize;
public final String nextKey;
public final String lastKey;
public ChildReferencesInfo( long totalSize,
long blockSize,
String nextKey,
String lastKey ) {
this.totalSize = totalSize;
this.blockSize = blockSize;
this.nextKey = nextKey;
this.lastKey = lastKey;
}
@Override
public String toString() {
return "totalSize: " + totalSize + "; blockSize: " + blockSize + "; nextKey: " + nextKey + "; lastKey: " + lastKey;
}
}
protected ChildReference childReferenceFrom( Object value ) {
if (value instanceof Document) {
Document doc = (Document)value;
String keyStr = doc.getString(KEY);
NodeKey key = new NodeKey(keyStr);
String nameStr = doc.getString(NAME);
Name name = names.create(nameStr, decoder);
// We always use 1 for the SNS index, since the SNS index is dependent upon SNS nodes before it
return new ChildReference(key, name, 1);
}
return null;
}
public EditableDocument fromChildReference( ChildReference ref ) {
// We don't write the
return Schematic.newDocument(KEY, valueToDocument(ref.getKey(), null, null), NAME, strings.create(ref.getName()));
}
public Set getReferrers( Document document,
ReferenceType type ) {
// Get the properties container ...
Document referrers = document.getDocument(REFERRERS);
if (referrers == null) {
return new HashSet();
}
// Get the NodeKeys in the respective arrays ...
Set result = new HashSet();
if (type != ReferenceType.WEAK) {
Document strong = referrers.getDocument(STRONG);
if (strong != null) {
for (String keyString : strong.keySet()) {
result.add(new NodeKey(keyString));
}
}
}
if (type != ReferenceType.STRONG) {
Document weak = referrers.getDocument(WEAK);
if (weak != null) {
for (String keyString : weak.keySet()) {
result.add(new NodeKey(keyString));
}
}
}
return result;
}
public Map getReferrerCounts( Document document,
ReferenceType type ) {
// Get the properties container ...
Document referrers = document.getDocument(REFERRERS);
if (referrers == null) {
return Collections.emptyMap();
}
// Get the NodeKeys in the respective arrays ...
Map result = new HashMap<>();
if (type == ReferenceType.STRONG || type == ReferenceType.BOTH) {
Document strong = referrers.getDocument(STRONG);
if (strong != null) {
for (String keyString : strong.keySet()) {
result.put(new NodeKey(keyString), strong.getInteger(keyString));
}
}
}
if (type == ReferenceType.WEAK || type == ReferenceType.BOTH) {
Document weak = referrers.getDocument(WEAK);
if (weak != null) {
for (String keyString : weak.keySet()) {
result.put(new NodeKey(keyString), weak.getInteger(keyString));
}
}
}
return result;
}
public void changeReferrers( EditableDocument document,
ReferrerChanges changes ) {
if (changes.isEmpty()) {
// There are no changes requested ...
return;
}
// Get the properties container ...
EditableDocument referrers = document.getDocument(REFERRERS);
List strongAdded = changes.getAddedReferrers(ReferenceType.STRONG);
List weakAdded = changes.getAddedReferrers(ReferenceType.WEAK);
if (referrers == null) {
// There are no references in the document, so create it from the added references ...
referrers = document.setDocument(REFERRERS);
if (!strongAdded.isEmpty()) {
Set strongAddedSet = new HashSet(strongAdded);
EditableDocument strong = referrers.setDocument(STRONG);
for (NodeKey key : strongAddedSet) {
strong.set(key.toString(), Collections.frequency(strongAdded, key));
}
}
if (!weakAdded.isEmpty()) {
Set weakAddedSet = new HashSet(weakAdded);
EditableDocument weak = referrers.setDocument(WEAK);
for (NodeKey key : weakAddedSet) {
weak.set(key.toString(), Collections.frequency(weakAdded, key));
}
}
return;
}
// There are already some references, so update them
List strongRemoved = changes.getRemovedReferrers(ReferenceType.STRONG);
Map strongCount = computeReferrersCountDelta(strongAdded, strongRemoved);
if (!strongCount.isEmpty()) {
EditableDocument strong = referrers.getOrCreateDocument(STRONG);
updateReferrers(strong, strongCount);
}
List weakRemoved = changes.getRemovedReferrers(ReferenceType.WEAK);
Map weakCount = computeReferrersCountDelta(weakAdded, weakRemoved);
if (!weakCount.isEmpty()) {
EditableDocument weak = referrers.getOrCreateDocument(WEAK);
updateReferrers(weak, weakCount);
}
}
private void updateReferrers( EditableDocument owningDocument,
Map referrersCountDelta ) {
for (NodeKey strongKey : referrersCountDelta.keySet()) {
int newCount = referrersCountDelta.get(strongKey);
String keyString = strongKey.toString();
Integer existingCount = (Integer)owningDocument.get(keyString);
if (existingCount != null) {
int actualCount = existingCount + newCount;
if (actualCount <= 0) {
owningDocument.remove(keyString);
} else {
owningDocument.set(keyString, actualCount);
}
} else if (newCount > 0) {
owningDocument.set(keyString, newCount);
}
}
}
/**
* Given the lists of added & removed referrers (which may contain duplicates), compute the delta with which the count has to
* be updated in the document
*
* @param addedReferrers the list of referrers that was added
* @param removedReferrers the list of referrers that was removed
* @return a map(nodekey, delta) pairs
*/
private Map computeReferrersCountDelta( List addedReferrers,
List removedReferrers ) {
Map referrersCountDelta = new HashMap(0);
Set addedReferrersUnique = new HashSet(addedReferrers);
for (NodeKey addedReferrer : addedReferrersUnique) {
int referrersCount = Collections.frequency(addedReferrers, addedReferrer)
- Collections.frequency(removedReferrers, addedReferrer);
referrersCountDelta.put(addedReferrer, referrersCount);
}
Set removedReferrersUnique = new HashSet(removedReferrers);
for (NodeKey removedReferrer : removedReferrersUnique) {
// process what's left in the removed list, only if not found in the added
if (!referrersCountDelta.containsKey(removedReferrer)) {
referrersCountDelta.put(removedReferrer, -1 * Collections.frequency(removedReferrers, removedReferrer));
}
}
return referrersCountDelta;
}
protected Object valueToDocument( Object value,
Set unusedBinaryKeys,
Set usedBinaryKeys) {
if (value == null) {
return null;
}
if (value instanceof String) {
String valueStr = (String)value;
if (valueStr.length() < this.largeStringSize.get()) {
// It's just a small string ...
return value;
}
// Otherwise, this string is larger than our threshold, and we should treat it as a binary value ...
value = binaries.create(valueStr);
// and just continue, where the value will be processed below ...
}
if (value instanceof NodeKey) {
return ((NodeKey)value).toString();
}
if (value instanceof UUID) {
return Schematic.newDocument("$uuid", this.strings.create((UUID)value));
}
if (value instanceof Boolean) {
return value;
}
if (value instanceof Long) {
return value;
}
if (value instanceof Integer) {
return new Long(((Integer)value).intValue());
}
if (value instanceof Double) {
return value;
}
if (value instanceof Name) {
Name name = (Name)value;
return Schematic.newDocument("$name", name.getString(encoder));
}
if (value instanceof Path) {
Path path = (Path)value;
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy