/* * Copyright 2008 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.services.fulltext; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.hibernate.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import ome.services.eventlogs.EventLogLoader; import ome.services.sessions.SessionManager; import ome.services.util.ExecutionThread; import ome.services.util.Executor; import ome.system.Principal; import ome.system.ServiceFactory; import ome.util.DetailsFieldBridge; import ome.util.SqlAction; /** * Library entry-point for indexing. Once the {@link FullTextThread} is properly * initialized calling {@link #run()} repeatedly and from multiple * {@link Thread threads} should be safe. * * For more control, use the {@link EventLogLoader#more()} method to test how * often calls to {@link #run} should be made. See {@link Main} for examples. * * By default, the indexing will take place as "root". * * @author Josh Moore, josh at glencoesoftware.com * @since 3.0-Beta3 */ public class FullTextThread extends ExecutionThread { private final static Logger log = LoggerFactory.getLogger(FullTextThread.class); private final static Principal DEFAULT_PRINCIPAL = new Principal("root", "system", "FullText"); private static final Executor.Work<?> PREPARE_INDEXING = new Executor.SimpleWork("FullTextIndexer", "prepare") { /** * Since this instance is used repeatedly, we need to check for * already set SqlAction */ @Override public synchronized void setSqlAction(SqlAction sql) { if (getSqlAction() == null) { super.setSqlAction(sql); } } @Transactional(readOnly = false) @Override public Object doWork(Session session, ServiceFactory sf) { // Re-index entries noted in the _updated_annotations table. getSqlAction().refreshEventLogFromUpdatedAnnotations(); return null; } }; final protected boolean waitForLock; final protected FullTextIndexer indexer; final protected FullTextBridge bridge; private boolean isactive = true; private final Lock activeLock = new ReentrantLock(true); /** * Uses default {@link Principal} for indexing */ public FullTextThread(SessionManager manager, Executor executor, FullTextIndexer indexer, FullTextBridge bridge) { this(manager, executor, indexer, bridge, DEFAULT_PRINCIPAL); } /** * Uses default {@link Principal} for indexing */ public FullTextThread(SessionManager manager, Executor executor, FullTextIndexer indexer, FullTextBridge bridge, boolean waitForLock) { this(manager, executor, indexer, bridge, DEFAULT_PRINCIPAL, waitForLock); } /** * Main constructor. No arguments can be null. */ public FullTextThread(SessionManager manager, Executor executor, FullTextIndexer indexer, FullTextBridge bridge, Principal principal) { super(manager, executor, indexer, principal); Assert.notNull(bridge); this.indexer = indexer; this.bridge = bridge; this.waitForLock = false; } /** * Main constructor. No arguments can be null. */ public FullTextThread(SessionManager manager, Executor executor, FullTextIndexer indexer, FullTextBridge bridge, Principal principal, boolean waitForLock) { super(manager, executor, indexer, principal); Assert.notNull(bridge); this.indexer = indexer; this.bridge = bridge; this.waitForLock = waitForLock; } /** * Called by Spring on creation. Currently a no-op. */ public void start() { log.info("Initializing Full-Text Indexer"); } /** * Passes the {@link FullTextIndexer} instance to * {@link ome.services.util.Executor.Work#doWork(Session, ServiceFactory)} * between calls to {@link DetailsFieldBridge#lock()} and * {@link DetailsFieldBridge#unlock()} in order to guarantee that no other * {@link org.hibernate.search.bridge.FieldBridge} can edit the property. * Therefore, only one indexer using this idiom can run at a time. */ @Override public void doRun() { activeLock.lock(); try { if (!isactive) { log.info("Inactive; skipping"); return; } } finally { activeLock.unlock(); } final Map<String, String> callContext = new HashMap<String, String>(); callContext.put("omero.group", "-1"); final boolean gotLock; if (waitForLock) { DetailsFieldBridge.lock(); gotLock = true; } else { gotLock = DetailsFieldBridge.tryLock(); } if (gotLock) { try { DetailsFieldBridge.setFieldBridge(this.bridge); this.executor.execute(callContext, getPrincipal(), PREPARE_INDEXING); this.executor.execute(callContext, getPrincipal(), work); } finally { DetailsFieldBridge.unlock(); } } else { log.info("Currently running; skipping"); } } /** * Called by Spring on destruction. Waits for the global lock on * {@link DetailsFieldBridge} then marks this thread as inactive. */ public void stop() { log.info("Shutting down Full-Text Indexer"); this.indexer.loader.setStop(true); boolean acquiredLock = false; try { acquiredLock = activeLock.tryLock(60, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("active lock acquisition interrupted."); } if (!acquiredLock) { log.error("Could not acquire active lock " + "for indexer within 60 seconds. Overriding."); } acquiredLock = DetailsFieldBridge.tryLock(); if (!acquiredLock) { log.error("Could not acquire bridge lock. " + "Waiting 60 seconds and aborting."); try { Thread.sleep(60 * 1000L); } catch (InterruptedException e) { log.warn("bridge lock acquisition interrupted."); } } isactive = false; if (acquiredLock) { DetailsFieldBridge.unlock(); } } }