/*
* SearchEnginePlugIn.java
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Author: Winter Lau (javayou@gmail.com)
* http://dlog4j.sourceforge.net
*/
package com.liusoft.dlog4j.plugin;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.config.ModuleConfig;
import com.liusoft.dlog4j.Globals;
import com.liusoft.dlog4j.db.HibernateUtils;
import com.liusoft.dlog4j.search.SearchDataProvider;
import com.liusoft.dlog4j.search.SearchEnabled;
import com.liusoft.dlog4j.search.SearchProxy;
import com.liusoft.dlog4j.util.StringUtils;
/**
* <p>A search engine daemon using lucene implements struts's plugin</p>
* <p>This plugin require a property named "basePath"
* to indicate where the lucene files stored.</p>
* <p>
* In UNIX or Linux OS, you must use "file://" to a no-webapp-context path<br/>
* Example: "file:///data/lucene" refer to /data/lucene but {webapp}/data/lucene
* </p>
* @author Winter Lau
*/
public class SearchEnginePlugIn extends DaemonPlugin {
private String basePath;
private String dataProvider0;
private String dataProvider1;
private String dataProvider2;
private String dataProvider3;
private String dataProvider4;
private String dataProvider5;
private String dataProvider6;
private String dataProvider7;
private String dataProvider8;
private String dataProvider9;
private ThreadGroup tGroup = new ThreadGroup("build_idx_thread");
private boolean stop = false;
/**
* ��ʼ����������
*/
public void init(ActionServlet servlet, ModuleConfig config)
throws ServletException
{
if(basePath.startsWith(Globals.LOCAL_PATH_PREFIX)){
basePath = basePath.substring(Globals.LOCAL_PATH_PREFIX.length());
}
else if(basePath.startsWith("/")){
basePath = servlet.getServletContext().getRealPath(basePath);
}
//��ʼ����������
SearchProxy.init(basePath);
super.init(servlet, config);
}
public void destroy() {
stop = true;
int tCount = tGroup.activeCount();
if(tCount > 0){
Thread[] threads = new Thread[tCount];
int tc = tGroup.enumerate(threads);
for(int i=0;i<tc;i++){
if(threads[i] instanceof BuildIndexThread)
try{
threads[i].join(10000, 200);
}catch(InterruptedException e){
log.error("Exception occurred when waiting for thread " + threads[i].getClass().getName(), e);
}
}
}
super.destroy();
}
/**
* �Զ�build�ĵ�������
*/
protected void service() throws Exception {
if(StringUtils.isNotEmpty(dataProvider0))
buildIndexes(dataProvider0);
if(StringUtils.isNotEmpty(dataProvider1))
buildIndexes(dataProvider1);
if(StringUtils.isNotEmpty(dataProvider2))
buildIndexes(dataProvider2);
if(StringUtils.isNotEmpty(dataProvider3))
buildIndexes(dataProvider3);
if(StringUtils.isNotEmpty(dataProvider4))
buildIndexes(dataProvider4);
if(StringUtils.isNotEmpty(dataProvider5))
buildIndexes(dataProvider5);
if(StringUtils.isNotEmpty(dataProvider6))
buildIndexes(dataProvider6);
if(StringUtils.isNotEmpty(dataProvider7))
buildIndexes(dataProvider7);
if(StringUtils.isNotEmpty(dataProvider8))
buildIndexes(dataProvider8);
if(StringUtils.isNotEmpty(dataProvider9))
buildIndexes(dataProvider9);
}
/**
* ��������
* TODO: ��η�ֹ��һ���̻߳�û������������һ���µ��߳�
* @param providerClass
* @param lastTime
*/
private int buildIndexes(final String providerClass){
if(stop)
return -1;
new BuildIndexThread(context(), tGroup, providerClass).start();
return 0;
}
protected String name() {
return "search_engine";
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
public void setDataProvider0(String dataProvider0) {
this.dataProvider0 = dataProvider0;
}
public void setDataProvider1(String dataProvider1) {
this.dataProvider1 = dataProvider1;
}
public void setDataProvider2(String dataProvider2) {
this.dataProvider2 = dataProvider2;
}
public void setDataProvider3(String dataProvider3) {
this.dataProvider3 = dataProvider3;
}
public void setDataProvider4(String dataProvider4) {
this.dataProvider4 = dataProvider4;
}
public void setDataProvider5(String dataProvider5) {
this.dataProvider5 = dataProvider5;
}
public void setDataProvider6(String dataProvider6) {
this.dataProvider6 = dataProvider6;
}
public void setDataProvider7(String dataProvider7) {
this.dataProvider7 = dataProvider7;
}
public void setDataProvider8(String dataProvider8) {
this.dataProvider8 = dataProvider8;
}
public void setDataProvider9(String dataProvider9) {
this.dataProvider9 = dataProvider9;
}
}
/**
* �����������߳�
* @author liudong
*/
class BuildIndexThread extends Thread {
protected final static Log log = LogFactory.getLog(BuildIndexThread.class);
private ServletContext context;
private String providerClass;
public BuildIndexThread(ServletContext context, ThreadGroup group,
String providerClass) {
super(group, providerClass);
this.context = context;
this.providerClass = providerClass;
}
public void run() {
List objs = null;
try {
Date lastTime = getLastActiveTime(providerClass);
SearchDataProvider sdp = (SearchDataProvider) Class.forName(
providerClass).newInstance();
if (lastTime == null)
lastTime = new Date(0);
objs = sdp.fetchAfter(lastTime);
if (objs != null) {
for (int i = 0; i < objs.size(); i++) {
SearchEnabled obj = (SearchEnabled) objs.get(i);
SearchProxy.add(obj);
if (i > 0 && (i + 1) % 10 == 0)
log.info((i + 1) + " document's indexes added.");
}
saveLastActiveTime(providerClass, new Date());
if (objs.size() > 0) {
log.info(objs.size() + " documents writed to disk of "
+ providerClass);
}
} else
log.warn("fetch data of " + providerClass + " return null");
} catch (Exception e) {
log.error("Exception occur when buildIndexes using "
+ providerClass, e);
} finally {
HibernateUtils.closeSession();
objs = null;
}
}
/**
* ��ȡij�����ݽӿ��ϴεĻʱ��
*
* @param pvdClass
* @return
* @throws IOException
*/
private Date getLastActiveTime(String pvdClass) throws IOException {
// Date lastTime = null;
StringBuffer status_file_uri = new StringBuffer(STATUS_FILE_PATH);
status_file_uri.append(pvdClass);
status_file_uri.append(".his");
String realPath = context.getRealPath(status_file_uri.toString());
Properties props = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(realPath);
props.load(fis);
String s_last_time = props.getProperty(TIME_KEY);
return new Date(Long.parseLong(s_last_time));
} catch (FileNotFoundException e) {
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
} finally {
props = null;
if (fis != null)
fis.close();
}
return null;
}
/**
* �������һ�λʱ��
*
* @param pvdClass
* @param time
* @throws IOException
*/
private void saveLastActiveTime(String pvdClass, Date time)
throws IOException {
StringBuffer status_file_uri = new StringBuffer(STATUS_FILE_PATH);
status_file_uri.append(pvdClass);
status_file_uri.append(".his");
String realPath = context.getRealPath(status_file_uri.toString());
Properties props = new Properties();
FileOutputStream fos = null;
try {
props.setProperty(TIME_KEY, String.valueOf(time.getTime()));
props.setProperty("LAST_TIME", time.toString());
fos = new FileOutputStream(realPath);
props.store(fos, null);
} finally {
props = null;
if (fos != null){
fos.close();
fos = null;
}
}
}
private final static String STATUS_FILE_PATH = "/WEB-INF/tmp/";
private final static String TIME_KEY = "LAST_ACTIVITY_TIME";
}