package com.constellio.data.dao.services.bigVault.solr;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import com.constellio.data.dao.dto.records.RecordsFlushing;
public class BigVaultServerTransactionCombinator {
public static final int DEFAULT_MAX_TRANSACTION_SIZE = 10000;
List<SolrInputDocument> mergeNewDocuments = new LinkedList<>();
List<SolrInputDocument> mergeUpdatedDocuments = new LinkedList<>();
Map<String, SolrInputDocument> newDocumentsById = new HashMap<>();
Map<String, SolrInputDocument> updatedDocumentsById = new HashMap<>();
Set<String> deletedRecords = new HashSet<>();
List<String> deletedQueries = new ArrayList<>();
int maximumTransactionSize;
public BigVaultServerTransactionCombinator(int maximumTransactionSize) {
this.maximumTransactionSize = maximumTransactionSize;
}
public BigVaultServerTransactionCombinator combineWith(BigVaultServerTransaction newTransaction) {
// for (SolrInputDocument newDocInFirstTransaction : firstTransaction.getNewDocuments()) {
// String id = (String) newDocInFirstTransaction.getFieldValue("id");
// addMissingFields(newDocInFirstTransaction);
// mergeNewDocuments.add(newDocInFirstTransaction);
// newDocumentsById.put(id, newDocInFirstTransaction);
// }
//
for (SolrInputDocument newDocInSecondTransaction : newTransaction.getNewDocuments()) {
filterMetadatas(newDocInSecondTransaction);
if (isAtomicUpdate(newDocInSecondTransaction)) {
handleUpdatedDocument(newDocInSecondTransaction);
} else {
handleNewDocument(newDocInSecondTransaction);
}
}
// for (SolrInputDocument updatedDocInFirstTransaction : firstTransaction.getUpdatedDocuments()) {
// String id = (String) updatedDocInFirstTransaction.getFieldValue("id");
// addMissingFields(updatedDocInFirstTransaction);
// mergeUpdatedDocuments.add(updatedDocInFirstTransaction);
// updatedDocumentsById.put(id, updatedDocInFirstTransaction);
// }
for (SolrInputDocument updatedDocInSecondTransaction : newTransaction.getUpdatedDocuments()) {
filterMetadatas(updatedDocInSecondTransaction);
handleUpdatedDocument(updatedDocInSecondTransaction);
}
for (String secondTransactionDeletedRecord : newTransaction.getDeletedRecords()) {
handleDeletedDocument(secondTransactionDeletedRecord);
}
this.deletedQueries.addAll(newTransaction.getDeletedQueries());
return this;
}
private void filterMetadatas(SolrInputDocument doc) {
doc.remove("content_txt_fr");
}
public boolean canCombineWith(BigVaultServerTransaction otherTransaction) {
int totalAddUpdate = mergeNewDocuments.size() + mergeUpdatedDocuments.size() + otherTransaction.getNewDocuments().size()
+ otherTransaction.getUpdatedDocuments().size();
return deletedQueries.isEmpty() && otherTransaction.getDeletedQueries().isEmpty()
&& totalAddUpdate < maximumTransactionSize;
//
// if (!otherTransaction.deletedQueries.isEmpty()) {
// return false;
// }
// List<String> ids = getAddUpdateDeleteRecordIds();
// List<String> otherTransactionIds = otherTransaction.getAddUpdateDeleteRecordIds();
// for (String otherTransactionId : otherTransactionIds) {
// if (ids.contains(otherTransactionId)) {
// //if (!otherTransactionId.startsWith("idx_") && ids.contains(otherTransactionId)) {
// return false;
// }
// }
//
// return true;
}
public BigVaultServerTransaction combineAndClean() {
String transactionId = UUID.randomUUID().toString();
BigVaultServerTransaction transaction = new BigVaultServerTransaction(transactionId,
RecordsFlushing.NOW(),
new ArrayList<>(mergeNewDocuments), new ArrayList<>(mergeUpdatedDocuments),
new ArrayList<String>(deletedRecords), new ArrayList<>(deletedQueries));
this.mergeNewDocuments.clear();
this.mergeUpdatedDocuments.clear();
this.newDocumentsById.clear();
this.updatedDocumentsById.clear();
this.deletedRecords.clear();
this.deletedQueries.clear();
return transaction;
}
private void handleDeletedDocument(String secondTransactionDeletedRecord) {
deletedRecords.add(secondTransactionDeletedRecord);
if (newDocumentsById.containsKey(secondTransactionDeletedRecord)) {
removeNewDocument(secondTransactionDeletedRecord);
}
if (updatedDocumentsById.containsKey(secondTransactionDeletedRecord)) {
removeUpdatedDocument(secondTransactionDeletedRecord);
}
}
private void handleUpdatedDocument(SolrInputDocument updatedDocInSecondTransaction) {
String id = (String) updatedDocInSecondTransaction.getFieldValue("id");
addMissingFields(updatedDocInSecondTransaction);
if (newDocumentsById.containsKey(id)) {
SolrInputDocument newDocument = newDocumentsById.get(id);
if (isAtomicUpdate(updatedDocInSecondTransaction)) {
for (String fieldName : updatedDocInSecondTransaction.getFieldNames()) {
if (!fieldName.equals("id") && !fieldName.equals("_version_")) {
SolrInputField field = updatedDocInSecondTransaction.get(fieldName);
if (field.getValue() instanceof Map) {
Map<String, Object> atomicSetValue = (Map<String, Object>) field.getValue();
if (atomicSetValue.containsKey("set")) {
Object value = atomicSetValue.get("set");
newDocument.setField(fieldName, value);
} else if (atomicSetValue.containsKey("inc") && fieldName.endsWith("_d")) {
double value = toDouble(atomicSetValue.get("inc"));
SolrInputField firstTransactionFieldValue = newDocument.get(fieldName);
if (firstTransactionFieldValue == null) {
newDocument.setField(fieldName, value);
} else {
double valueInFirstTransaction = toDouble(firstTransactionFieldValue.getValue());
newDocument.setField(fieldName, valueInFirstTransaction + value);
}
} else {
throw new UnsupportedOperationException("TODO");
}
} else {
newDocument.setField(fieldName, field.getValue());
}
}
}
} else {
removeNewDocument(id);
mergeUpdatedDocuments.add(updatedDocInSecondTransaction);
updatedDocumentsById.put(id, updatedDocInSecondTransaction);
}
} else if (updatedDocumentsById.containsKey(id)) {
SolrInputDocument updatedDocument = updatedDocumentsById.get(id);
if (isAtomicUpdate(updatedDocInSecondTransaction)) {
for (String fieldName : updatedDocInSecondTransaction.getFieldNames()) {
if (!fieldName.equals("id") && !fieldName.equals("_version_")) {
SolrInputField field = updatedDocInSecondTransaction.get(fieldName);
if (field.getValue() instanceof Map) {
Map<String, Object> atomicSetValue = (Map<String, Object>) field.getValue();
if (atomicSetValue.containsKey("set")) {
updatedDocument.setField(fieldName, atomicSetValue);
} else if (atomicSetValue.containsKey("inc") && fieldName.endsWith("_d")) {
double value = toDouble(atomicSetValue.get("inc"));
SolrInputField firstTransactionFieldValue = updatedDocument.get(fieldName);
if (firstTransactionFieldValue == null) {
updatedDocument.setField(fieldName, atomicSetValue);
} else {
Map<String, Object> firstTransactionUpdatedMap = (Map<String, Object>) firstTransactionFieldValue
.getValue();
if (firstTransactionUpdatedMap.containsKey("set")) {
double firstTransactionUpdatedMapValue = toDouble(firstTransactionUpdatedMap.get("set"));
updatedDocument
.setField(fieldName,
newAtomicSetMap(firstTransactionUpdatedMapValue + value));
} else if (firstTransactionUpdatedMap.containsKey("inc")) {
double firstTransactionUpdatedMapValue = toDouble(firstTransactionUpdatedMap.get("inc"));
updatedDocument.setField(fieldName, newAtomicIncrementMap(
firstTransactionUpdatedMapValue + value));
}
}
} else {
throw new UnsupportedOperationException("TODO");
}
} else {
updatedDocument.setField(fieldName, field.getValue());
}
}
}
} else {
removeUpdatedDocument(id);
mergeUpdatedDocuments.add(updatedDocInSecondTransaction);
updatedDocumentsById.put(id, updatedDocInSecondTransaction);
}
} else {
mergeUpdatedDocuments.add(updatedDocInSecondTransaction);
updatedDocumentsById.put(id, updatedDocInSecondTransaction);
}
}
private double toDouble(Object set) {
return Double.valueOf(set.toString());
}
private void handleNewDocument(SolrInputDocument newDocInSecondTransaction) {
String id = (String) newDocInSecondTransaction.getFieldValue("id");
deletedRecords.remove(id);
if (newDocumentsById.containsKey(id)) {
Iterator<SolrInputDocument> docsIterator = mergeNewDocuments.iterator();
while (docsIterator.hasNext()) {
if (id.equals(docsIterator.next().getFieldValue("id"))) {
docsIterator.remove();
}
}
}
addMissingFields(newDocInSecondTransaction);
mergeNewDocuments.add(newDocInSecondTransaction);
newDocumentsById.put(id, newDocInSecondTransaction);
}
private void removeUpdatedDocument(String secondTransactionDeletedRecord) {
updatedDocumentsById.remove(secondTransactionDeletedRecord);
for (Iterator<SolrInputDocument> iterator = mergeUpdatedDocuments.iterator(); iterator.hasNext(); ) {
SolrInputDocument doc = iterator.next();
if (secondTransactionDeletedRecord.equals(doc.getFieldValue("id"))) {
iterator.remove();
break;
}
}
}
private void removeNewDocument(String secondTransactionDeletedRecord) {
newDocumentsById.remove(secondTransactionDeletedRecord);
for (Iterator<SolrInputDocument> iterator = mergeNewDocuments.iterator(); iterator.hasNext(); ) {
SolrInputDocument doc = iterator.next();
if (secondTransactionDeletedRecord.equals(doc.getFieldValue("id"))) {
iterator.remove();
break;
}
}
}
private boolean isAtomicUpdate(SolrInputDocument updatedDocInSecondTransaction) {
for (String fieldName : updatedDocInSecondTransaction.getFieldNames()) {
if (updatedDocInSecondTransaction.getFieldValue(fieldName) instanceof Map) {
return true;
}
}
return false;
}
private void addMissingFields(SolrInputDocument updatedDocInransaction) {
// String id = (String) updatedDocInransaction.getFieldValue("id");
// if (!updatedDocInransaction.getFieldNames().contains("type_s") && id.startsWith("idx_")) {
// updatedDocInransaction.setField("type_s", "index");
// }
}
private Map<String, Object> newAtomicIncrementMap(Object value) {
Map<String, Object> map = new HashMap<>();
map.put("inc", value);
return map;
}
private Map<String, Object> newAtomicSetMap(Object value) {
Map<String, Object> map = new HashMap<>();
map.put("set", value);
return map;
}
public boolean hasData() {
return !newDocumentsById.isEmpty() || !updatedDocumentsById.isEmpty() || !deletedRecords.isEmpty() || !deletedQueries
.isEmpty();
}
public static BigVaultServerTransaction combineAll(BigVaultServerTransaction... transactions) {
BigVaultServerTransactionCombinator combinator = new BigVaultServerTransactionCombinator(DEFAULT_MAX_TRANSACTION_SIZE);
for (BigVaultServerTransaction transaction : transactions) {
combinator.combineWith(transaction);
}
return combinator.combineAndClean();
}
}