/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/search/trunk/search-impl/impl/src/java/org/sakaiproject/search/component/service/impl/SearchIndexBuilderWorkerImpl.java $
* $Id: SearchIndexBuilderWorkerImpl.java 111643 2012-08-20 13:41:59Z david.horwitz@uct.ac.za $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.search.component.service.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.UUID;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.search.api.SearchIndexBuilderWorker;
import org.sakaiproject.search.api.SearchService;
import org.sakaiproject.search.dao.SearchIndexBuilderWorkerDao;
import org.sakaiproject.search.indexer.api.IndexQueueListener;
import org.sakaiproject.search.model.SearchBuilderItem;
import org.sakaiproject.search.model.SearchWriterLock;
import org.sakaiproject.search.model.impl.SearchWriterLockImpl;
import org.sakaiproject.thread_local.cover.ThreadLocalManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
public class SearchIndexBuilderWorkerImpl implements Runnable, SearchIndexBuilderWorker
{
private static Log log = LogFactory.getLog(SearchIndexBuilderWorkerImpl.class);
/**
* The lock we use to ensure single search index writer
*/
public static final String LOCKKEY = "searchlockkey";
protected static final Object GLOBAL_CONTEXT = null;
private static final String NO_NODE = "none";
private static final String NODE_LOCK = "nodelockkey";
private int numThreads = 2;
/**
* The maximum sleep time for the wait/notify semaphore
*/
public long sleepTime = 5L * 60000L;
/**
* A load factor 1 is full load, 100 is normal The load factor controlls the
* backoff of the indexer threads. If the load Factor is high, the search
* threads back off more.
*/
private long loadFactor = 1000L;
/**
* The currently running index Builder thread
*/
private Thread indexBuilderThread[] = null;
/**
* sync object
*/
private Object threadStartLock = new Object();
/**
* dependency: the search index builder that is accepting new items
*/
private SearchIndexBuilderImpl searchIndexBuilder = null;
/**
* dependency: the current search service, used to get the location of the
* index
*/
private SearchService searchService = null;
/**
* dependency
*/
private ServerConfigurationService serverConfigurationService;
private DataSource dataSource = null;
/**
* Semaphore
*/
private Object sem = new Object();
/**
* The number of items to process in a batch, default = 100
*/
private boolean enabled = false;
private SessionManager sessionManager;
private UserDirectoryService userDirectoryService;
private EntityManager entityManager;
private EventTrackingService eventTrackingService;
private boolean runThreads = false;
private ThreadLocal<String> nodeIDHolder = new ThreadLocal<String>();
private SearchIndexBuilderWorkerDao searchIndexBuilderWorkerDao = null;
private long lastLock = System.currentTimeMillis();
private long lastEvent = System.currentTimeMillis();
private long lastIndex;
private long startDocIndex;
private String nowIndexing;
private String lastIndexing;
private boolean soakTest = false;
private boolean started = false;
private boolean indexExists = false;
private static HashMap<String, String> nodeIDList = new HashMap<String, String>();;
private static String lockedTo = null;
private static String SELECT_LOCK_SQL = "select id, nodename, "
+ "lockkey, expires from searchwriterlock where lockkey = ?";
private static String UPDATE_LOCK_SQL = "update searchwriterlock set "
+ "nodename = ?, expires = ? where id = ? "
+ "and nodename = ? and lockkey = ? ";
private static String INSERT_LOCK_SQL = "insert into searchwriterlock "
+ "( id,nodename,lockkey, expires ) values ( ?, ?, ?, ? )";
private static String COUNT_WORK_SQL = " select count(*) "
+ "from searchbuilderitem where searchstate = ? ";
private static String CLEAR_LOCK_SQL = "update searchwriterlock "
+ "set nodename = ?, expires = ? where nodename = ? and lockkey = ? ";
private static String SELECT_NODE_LOCK_SQL = "select id, nodename, "
+ "lockkey, expires from searchwriterlock where lockkey like '" + NODE_LOCK
+ "%'";
private static String UPDATE_NODE_LOCK_SQL = "update searchwriterlock set "
+ "expires = ? where nodename = ? and lockkey = ? ";
private static final String SELECT_EXPIRED_NODES_SQL = "select id from searchwriterlock "
+ "where lockkey like '" + NODE_LOCK + "%' and expires < ? ";
private static final String DELETE_LOCKNODE_SQL = "delete from searchwriterlock "
+ "where id = ? ";
public void init()
{
if (started && !runThreads)
{
log.warn("JVM Shutdown in progress, will not startup");
return;
}
if (org.sakaiproject.component.cover.ComponentManager.hasBeenClosed())
{
log.warn("Component manager Shutdown in progress, will not startup");
return;
}
started = true;
runThreads = true;
enabled = "true".equals(serverConfigurationService.getString("search.enable",
"false"));
enabled = enabled && serverConfigurationService.getBoolean("search.indexbuild",true);
try
{
if (searchIndexBuilder == null)
{
log.error("Search Index Worker needs SearchIndexBuilder ");
}
if (searchService == null)
{
log.error("Search Index Worker needs SearchService ");
}
if (searchIndexBuilderWorkerDao == null)
{
log.error("Search Index Worker needs SearchIndexBuilderWorkerDao ");
}
if (eventTrackingService == null)
{
log.error("Search Index Worker needs EventTrackingService ");
}
if (entityManager == null)
{
log.error("Search Index Worker needs EntityManager ");
}
if (userDirectoryService == null)
{
log.error("Search Index Worker needs UserDirectortyService ");
}
if (sessionManager == null)
{
log.error("Search Index Worker needs SessionManager ");
}
log.debug("init start");
indexBuilderThread = new Thread[numThreads];
for (int i = 0; i < indexBuilderThread.length; i++)
{
indexBuilderThread[i] = new Thread(this);
indexBuilderThread[i].setName("SearchBuilder_"+String.valueOf(i));
indexBuilderThread[i].start();
}
eventTrackingService.addLocalObserver(new Observer(){
public void update(Observable arg0, Object arg1)
{
lastEvent = System.currentTimeMillis();
}
});
searchIndexBuilder.addIndexQueueListener(new IndexQueueListener() {
public void added(String name)
{
checkRunning();
}
});
/*
* Capture shutdown
*/
Runtime.getRuntime().addShutdownHook(new Thread()
{
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run()
{
runThreads = false;
}
});
}
catch (Throwable t)
{
log.error("Failed to init ", t);
}
}
/**
* Main run target of the worker thread {@inheritDoc}
*/
public void run()
{
if (!enabled) return;
int threadno = -1;
Thread tt = Thread.currentThread();
for ( int i = 0; i < indexBuilderThread.length; i++ ) {
if ( indexBuilderThread[i] == tt ) {
threadno = i;
}
}
String nodeID = getNodeID();
org.sakaiproject.component.cover.ComponentManager.waitTillConfigured();
try
{
while (runThreads)
{
log.debug("Run Processing Thread");
org.sakaiproject.tool.api.Session s = null;
if (s == null)
{
s = sessionManager.startSession();
User u = userDirectoryService.getUser("admin");
s.setUserId(u.getId());
}
while (runThreads)
{
sessionManager.setCurrentSession(s);
//SAK-17117 before we do this clear threadLocal
//get the security advisor stack otherwise later calls will fail
Object obj = ThreadLocalManager.get("SakaiSecurity.advisor.stack");
Object sess = ThreadLocalManager.get("org.sakaiproject.api.kernel.session.current");
Object toolsess = ThreadLocalManager.get("org.sakaiproject.api.kernel.session.current.tool");
ThreadLocalManager.clear();
ThreadLocalManager.set("SakaiSecurity.advisor.stack", obj);
ThreadLocalManager.set("org.sakaiproject.api.kernel.session.current", sess);
ThreadLocalManager.set("org.sakaiproject.api.kernel.session.current.tool", toolsess);
try
{
int totalDocs = searchIndexBuilder.getPendingDocuments();
long lastEvent = getLastEventTime();
long now = System.currentTimeMillis();
long interval = now - lastEvent;
boolean process = false;
boolean createIndex = false;
if (!indexExists)
{
if (!searchIndexBuilderWorkerDao.indexExists())
{
process = true;
createIndex = true;
log
.debug("No cluster Index exists, creating for the first time");
} else {
indexExists = true;
}
}
else
{
// if activity == totalDocs and interval > 10
if ( totalDocs > 200 ) {
loadFactor = 10L;
} else {
loadFactor = 1000L;
}
if ( totalDocs == 0 ) {
process = false;
} else if (totalDocs < 20 && interval > (20 * loadFactor))
{
process = true;
}
else if (totalDocs >= 20 && totalDocs < 50
&& interval > (10 * loadFactor))
{
process = true;
}
else if (totalDocs >= 50 && totalDocs < 90
&& interval > (5 * loadFactor))
{
process = true;
}
else if (totalDocs > ((90 * loadFactor) / 1000))
{
process = true;
}
}
// should this node consider taking the lock ?
long lastLockInterval = (System.currentTimeMillis() - lastLock);
long lastLockMetric = lastLockInterval * totalDocs;
// if we have 1000 docs, then indexing should happen
// after 10 seconds break
// 1000*10000 10000000
// 500 docs/ 20 seconds
//
// make certain that we are alive
log.debug("Activity "+(lastLockMetric > (10000L * loadFactor))+" "+(lastLockInterval > (60L * loadFactor))+" "+createIndex);
if (lastLockMetric > (10000L * loadFactor)
|| lastLockInterval > (60L * loadFactor) || createIndex)
{
log.debug("===" + process + "=============PROCESSING ");
if (process && getLockTransaction(2L * 60L * 1000L,createIndex))
{
log.debug("===" + nodeID + "=============PROCESSING ");
if (lockedTo != null && lockedTo.equals(nodeID))
{
log
.error("+++++++++++++++Local Lock Collision+++++++++++++");
}
lockedTo = nodeID;
lastLock = System.currentTimeMillis();
if (createIndex)
{
log
.info("=======================Search Index being created for the first time");
searchIndexBuilderWorkerDao
.createIndexTransaction(this);
indexExists = true;
log
.info("=======================Done creating Search Index for the first time");
}
else
{
int batchSize = 100;
if ( totalDocs > 500 ) {
batchSize = 200;
} else if ( totalDocs > 1000 ) {
batchSize = 500;
} else if ( totalDocs > 10000 ) {
batchSize = 1000;
}
searchIndexBuilderWorkerDao
.processToDoListTransaction(this, batchSize);
}
lastLock = System.currentTimeMillis();
if (lockedTo.equals(nodeID))
{
lockedTo = null;
}
else
{
log
.error("+++++++++++++++++++++++++++Lost Local Lock+++++++++++");
}
log.debug("===" + nodeID + "=============COMPLETED ");
}
else
{
break;
}
}
else
{
// make certain the node updates hearbeat
updateNodeLock(2L * 60L * 1000L);
log.debug("Not taking Lock, too much activity");
break;
}
}
finally
{
clearLockTransaction();
}
}
// this is here force cluster members
// this will not reload the index on this node as
if (indexExists)
{
try
{
searchService.reload();
}
catch (Exception ex)
{
log
.info("No Search Segment exists at present, this is Ok on first start :"
+ ex.getMessage());
}
}
if (!runThreads)
{
break;
}
try
{
log.debug("Sleeping Processing Thread");
synchronized (sem)
{
log.debug("++++++WAITING " + nodeID);
sem.wait(sleepTime);
log.debug("+++++ALIVE " + nodeID);
}
log.debug("Wakey Wakey Processing Thread");
if (org.sakaiproject.component.cover.ComponentManager.hasBeenClosed())
{
runThreads = false;
break;
}
if (soakTest && (searchService.getPendingDocs() == 0))
{
log
.error("SOAK TEST---SOAK TEST---SOAK TEST. Index Rebuild Started");
searchService.rebuildInstance();
}
}
catch (InterruptedException e)
{
log.debug(" Exit From sleep " + e.getMessage());
break;
}
}
}
catch (Throwable t)
{
log.warn("Failed in IndexBuilder when indexing document: "+getNowIndexing(), t);
}
finally
{
log.debug("IndexBuilder run exit " + tt.getName());
if ( threadno != -1 ) {
indexBuilderThread[threadno] = null;
}
}
}
private String getNodeID()
{
String nodeID = (String) nodeIDHolder.get();
if (nodeID == null)
{
nodeID = UUID.randomUUID().toString();
nodeIDHolder.set(nodeID);
if (nodeIDList.get(nodeID) == null)
{
nodeIDList.put(nodeID, nodeID);
}
else
{
log.error("============NODE ID " + nodeID
+ " has already been issued, there must be a clash");
}
}
return nodeID;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.component.service.impl.SearchIndexBuilderWorkerAPI#updateNodeLock(java.sql.Connection)
*/
public void updateNodeLock(long lifeLeft) throws SQLException
{
Connection connection = null;
String nodeID = getNodeID();
PreparedStatement updateNodeLock = null;
PreparedStatement deleteExpiredNodeLock = null;
PreparedStatement selectExpiredNodeLock = null;
PreparedStatement insertLock = null;
ResultSet resultSet = null;
String threadID = Thread.currentThread().getName();
boolean savedautocommit = false;
Timestamp now = new Timestamp(System.currentTimeMillis());
// a node can expire, after 2 minutes, to indicate to an admin that it
// is dead
// the admin can then force the
Timestamp nodeExpired = new Timestamp(now.getTime() + lifeLeft);
try
{
connection = dataSource.getConnection();
connection.setAutoCommit(false);
updateNodeLock = connection.prepareStatement(UPDATE_NODE_LOCK_SQL);
deleteExpiredNodeLock = connection.prepareStatement(DELETE_LOCKNODE_SQL);
selectExpiredNodeLock = connection.prepareStatement(SELECT_EXPIRED_NODES_SQL);
insertLock = connection.prepareStatement(INSERT_LOCK_SQL);
int retries = 5;
boolean updated = false;
while (!updated && retries > 0)
{
try
{
try
{
insertLock.clearParameters();
insertLock.setString(1, "Node:" + nodeID); // id
insertLock.setString(2, nodeID); // nodename
insertLock.setString(3, NODE_LOCK + nodeID); // lockkey
insertLock.setTimestamp(4, nodeExpired); // expires
log.debug(threadID + " Doing " + INSERT_LOCK_SQL + ":{" + "Node:"
+ nodeID + "}{" + nodeID + "}{" + NODE_LOCK + nodeID
+ "}{" + nodeExpired + "}");
insertLock.executeUpdate();
}
catch (SQLException ex)
{
updateNodeLock.clearParameters();
updateNodeLock.setTimestamp(1, nodeExpired); // expires
updateNodeLock.setString(2, nodeID); // nodename
updateNodeLock.setString(3, NODE_LOCK + nodeID); // lockkey
log.debug(threadID + " Doing " + UPDATE_NODE_LOCK_SQL + ":{"
+ nodeExpired + "}{" + nodeID + "}{" + NODE_LOCK + nodeID
+ "}");
if (updateNodeLock.executeUpdate() != 1)
{
log.warn("Failed to update node heartbeat " + nodeID);
}
}
log.debug(threadID + " Doing Commit ");
connection.commit();
updated = true;
}
catch (SQLException e)
{
log.warn("Retrying ", e);
try
{
connection.rollback();
}
catch (Exception ex)
{
log.debug(ex);
}
retries--;
try
{
Thread.sleep(100);
}
catch (InterruptedException ie)
{
log.debug(ie);
}
}
}
if (!updated)
{
log.error("Failed to update node lock, will try next time ");
}
else
{
log.debug("Updated Node Lock on " + nodeID + " to Expire at"
+ nodeExpired);
}
retries = 5;
updated = false;
while (!updated && retries > 0)
{
try
{
selectExpiredNodeLock.clearParameters();
selectExpiredNodeLock.setTimestamp(1, now);
log.debug(threadID + " Doing " + SELECT_EXPIRED_NODES_SQL + ":{"
+ now + "}");
resultSet = selectExpiredNodeLock.executeQuery();
while (resultSet.next())
{
String id = resultSet.getString(1);
deleteExpiredNodeLock.clearParameters();
deleteExpiredNodeLock.setString(1, id);
deleteExpiredNodeLock.execute();
connection.commit();
}
log.debug(threadID + " Doing Commit");
connection.commit();
resultSet.close();
updated = true;
}
catch (SQLException e)
{
log.info("Retrying Delete Due to " + e.getMessage());
log.debug("Detailed Traceback ", e);
try
{
resultSet.close();
}
catch (Exception ex)
{
log.debug(ex);
}
try
{
connection.rollback();
}
catch (Exception ex)
{
log.debug(ex);
}
retries--;
try
{
Thread.sleep(100);
}
catch (InterruptedException ie)
{
log.debug(ie);
}
}
}
if (!updated)
{
log.warn("Failed to clear old nodes, will try next time ");
}
}
catch (Exception ex)
{
log.error("Failed to register node ", ex);
if (connection != null)
{
connection.rollback();
}
}
finally
{
if (resultSet != null)
{
try
{
resultSet.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (insertLock != null)
{
try
{
insertLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (updateNodeLock != null)
{
try
{
updateNodeLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (selectExpiredNodeLock != null)
{
try
{
selectExpiredNodeLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (deleteExpiredNodeLock != null)
{
try
{
deleteExpiredNodeLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (connection != null)
{
try
{
connection.setAutoCommit(savedautocommit);
connection.close();
}
catch (SQLException e)
{
log.debug(e);
}
connection = null;
}
}
}
public boolean getLockTransaction(long nodeLifetime)
{
return getLockTransaction(nodeLifetime, false);
}
/**
* @return
* @throws HibernateException
*/
public boolean getLockTransaction(long nodeLifetime, boolean forceLock)
{
if (searchIndexBuilderWorkerDao.isLockRequired())
{
return getHardLock(nodeLifetime, forceLock);
}
else
{
try
{
updateNodeLock(nodeLifetime);
}
catch (SQLException e)
{
log.warn("Failed to update node lock " + e.getClass().getName() + " :"
+ e.getMessage());
}
return true;
}
}
public boolean getHardLock(long nodeLifetime, boolean forceLock)
{
String nodeID = getNodeID();
Connection connection = null;
boolean locked = false;
boolean autoCommit = false;
PreparedStatement selectLock = null;
PreparedStatement updateLock = null;
PreparedStatement insertLock = null;
PreparedStatement countWork = null;
ResultSet resultSet = null;
Timestamp now = new Timestamp(System.currentTimeMillis());
Timestamp expiryDate = new Timestamp(now.getTime() + (10L * 60L * 1000L));
try
{
// I need to go direct to JDBC since its just too awful to
// try and do this in Hibernate.
updateNodeLock(nodeLifetime);
connection = dataSource.getConnection();
autoCommit = connection.getAutoCommit();
if (autoCommit)
{
connection.setAutoCommit(false);
}
selectLock = connection.prepareStatement(SELECT_LOCK_SQL);
updateLock = connection.prepareStatement(UPDATE_LOCK_SQL);
insertLock = connection.prepareStatement(INSERT_LOCK_SQL);
countWork = connection.prepareStatement(COUNT_WORK_SQL);
SearchWriterLock swl = null;
selectLock.clearParameters();
selectLock.setString(1, LOCKKEY);
resultSet = selectLock.executeQuery();
if (resultSet.next())
{
swl = new SearchWriterLockImpl();
swl.setId(resultSet.getString(1));
swl.setNodename(resultSet.getString(2));
swl.setLockkey(resultSet.getString(3));
swl.setExpires(resultSet.getTimestamp(4));
log.debug("GOT Lock Record " + swl.getId() + "::" + swl.getNodename()
+ "::" + swl.getExpires());
}
resultSet.close();
resultSet = null;
boolean takelock = false;
if (swl == null)
{
log.debug("_-------------NO Lock Record");
takelock = true;
}
else if ("none".equals(swl.getNodename()))
{
takelock = true;
log.debug(nodeID + "_-------------no lock");
}
else if (nodeID.equals(swl.getNodename()))
{
takelock = true;
log.debug(nodeID + "_------------matched threadid ");
}
else if (swl.getExpires() == null || swl.getExpires().before(now))
{
takelock = true;
log.debug(nodeID + "_------------thread dead ");
}
if (takelock)
{
// any work ?
int nitems = 0;
if (!forceLock)
{
countWork.clearParameters();
countWork.setInt(1, SearchBuilderItem.STATE_PENDING.intValue());
resultSet = countWork.executeQuery();
if (resultSet.next())
{
nitems = resultSet.getInt(1);
}
resultSet.close();
resultSet = null;
}
if (nitems > 0 || forceLock)
{
try
{
if (swl == null)
{
insertLock.clearParameters();
insertLock.setString(1, nodeID);
insertLock.setString(2, nodeID);
insertLock.setString(3, LOCKKEY);
insertLock.setTimestamp(4, expiryDate);
if (insertLock.executeUpdate() == 1)
{
log.debug("INSERT Lock Record " + nodeID + "::" + nodeID
+ "::" + expiryDate);
locked = true;
}
}
else
{
updateLock.clearParameters();
updateLock.setString(1, nodeID);
updateLock.setTimestamp(2, expiryDate);
updateLock.setString(3, swl.getId());
updateLock.setString(4, swl.getNodename());
updateLock.setString(5, swl.getLockkey());
if (updateLock.executeUpdate() == 1)
{
log.debug("UPDATED Lock Record " + swl.getId() + "::"
+ nodeID + "::" + expiryDate);
locked = true;
}
}
}
catch (SQLException sqlex)
{
locked = false;
log.debug("Failed to get lock, but this is Ok ", sqlex);
}
}
}
connection.commit();
}
catch (Exception ex)
{
if (connection != null)
{
try
{
connection.rollback();
}
catch (SQLException e)
{
log.debug(e);
}
}
log.error("Failed to get lock " + ex.getMessage());
locked = false;
}
finally
{
if (resultSet != null)
{
try
{
resultSet.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (selectLock != null)
{
try
{
selectLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (updateLock != null)
{
try
{
updateLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (insertLock != null)
{
try
{
insertLock.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (countWork != null)
{
try
{
countWork.close();
}
catch (SQLException e)
{
log.debug(e);
}
}
if (connection != null)
{
try
{
connection.setAutoCommit(autoCommit);
}
catch (SQLException e)
{
}
try
{
connection.close();
log.debug("Connection Closed ");
}
catch (SQLException e)
{
log.error("Error Closing Connection ", e);
}
connection = null;
}
}
return locked;
}
/**
* Count the number of pending documents waiting to be indexed on this
* cluster node. All nodes will potentially perform the index in a cluster,
* however only one must be doing the index, hence this method attampts to
* grab a lock on the writer. If sucessfull it then gets the real number of
* pending documents. There is a timeout, such that if the witer has not
* been seen for 10 minutes, it is assumed that something has gon wrong with
* it, and a new writer is elected on a first grab basis. Every time the
* elected writer comes back, it updates its record to say its still active.
* We could do some round robin timeout, or allow deployers to select a pool
* of index writers in Sakai properties. {@inheritDoc}
*/
private void clearLockTransaction()
{
if (searchIndexBuilderWorkerDao.isLockRequired())
{
clearHardLock();
}
}
public void clearHardLock()
{
String nodeID = getNodeID();
Connection connection = null;
PreparedStatement clearLock = null;
try
{
connection = dataSource.getConnection();
clearLock = connection.prepareStatement(CLEAR_LOCK_SQL);
clearLock.clearParameters();
clearLock.setString(1, NO_NODE);
clearLock.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
clearLock.setString(3, nodeID);
clearLock.setString(4, LOCKKEY);
if (clearLock.executeUpdate() == 1)
{
log.debug("UNLOCK - OK::" + nodeID + "::now");
}
else
{
log.debug("UNLOCK - no-lock::" + nodeID + "::now");
}
connection.commit();
}
catch (Exception ex)
{
try
{
if (connection != null)
{
connection.rollback();
}
}
catch (SQLException e)
{
}
log.error("Failed to clear lock" + ex.getMessage());
}
finally
{
if (clearLock != null)
{
try
{
clearLock.close();
}
catch (SQLException e)
{
log.error("Error Closing Prepared Statement ", e);
}
}
if (connection != null)
{
try
{
connection.close();
log.debug("Connection Closed");
}
catch (SQLException e)
{
log.error("Error Closing Connection", e);
}
}
}
}
public boolean isRunning()
{
if (org.sakaiproject.component.cover.ComponentManager.hasBeenClosed())
{
runThreads = false;
}
return runThreads;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.component.service.impl.SearchIndexBuilderWorkerAPI#checkRunning()
*/
public void checkRunning()
{
if (!enabled) return;
runThreads = true;
synchronized (threadStartLock)
{
for (int i = 0; i < indexBuilderThread.length; i++)
{
if (indexBuilderThread[i] == null)
{
indexBuilderThread[i] = new Thread(this);
indexBuilderThread[i].setName(String.valueOf(i) + "::"
+ this.getClass().getName());
indexBuilderThread[i].start();
}
}
}
synchronized (sem)
{
log.debug("_________NOTIFY");
sem.notify();
log.debug("_________NOTIFY COMPLETE");
}
}
public void destroy()
{
if (!enabled) return;
log.debug("Destroy SearchIndexBuilderWorker ");
runThreads = false;
synchronized (sem)
{
sem.notifyAll();
}
}
/**
* @return Returns the sleepTime.
*/
public long getSleepTime()
{
return sleepTime;
}
/**
* @param sleepTime
* The sleepTime to set.
*/
public void setSleepTime(long sleepTime)
{
this.sleepTime = sleepTime;
}
/**
* @return Returns the dataSource.
*/
public DataSource getDataSource()
{
return dataSource;
}
/**
* @param dataSource
* The dataSource to set.
*/
public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
}
/**
* @return Returns the searchIndexBuilderWorkerDao.
*/
public SearchIndexBuilderWorkerDao getSearchIndexBuilderWorkerDao()
{
return searchIndexBuilderWorkerDao;
}
/**
* @param searchIndexBuilderWorkerDao
* The searchIndexBuilderWorkerDao to set.
*/
public void setSearchIndexBuilderWorkerDao(
SearchIndexBuilderWorkerDao searchIndexBuilderWorkerDao)
{
this.searchIndexBuilderWorkerDao = searchIndexBuilderWorkerDao;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.component.service.impl.SearchIndexBuilderWorkerAPI#getCurrentLock()
*/
public SearchWriterLock getCurrentLock()
{
getNodeID();
Connection connection = null;
PreparedStatement selectLock = null;
ResultSet resultSet = null;
try
{
// I need to go direct to JDBC since its just too awful to
// try and do this in Hibernate.
connection = dataSource.getConnection();
selectLock = connection.prepareStatement(SELECT_LOCK_SQL);
SearchWriterLock swl = null;
selectLock.clearParameters();
selectLock.setString(1, LOCKKEY);
resultSet = selectLock.executeQuery();
if (resultSet.next())
{
swl = new SearchWriterLockImpl();
swl.setId(resultSet.getString(1));
swl.setNodename(resultSet.getString(2));
swl.setLockkey(resultSet.getString(3));
swl.setExpires(resultSet.getTimestamp(4));
log.debug("GOT Lock Record " + swl.getId() + "::" + swl.getNodename()
+ "::" + swl.getExpires());
}
resultSet.close();
resultSet = null;
if (swl == null)
{
swl = new SearchWriterLockImpl();
swl.setNodename(NO_NODE);
swl.setLockkey(LOCKKEY);
swl.setExpires(new Timestamp(0));
}
return swl;
}
catch (Exception ex)
{
log.error("Failed to get lock " + ex.getMessage());
SearchWriterLock swl = new SearchWriterLockImpl();
swl.setNodename(NO_NODE);
swl.setLockkey(LOCKKEY);
swl.setExpires(new Timestamp(0));
return swl;
}
finally
{
if (resultSet != null)
{
try
{
resultSet.close();
}
catch (SQLException e)
{
}
}
if (selectLock != null)
{
try
{
selectLock.close();
}
catch (SQLException e)
{
}
}
if (connection != null)
{
try
{
connection.close();
log.debug("Connection Closed ");
}
catch (SQLException e)
{
log.error("Error Closing Connection ", e);
}
connection = null;
}
}
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.component.service.impl.SearchIndexBuilderWorkerAPI#getNodeStatus()
*/
public List<SearchWriterLock> getNodeStatus()
{
getNodeID();
Connection connection = null;
PreparedStatement selectLock = null;
ResultSet resultSet = null;
ArrayList<SearchWriterLock>locks = new ArrayList<SearchWriterLock>();
try
{
// I need to go direct to JDBC since its just too awful to
// try and do this in Hibernate.
connection = dataSource.getConnection();
selectLock = connection.prepareStatement(SELECT_NODE_LOCK_SQL);
selectLock.clearParameters();
resultSet = selectLock.executeQuery();
while (resultSet.next())
{
SearchWriterLock swl = new SearchWriterLockImpl();
swl.setId(resultSet.getString(1));
swl.setNodename(resultSet.getString(2));
swl.setLockkey(resultSet.getString(3));
swl.setExpires(resultSet.getTimestamp(4));
log.debug("GOT Lock Record " + swl.getId() + "::" + swl.getNodename()
+ "::" + swl.getExpires());
locks.add(swl);
}
resultSet.close();
resultSet = null;
return locks;
}
catch (Exception ex)
{
log.error("Failed to load nodes ", ex);
return locks;
}
finally
{
if (resultSet != null)
{
try
{
resultSet.close();
}
catch (SQLException e)
{
}
}
if (selectLock != null)
{
try
{
selectLock.close();
}
catch (SQLException e)
{
}
}
if (connection != null)
{
try
{
connection.close();
log.debug("Connection Closed ");
}
catch (SQLException e)
{
log.error("Error Closing Connection ", e);
}
connection = null;
}
}
}
public boolean removeWorkerLock()
{
Connection connection = null;
PreparedStatement selectLock = null;
PreparedStatement selectNodeLock = null;
PreparedStatement clearLock = null;
ResultSet resultSet = null;
try
{
// I need to go direct to JDBC since its just too awful to
// try and do this in Hibernate.
connection = dataSource.getConnection();
selectNodeLock = connection.prepareStatement(SELECT_NODE_LOCK_SQL);
selectLock = connection.prepareStatement(SELECT_LOCK_SQL);
clearLock = connection.prepareStatement(CLEAR_LOCK_SQL);
SearchWriterLock swl = null;
selectLock.clearParameters();
selectLock.setString(1, LOCKKEY);
resultSet = selectLock.executeQuery();
if (resultSet.next())
{
swl = new SearchWriterLockImpl();
swl.setId(resultSet.getString(1));
swl.setNodename(resultSet.getString(2));
swl.setLockkey(resultSet.getString(3));
swl.setExpires(resultSet.getTimestamp(4));
}
else
{
connection.rollback();
return true;
}
resultSet.close();
resultSet = null;
selectNodeLock.clearParameters();
resultSet = selectLock.executeQuery();
while (resultSet.next())
{
SearchWriterLock node = new SearchWriterLockImpl();
node.setId(resultSet.getString(1));
node.setNodename(resultSet.getString(2));
node.setLockkey(resultSet.getString(3));
node.setExpires(resultSet.getTimestamp(4));
if (swl.getNodename().equals(node.getNodename()))
{
log.info("Cant remove Lock to node " + node.getNodename()
+ " node exists ");
connection.rollback();
return false;
}
}
resultSet.close();
resultSet = null;
clearLock.clearParameters();
clearLock.setString(1, NO_NODE);
clearLock.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
clearLock.setString(3, swl.getNodename());
clearLock.setString(4, LOCKKEY);
if (clearLock.executeUpdate() == 1)
{
log.warn("NODE UNLOCKED BY USER " + swl.getNodename());
connection.commit();
}
else
{
log.info("NODE NOT UNLOCKED BY USER " + swl.getNodename());
connection.commit();
return false;
}
return true;
}
catch (Exception ex)
{
log.error("Failed to unlock ", ex);
return false;
}
finally
{
if (resultSet != null)
{
try
{
resultSet.close();
}
catch (SQLException e)
{
}
}
if (selectLock != null)
{
try
{
selectLock.close();
}
catch (SQLException e)
{
}
}
if (selectNodeLock != null)
{
try
{
selectNodeLock.close();
}
catch (SQLException e)
{
}
}
if (clearLock != null)
{
try
{
clearLock.close();
}
catch (SQLException e)
{
}
}
if (connection != null)
{
try
{
connection.close();
log.debug("Connection Closed ");
}
catch (SQLException e)
{
log.error("Error Closing Connection ", e);
}
connection = null;
}
}
}
public long getLastEventTime()
{
return lastEvent;
}
public void setLastIndex(long l)
{
this.lastIndex = l;
}
public void setStartDocIndex(long startDocIndex)
{
this.startDocIndex = startDocIndex;
}
public void setNowIndexing(String reference)
{
this.lastIndexing = this.nowIndexing;
this.nowIndexing = reference;
}
/**
* @return Returns the lastIndex.
*/
public long getLastIndex()
{
return lastIndex;
}
/**
* @return Returns the nowIndexing.
*/
public String getNowIndexing()
{
return nowIndexing;
}
/**
* @return Returns the startDocIndex.
*/
public long getStartDocIndex()
{
return startDocIndex;
}
public String getLastDocument()
{
return lastIndexing;
}
public String getLastElapsed()
{
long l = lastIndex;
long h = l / 3600000L;
l = l - (3600000L * h);
long m = l / 600000L;;
l = l - (60000L * m);
long s = l / 1000;
l = l - (1000L * s);
return "" + h + "h" + m + "m" + s + "." + l + "s";
}
public String getCurrentDocument()
{
return nowIndexing;
}
public String getCurrentElapsed()
{
long l = System.currentTimeMillis() - startDocIndex;
long h = l / 3600000L;
l = l - (3600000L * h);
long m = l / 60000L;
l = l - (60000L * m);
long s = l / 1000L;
l = l - (1000L * s);
return "" + h + "h" + m + "m" + s + "." + l + "s";
}
/**
* @return the loadFactor
*/
public long getLoadFactor()
{
return loadFactor;
}
/**
* @param loadFactor
* the loadFactor to set
*/
public void setLoadFactor(long loadFactor)
{
this.loadFactor = loadFactor;
}
/**
* Is the lock on this node, but not this thread lockedTo == null, localloc ==
* false lockedTo == this node, locallock = false; lockedTo != this node,
* localLock = true
*/
public boolean isLocalLock()
{
if (lockedTo == null)
{
return false;
}
else if (getNodeID().equals(lockedTo))
{
return false;
}
return true;
}
/**
* @return the soakTest
*/
public boolean getSoakTest()
{
return soakTest;
}
/**
* Puts the index builder into a Soak test, when there are no pending items,
* it starts building again.
*
* @param soakTest
* the soakTest to set
*/
public void setSoakTest(boolean soakTest)
{
this.soakTest = soakTest;
if (soakTest)
{
log.warn("SOAK TEST ACTIVE ======================DONT USE FOR PRODUCTION ");
}
}
/**
* @return the entityManager
*/
public EntityManager getEntityManager()
{
return entityManager;
}
/**
* @param entityManager the entityManager to set
*/
public void setEntityManager(EntityManager entityManager)
{
this.entityManager = entityManager;
}
/**
* @return the eventTrackingService
*/
public EventTrackingService getEventTrackingService()
{
return eventTrackingService;
}
/**
* @param eventTrackingService the eventTrackingService to set
*/
public void setEventTrackingService(EventTrackingService eventTrackingService)
{
this.eventTrackingService = eventTrackingService;
}
/**
* @return the searchIndexBuilder
*/
public SearchIndexBuilderImpl getSearchIndexBuilder()
{
return searchIndexBuilder;
}
/**
* @param searchIndexBuilder the searchIndexBuilder to set
*/
public void setSearchIndexBuilder(SearchIndexBuilderImpl searchIndexBuilder)
{
this.searchIndexBuilder = searchIndexBuilder;
}
/**
* @return the searchService
*/
public SearchService getSearchService()
{
return searchService;
}
/**
* @param searchService the searchService to set
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* @return the serverConfigurationService
*/
public ServerConfigurationService getServerConfigurationService()
{
return serverConfigurationService;
}
/**
* @param serverConfigurationService the serverConfigurationService to set
*/
public void setServerConfigurationService(
ServerConfigurationService serverConfigurationService)
{
this.serverConfigurationService = serverConfigurationService;
}
/**
* @return the sessionManager
*/
public SessionManager getSessionManager()
{
return sessionManager;
}
/**
* @param sessionManager the sessionManager to set
*/
public void setSessionManager(SessionManager sessionManager)
{
this.sessionManager = sessionManager;
}
/**
* @return the userDirectoryService
*/
public UserDirectoryService getUserDirectoryService()
{
return userDirectoryService;
}
/**
* @param userDirectoryService the userDirectoryService to set
*/
public void setUserDirectoryService(UserDirectoryService userDirectoryService)
{
this.userDirectoryService = userDirectoryService;
}
public void setNumThreads(int numThreads) {
this.numThreads = numThreads;
}
}