Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.orientechnologies.orient.core.index.OClassIndexManager Maven / Gradle / Ivy
/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.core.index;
import static com.orientechnologies.orient.core.hook.ORecordHook.TYPE.BEFORE_CREATE;
import static com.orientechnologies.orient.core.hook.ORecordHook.TYPE.BEFORE_UPDATE;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.OHookReplacedRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeTimeLine;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.db.record.ORecordOperation;
import com.orientechnologies.orient.core.db.record.OTrackedMultiValue;
import com.orientechnologies.orient.core.exception.OConcurrentModificationException;
import com.orientechnologies.orient.core.exception.OFastConcurrentModificationException;
import com.orientechnologies.orient.core.exception.ORecordNotFoundException;
import com.orientechnologies.orient.core.hook.ODocumentHookAbstract;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
import com.orientechnologies.orient.core.storage.ORecordDuplicatedException;
import com.orientechnologies.orient.core.version.ORecordVersion;
/**
* Handles indexing when records change.
*
* @author Andrey Lomakin, Artem Orobets
*/
public class OClassIndexManager extends ODocumentHookAbstract {
private final ODatabaseDocument database;
private final ThreadLocal, List>>> lockedKeys = new ThreadLocal, List>>>();
public OClassIndexManager(ODatabaseDocument database) {
super();
this.database = database;
}
private static void processCompositeIndexUpdate(final OIndex index, final Set dirtyFields, final ODocument iRecord) {
final OCompositeIndexDefinition indexDefinition = (OCompositeIndexDefinition) index.getDefinition();
final List indexFields = indexDefinition.getFields();
final String multiValueField = indexDefinition.getMultiValueField();
for (final String indexField : indexFields) {
if (dirtyFields.contains(indexField)) {
final List origValues = new ArrayList(indexFields.size());
for (final String field : indexFields) {
if (!field.equals(multiValueField))
if (dirtyFields.contains(field)) {
origValues.add(iRecord.getOriginalValue(field));
} else {
origValues.add(iRecord. field(field));
}
}
if (multiValueField == null) {
final Object origValue = indexDefinition.createValue(origValues);
final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord);
if (!indexDefinition.isNullValuesIgnored() || origValue != null)
index.remove(origValue, iRecord);
if (!indexDefinition.isNullValuesIgnored() || newValue != null)
index.put(newValue, iRecord.getIdentity());
} else {
final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(multiValueField);
if (multiValueChangeTimeLine == null) {
if (dirtyFields.contains(multiValueField))
origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.getOriginalValue(multiValueField));
else
origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.field(multiValueField));
final Object origValue = indexDefinition.createValue(origValues);
final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord);
processIndexUpdateFieldAssignment(index, iRecord, origValue, newValue);
} else {
if (dirtyFields.size() == 1) {
final Map keysToAdd = new HashMap();
final Map keysToRemove = new HashMap();
for (OMultiValueChangeEvent changeEvent : multiValueChangeTimeLine.getMultiValueChangeEvents()) {
indexDefinition.processChangeEvent(changeEvent, keysToAdd, keysToRemove, origValues.toArray());
}
for (final Object keyToRemove : keysToRemove.keySet())
index.remove(keyToRemove, iRecord);
for (final Object keyToAdd : keysToAdd.keySet())
index.put(keyToAdd, iRecord.getIdentity());
} else {
final OTrackedMultiValue fieldValue = iRecord.field(multiValueField);
final Object restoredMultiValue = fieldValue
.returnOriginalState(multiValueChangeTimeLine.getMultiValueChangeEvents());
origValues.add(indexDefinition.getMultiValueDefinitionIndex(), restoredMultiValue);
final Object origValue = indexDefinition.createValue(origValues);
final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord);
processIndexUpdateFieldAssignment(index, iRecord, origValue, newValue);
}
}
}
return;
}
}
}
private static void processSingleIndexUpdate(final OIndex index, final Set dirtyFields, final ODocument iRecord) {
final OIndexDefinition indexDefinition = index.getDefinition();
final List indexFields = indexDefinition.getFields();
if (indexFields.isEmpty())
return;
final String indexField = indexFields.get(0);
if (!dirtyFields.contains(indexField))
return;
final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(indexField);
if (multiValueChangeTimeLine != null) {
final OIndexDefinitionMultiValue indexDefinitionMultiValue = (OIndexDefinitionMultiValue) indexDefinition;
final Map keysToAdd = new HashMap();
final Map keysToRemove = new HashMap();
for (OMultiValueChangeEvent changeEvent : multiValueChangeTimeLine.getMultiValueChangeEvents()) {
indexDefinitionMultiValue.processChangeEvent(changeEvent, keysToAdd, keysToRemove);
}
for (final Object keyToRemove : keysToRemove.keySet())
index.remove(keyToRemove, iRecord);
for (final Object keyToAdd : keysToAdd.keySet())
index.put(keyToAdd, iRecord.getIdentity());
} else {
final Object origValue = indexDefinition.createValue(iRecord.getOriginalValue(indexField));
final Object newValue = indexDefinition.getDocumentValueToIndex(iRecord);
processIndexUpdateFieldAssignment(index, iRecord, origValue, newValue);
}
}
private static void processIndexUpdateFieldAssignment(OIndex index, ODocument iRecord, final Object origValue,
final Object newValue) {
final OIndexDefinition indexDefinition = index.getDefinition();
if ((origValue instanceof Collection) && (newValue instanceof Collection)) {
final Set valuesToRemove = new HashSet((Collection) origValue);
final Set valuesToAdd = new HashSet((Collection) newValue);
valuesToRemove.removeAll((Collection) newValue);
valuesToAdd.removeAll((Collection) origValue);
for (final Object valueToRemove : valuesToRemove) {
if (!indexDefinition.isNullValuesIgnored() || valueToRemove != null) {
index.remove(valueToRemove, iRecord);
}
}
for (final Object valueToAdd : valuesToAdd) {
if (!indexDefinition.isNullValuesIgnored() || valueToAdd != null) {
index.put(valueToAdd, iRecord);
}
}
} else {
deleteIndexKey(index, iRecord, origValue);
if (newValue instanceof Collection) {
for (final Object newValueItem : (Collection) newValue) {
index.put(newValueItem, iRecord.getIdentity());
}
} else if (!indexDefinition.isNullValuesIgnored() || newValue != null) {
index.put(newValue, iRecord.getIdentity());
}
}
}
private static boolean processCompositeIndexDelete(final OIndex index, final Set dirtyFields, final ODocument iRecord) {
final OCompositeIndexDefinition indexDefinition = (OCompositeIndexDefinition) index.getDefinition();
final String multiValueField = indexDefinition.getMultiValueField();
final List indexFields = indexDefinition.getFields();
for (final String indexField : indexFields) {
// REMOVE IT
if (dirtyFields.contains(indexField)) {
final List origValues = new ArrayList(indexFields.size());
for (final String field : indexFields) {
if (!field.equals(multiValueField))
if (dirtyFields.contains(field))
origValues.add(iRecord.getOriginalValue(field));
else
origValues.add(iRecord. field(field));
}
if (multiValueField != null) {
final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(multiValueField);
if (multiValueChangeTimeLine != null) {
final OTrackedMultiValue fieldValue = iRecord.field(multiValueField);
final Object restoredMultiValue = fieldValue.returnOriginalState(multiValueChangeTimeLine.getMultiValueChangeEvents());
origValues.add(indexDefinition.getMultiValueDefinitionIndex(), restoredMultiValue);
} else if (dirtyFields.contains(multiValueField))
origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.getOriginalValue(multiValueField));
else
origValues.add(indexDefinition.getMultiValueDefinitionIndex(), iRecord.field(multiValueField));
}
final Object origValue = indexDefinition.createValue(origValues);
deleteIndexKey(index, iRecord, origValue);
return true;
}
}
return false;
}
private static void deleteIndexKey(final OIndex index, final ODocument iRecord, final Object origValue) {
final OIndexDefinition indexDefinition = index.getDefinition();
if (origValue instanceof Collection) {
for (final Object valueItem : (Collection) origValue) {
if (!indexDefinition.isNullValuesIgnored() || valueItem != null)
index.remove(valueItem, iRecord);
}
} else if (!indexDefinition.isNullValuesIgnored() || origValue != null) {
index.remove(origValue, iRecord);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static boolean processSingleIndexDelete(final OIndex index, final Set dirtyFields, final ODocument iRecord) {
final OIndexDefinition indexDefinition = index.getDefinition();
final List indexFields = indexDefinition.getFields();
if (indexFields.isEmpty()) {
return false;
}
final String indexField = indexFields.iterator().next();
if (dirtyFields.contains(indexField)) {
final OMultiValueChangeTimeLine multiValueChangeTimeLine = iRecord.getCollectionTimeLine(indexField);
final Object origValue;
if (multiValueChangeTimeLine != null) {
final OTrackedMultiValue fieldValue = iRecord.field(indexField);
final Object restoredMultiValue = fieldValue.returnOriginalState(multiValueChangeTimeLine.getMultiValueChangeEvents());
origValue = indexDefinition.createValue(restoredMultiValue);
} else
origValue = indexDefinition.createValue(iRecord.getOriginalValue(indexField));
deleteIndexKey(index, iRecord, origValue);
return true;
}
return false;
}
private ODocument checkIndexedPropertiesOnCreation(final ODocument record, final Collection> indexes) {
ODocument replaced = null;
Deque, List>> indexKeysMapQueue = lockedKeys.get();
if (indexKeysMapQueue == null) {
indexKeysMapQueue = new ArrayDeque, List>>();
lockedKeys.set(indexKeysMapQueue);
}
final TreeMap, List> indexKeysMap = new TreeMap, List>();
for (final OIndex index : indexes) {
if (index.getInternal() instanceof OIndexUnique) {
OIndexRecorder indexRecorder = new OIndexRecorder((OIndexUnique) index.getInternal());
addIndexEntry(record, record.getIdentity(), indexRecorder);
indexKeysMap.put(index, indexRecorder.getAffectedKeys());
}
}
for (Map.Entry, List> entry : indexKeysMap.entrySet()) {
final OIndexInternal index = entry.getKey().getInternal();
index.lockKeysForUpdateNoTx(entry.getValue());
}
indexKeysMapQueue.push(indexKeysMap);
for (Map.Entry, List> entry : indexKeysMap.entrySet()) {
final OIndex index = entry.getKey();
for (Object keyItem : entry.getValue()) {
final ODocument r = index.checkEntry(record, keyItem);
if (r != null)
if (replaced == null)
replaced = r;
else {
throw new OIndexException("Cannot merge record from multiple indexes. Use this strategy when you have only one index");
}
}
}
return replaced;
}
private void checkIndexedPropertiesOnUpdate(final ODocument record, final Collection> indexes) {
Deque, List>> indexKeysMapQueue = lockedKeys.get();
if (indexKeysMapQueue == null) {
indexKeysMapQueue = new ArrayDeque, List>>();
lockedKeys.set(indexKeysMapQueue);
}
final TreeMap, List> indexKeysMap = new TreeMap, List>();
final Set dirtyFields = new HashSet(Arrays.asList(record.getDirtyFields()));
if (dirtyFields.isEmpty())
return;
for (final OIndex index : indexes) {
if (index instanceof OIndexUnique) {
final OIndexRecorder indexRecorder = new OIndexRecorder((OIndexUnique) index);
processIndexUpdate(record, dirtyFields, indexRecorder);
indexKeysMap.put(index, indexRecorder.getAffectedKeys());
}
}
for (Map.Entry, List> entry : indexKeysMap.entrySet()) {
final OIndexInternal index = entry.getKey().getInternal();
index.lockKeysForUpdateNoTx(entry.getValue());
}
indexKeysMapQueue.push(indexKeysMap);
for (Map.Entry, List> entry : indexKeysMap.entrySet()) {
final OIndex index = entry.getKey();
for (Object keyItem : entry.getValue()) {
index.checkEntry(record, keyItem);
}
}
}
private static ODocument checkForLoading(final ODocument iRecord) {
if (iRecord.getInternalStatus() == ORecordElement.STATUS.NOT_LOADED) {
try {
return (ODocument) iRecord.load();
} catch (final ORecordNotFoundException e) {
throw new OIndexException("Error during loading of record with id : " + iRecord.getIdentity());
}
}
return iRecord;
}
public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
return DISTRIBUTED_EXECUTION_MODE.TARGET_NODE;
}
@Override
public RESULT onRecordBeforeCreate(final ODocument document) {
final ODocument replaced = checkIndexes(document, BEFORE_CREATE);
if (replaced != null) {
OHookReplacedRecordThreadLocal.INSTANCE.set(replaced);
return RESULT.RECORD_REPLACED;
}
return RESULT.RECORD_NOT_CHANGED;
}
@Override
public void onRecordAfterCreate(ODocument document) {
document = checkForLoading(document);
// STORE THE RECORD IF NEW, OTHERWISE ITS RID
final OIdentifiable rid = document.getIdentity();
final OClass cls = ODocumentInternal.getImmutableSchemaClass(document);
if (cls != null) {
final Collection> indexes = cls.getIndexes();
addIndexesEntries(document, indexes);
}
}
@Override
public void onRecordCreateFailed(final ODocument iDocument) {
}
@Override
public void onRecordCreateReplicated(final ODocument iDocument) {
}
@Override
public RESULT onRecordBeforeUpdate(final ODocument iDocument) {
checkIndexes(iDocument, BEFORE_UPDATE);
return RESULT.RECORD_NOT_CHANGED;
}
@Override
public void onRecordAfterUpdate(ODocument iDocument) {
iDocument = checkForLoading(iDocument);
final OClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument);
if (cls == null)
return;
final Collection> indexes = cls.getIndexes();
if (!indexes.isEmpty()) {
final Set dirtyFields = new HashSet(Arrays.asList(iDocument.getDirtyFields()));
if (!dirtyFields.isEmpty()) {
for (final OIndex index : indexes) {
processIndexUpdate(iDocument, dirtyFields, index);
}
}
}
}
private void processIndexUpdate(ODocument iDocument, Set dirtyFields, OIndex index) {
if (index.getDefinition() instanceof OCompositeIndexDefinition)
processCompositeIndexUpdate(index, dirtyFields, iDocument);
else
processSingleIndexUpdate(index, dirtyFields, iDocument);
}
@Override
public void onRecordUpdateFailed(final ODocument iDocument) {
}
@Override
public void onRecordUpdateReplicated(final ODocument iDocument) {
}
@Override
public RESULT onRecordBeforeDelete(final ODocument iDocument) {
final ORecordVersion version = iDocument.getRecordVersion(); // Cache the transaction-provided value
if (iDocument.fields() == 0 && iDocument.getIdentity().isPersistent()) {
// FORCE LOADING OF CLASS+FIELDS TO USE IT AFTER ON onRecordAfterDelete METHOD
iDocument.reload();
if (version.getCounter() > -1 && iDocument.getRecordVersion().compareTo(version) != 0) // check for record version errors
if (OFastConcurrentModificationException.enabled())
throw OFastConcurrentModificationException.instance();
else
throw new OConcurrentModificationException(iDocument.getIdentity(), iDocument.getRecordVersion(), version,
ORecordOperation.DELETED);
}
return RESULT.RECORD_NOT_CHANGED;
}
@Override
public void onRecordAfterDelete(final ODocument iDocument) {
deleteIndexEntries(iDocument);
}
@Override
public void onRecordDeleteFailed(final ODocument iDocument) {
}
@Override
public void onRecordDeleteReplicated(final ODocument iDocument) {
}
private void addIndexesEntries(ODocument document, final Collection> indexes) {
// STORE THE RECORD IF NEW, OTHERWISE ITS RID
final OIdentifiable rid = document.getIdentity();
for (final OIndex index : indexes) {
addIndexEntry(document, rid, index);
}
}
private void addIndexEntry(ODocument document, OIdentifiable rid, OIndex index) {
final OIndexDefinition indexDefinition = index.getDefinition();
final Object key = indexDefinition.getDocumentValueToIndex(document);
if (key instanceof Collection) {
for (final Object keyItem : (Collection) key)
if (!indexDefinition.isNullValuesIgnored() || keyItem != null)
index.put(keyItem, rid);
} else if (!indexDefinition.isNullValuesIgnored() || key != null)
try {
index.put(key, rid);
} catch (ORecordDuplicatedException e) {
if (!database.getTransaction().isActive()) {
database.delete(document);
}
throw e;
}
}
private void deleteIndexEntries(ODocument iDocument) {
final OClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument);
if (cls == null)
return;
final Collection> indexes = new ArrayList>(cls.getIndexes());
if (!indexes.isEmpty()) {
final Set dirtyFields = new HashSet(Arrays.asList(iDocument.getDirtyFields()));
if (!dirtyFields.isEmpty()) {
// REMOVE INDEX OF ENTRIES FOR THE OLD VALUES
final Iterator> indexIterator = indexes.iterator();
while (indexIterator.hasNext()) {
final OIndex index = indexIterator.next();
final boolean result;
if (index.getDefinition() instanceof OCompositeIndexDefinition)
result = processCompositeIndexDelete(index, dirtyFields, iDocument);
else
result = processSingleIndexDelete(index, dirtyFields, iDocument);
if (result)
indexIterator.remove();
}
}
// REMOVE INDEX OF ENTRIES FOR THE NON CHANGED ONLY VALUES
for (final OIndex index : indexes) {
final Object key = index.getDefinition().getDocumentValueToIndex(iDocument);
deleteIndexKey(index, iDocument, key);
}
}
}
private ODocument checkIndexes(ODocument document, TYPE hookType) {
document = checkForLoading(document);
ODocument replaced = null;
final OClass cls = ODocumentInternal.getImmutableSchemaClass(document);
if (cls != null) {
final Collection> indexes = cls.getIndexes();
switch (hookType) {
case BEFORE_CREATE:
replaced = checkIndexedPropertiesOnCreation(document, indexes);
break;
case BEFORE_UPDATE:
checkIndexedPropertiesOnUpdate(document, indexes);
break;
default:
throw new IllegalArgumentException("Invalid hook type: " + hookType);
}
}
return replaced;
}
@Override
public void onRecordFinalizeUpdate(ODocument document) {
unlockKeys();
}
@Override
public void onRecordFinalizeCreation(ODocument document) {
unlockKeys();
}
private void unlockKeys() {
Deque, List>> indexKeysMapQueue = lockedKeys.get();
if (indexKeysMapQueue == null)
return;
final TreeMap, List> indexKeyMap = indexKeysMapQueue.poll();
if (indexKeyMap == null)
return;
for (Map.Entry, List> entry : indexKeyMap.entrySet()) {
final OIndexInternal index = entry.getKey().getInternal();
try {
index.releaseKeysForUpdateNoTx(entry.getValue());
} catch (RuntimeException e) {
OLogManager.instance().error(this, "Error during unlock of keys for index %s", e, index.getName());
}
}
}
}