/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Thierry Delprat
* Benoit Delbosc
*/
package org.nuxeo.elasticsearch.listener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.naming.NamingException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.core.event.EventListener;
import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
import org.nuxeo.elasticsearch.api.ElasticSearchIndexing;
import org.nuxeo.elasticsearch.commands.IndexingCommand;
import org.nuxeo.elasticsearch.commands.IndexingCommands;
import org.nuxeo.elasticsearch.commands.IndexingCommandsStacker;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.transaction.TransactionHelper;
/**
* Synchronous Event listener used to record indexing command, submitted after commit completion.
*/
public class ElasticSearchInlineListener extends IndexingCommandsStacker implements EventListener, Synchronization {
private static final Log log = LogFactory.getLog(ElasticSearchInlineListener.class);
protected static ThreadLocal<Map<String, IndexingCommands>> transactionCommands = new ThreadLocal<Map<String, IndexingCommands>>() {
@Override
protected HashMap<String, IndexingCommands> initialValue() {
return new HashMap<>();
}
};
protected static ThreadLocal<Boolean> isEnlisted = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
@Override
protected Map<String, IndexingCommands> getAllCommands() {
return transactionCommands.get();
}
@Override
protected boolean isSyncIndexingByDefault() {
Boolean ret = useSyncIndexing.get();
if (ret == null) {
ret = false;
}
return ret;
}
@Override
public void handleEvent(Event event) {
String eventId = event.getName();
if (!isEnlisted.get()) {
if (event.isCommitEvent()) {
// manual flush on save if TxManager is not hooked
afterCompletion(Status.STATUS_COMMITTED);
return;
}
// try to enlist our listener
isEnlisted.set(registerSynchronization(this));
}
if (!(event.getContext() instanceof DocumentEventContext)) {
// don't process Events that are not tied to Documents
return;
}
DocumentEventContext docCtx = (DocumentEventContext) event.getContext();
stackCommand(docCtx, eventId);
}
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
try {
if (getAllCommands().isEmpty()) {
// return and un hook the current listener even if there's no commands to index
// unless, during next transaction this listener won't be hooked to it
return;
}
if (Status.STATUS_MARKED_ROLLBACK == status || Status.STATUS_ROLLEDBACK == status) {
return;
}
List<IndexingCommand> commandList = new ArrayList<>();
for (IndexingCommands cmds : getAllCommands().values()) {
for (IndexingCommand cmd : cmds.getCommands()) {
commandList.add(cmd);
}
}
ElasticSearchIndexing esi = Framework.getLocalService(ElasticSearchIndexing.class);
esi.runIndexingWorker(commandList);
} finally {
isEnlisted.set(false);
getAllCommands().clear();
useSyncIndexing.set(null);
}
}
public static ThreadLocal<Boolean> useSyncIndexing = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
@Override
public void set(Boolean value) {
super.set(value);
if (Boolean.TRUE.equals(value)) {
// switch existing stack to sync
for (IndexingCommands cmds : transactionCommands.get().values()) {
for (IndexingCommand cmd : cmds.getCommands()) {
cmd.makeSync();
}
}
}
}
};
protected boolean registerSynchronization(Synchronization sync) {
try {
TransactionManager tm = TransactionHelper.lookupTransactionManager();
if (tm != null) {
if (tm.getTransaction() != null) {
tm.getTransaction().registerSynchronization(sync);
return true;
}
if (!Framework.isTestModeSet()) {
log.error("Unable to register synchronization : no active transaction");
}
return false;
} else {
log.error("Unable to register synchronization : no TransactionManager");
return false;
}
} catch (NamingException | IllegalStateException | SystemException | RollbackException e) {
log.error("Unable to register synchronization", e);
return false;
}
}
}