/*
* SearchProxy.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.search;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.NestedNullException;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import com.liusoft.dlog4j.beans.DiaryBean;
/**
* ��������
*
* <pre>
* ��ˮ�����⣬�������²�ͬ��
* ֪��˭���⣬������ͬ��
* ��ɽ�����⣬��������ؽ�
* ����һҶ�⣬�����˵��κ���
* </pre>
*
* @author Winter Lau
*/
public class SearchProxy {
private final static Log log = LogFactory.getLog(SearchProxy.class);
public final static int MAX_RESULT_COUNT = 200;
//���ڴ洢���������
private final static String CLASSNAME_FIELD = "___className";
private static String _baseIndexPath;
public static void init(String _base_index_path){
_baseIndexPath = _base_index_path;
if(!_baseIndexPath.endsWith(File.separator))
_baseIndexPath += File.separator;
}
/**
* ����ĵ�
* @param doc
* @throws Exception
*/
public static synchronized void add(SearchEnabled doc) throws Exception {
if(doc == null)
return ;
Document lucene_doc = new Document();
//Set keyword field
String key = getField(doc, doc.getKeywordField());
lucene_doc.add(Keyword(doc.getKeywordField(), key));
//Set identity(classname) of object
lucene_doc.add(Keyword(CLASSNAME_FIELD, doc.getClass().getName()));
//Set storage field
String[] storeFields = doc.getStoreFields();
for(int i=0;storeFields!=null && i<storeFields.length;i++){
String propertyValue = getField(doc, storeFields[i]);
if(propertyValue!=null)
lucene_doc.add(Keyword(storeFields[i], propertyValue));
}
//Set indexed field
String[] indexFields = doc.getIndexFields();
for(int i=0;indexFields!=null && i<indexFields.length;i++){
String propertyValue = getField(doc, indexFields[i]);
lucene_doc.add(UnStored(indexFields[i], propertyValue));
}
//Write document
IndexWriter writer = getWriter(doc.name());
try {
writer.addDocument(lucene_doc);
writer.optimize();
}finally {
try{
writer.close();
}catch(Exception e){
log.error("Error occur when closing IndexWriter", e);
}finally{
writer = null;
}
lucene_doc = null;
}
}
/**
* ��������ɾ���ĵ�
* @param doc
* @return
* @throws Exception
*/
public static synchronized int remove(SearchEnabled doc){
if(doc == null)
return -1;
IndexReader reader = null;
try{
reader = getReader(doc.name());
String pvalue = getField(doc, doc.getKeywordField());
Term keyTerm = new Term(doc.getKeywordField(), pvalue);
return reader.deleteDocuments(keyTerm);
}catch(Exception e){
log.error("Error where delete index of "+ doc, e);
}finally{
if(reader!=null)
try{
reader.close();
}catch(Exception e){
log.error("Error occur when closing IndexReader", e);
}finally{
reader = null;
}
}
return -1;
}
/**
* �����ĵ�������
* @param doc
* @return
* @throws Exception
*/
public static void update(SearchEnabled doc) throws Exception{
if(doc == null)
return;
remove(doc);
add(doc);
}
/**
* �ĵ�����
* @param params
* @return
* @throws Exception
*/
public static List search(SearchParameter params) throws Exception{
if(params == null)
return null;
SearchEnabled searching = (SearchEnabled)params.getSearchObject().newInstance();
StringBuffer path = new StringBuffer(_baseIndexPath);
path.append(searching.name());
File f = new File(path.toString());
if(!f.exists())
return null;
IndexSearcher searcher = new IndexSearcher(path.toString());
//���������ؼ���
BooleanQuery comboQuery = new BooleanQuery();
int _query_count = 0;
StringTokenizer st = new StringTokenizer(params.getSearchKey());
while(st.hasMoreElements()){
String q = st.nextToken();
String[] indexFields = searching.getIndexFields();
for(int i=0;i<indexFields.length;i++){
QueryParser qp = new QueryParser(indexFields[i], analyzer);
try{
Query subjectQuery = qp.parse(q);
comboQuery.add(subjectQuery, BooleanClause.Occur.SHOULD);
_query_count ++;
}catch(Exception e){
log.error("Add query parameter failed. key="+q, e);
}
}
}
if(_query_count==0)//û���κιؼ�������
return null;
//�����ĸ�������
MultiFilter multiFilter = null;
HashMap conds = params.getConditions();
if(conds!=null){
Iterator keys = conds.keySet().iterator();
while(keys.hasNext()){
if(multiFilter == null)
multiFilter = new MultiFilter(0);
String key = (String)keys.next();
multiFilter.add(new FieldFilter(key,conds.get(key).toString()));
}
}
/*
* Creates a sort, possibly in reverse,
* by terms in the given field with the type of term values explicitly given.
*/
SortField[] s_fields = new SortField[2];
s_fields[0] = SortField.FIELD_SCORE;
s_fields[1] = new SortField(searching.getKeywordField(), SortField.INT, true);
Sort sort = new Sort(s_fields);
Hits hits = searcher.search(comboQuery, multiFilter, sort);
int numResults = hits.length();
//System.out.println(numResults + " found............................");
int result_count = Math.min(numResults, MAX_RESULT_COUNT);
List results = new ArrayList(result_count);
for(int i=0;i<result_count;i++){
Document doc = (Document)hits.doc(i);
//ӳ���ĵ����Ե�Java������
Object result = params.getSearchObject().newInstance();
Enumeration fields = doc.fields();
while(fields.hasMoreElements()){
Field field = (Field)fields.nextElement();
//System.out.println(field.name()+" -- "+field.stringValue());
if(CLASSNAME_FIELD.equals(field.name()))
continue;
//�����ֶβ�����ӳ��
if(!field.isStored())
continue;
//System.out.println("=========== begin to mapping ============");
//String --> anything
Class fieldType = getNestedPropertyType(result, field.name());
//System.out.println(field.name()+", class = " + fieldType.getName());
Object fieldValue = null;
if(fieldType.equals(Date.class))
fieldValue = new Date(Long.parseLong(field.stringValue()));
else
fieldValue = ConvertUtils.convert(field.stringValue(), fieldType);
//System.out.println(fieldValue+", class = " + fieldValue.getClass().getName());
setNestedProperty(result, field.name(), fieldValue);
}
results.add(result);
}
return results;
}
/**
* ��ȡǶ�����Ե�����
* @param obj
* @param field
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IntrospectionException
*/
private static Class getNestedPropertyType(Object obj, String field)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException, SecurityException, NoSuchFieldException, IntrospectionException {
StringTokenizer st = new StringTokenizer(field, ".");
Class nodeClass = obj.getClass();
while (st.hasMoreElements()) {
String f = st.nextToken();
PropertyDescriptor[] props = Introspector.getBeanInfo(nodeClass).getPropertyDescriptors();
for(int i=0;i<props.length;i++){
if(props[i].getName().equals(f)){
nodeClass = props[i].getPropertyType();
continue;
}
}
}
return nodeClass;
}
/**
* �����ֶ�ֵ
* @param obj
* @param field
* @param value
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws IntrospectionException
* @throws InstantiationException
*/
private static void setNestedProperty(Object obj, String field, Object value)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException, IntrospectionException,
InstantiationException {
StringTokenizer st = new StringTokenizer(field, ".");
Class nodeClass = obj.getClass();
StringBuffer tmp_prop = new StringBuffer();
while (st.hasMoreElements()) {
String f = st.nextToken();
if(tmp_prop.length()>0)
tmp_prop.append('.');
tmp_prop.append(f);
PropertyDescriptor[] props = Introspector.getBeanInfo(nodeClass)
.getPropertyDescriptors();
for (int i = 0; i < props.length; i++) {
if (props[i].getName().equals(f)) {
if(PropertyUtils.getNestedProperty(obj, tmp_prop.toString())==null){
nodeClass = props[i].getPropertyType();
PropertyUtils.setNestedProperty(obj, f, nodeClass
.newInstance());
}
continue;
}
}
}
PropertyUtils.setNestedProperty(obj, field, value);
}
/**
* ��ȡ������
* @param name
* @return
* @throws IOException
*/
private static IndexReader getReader(String name) throws IOException{
StringBuffer path = new StringBuffer(_baseIndexPath);
path.append(name);
try{
return IndexReader.open(path.toString());
}finally{
path = null;
}
}
/**
* ��ȡ����д
* @param name
* @return
* @throws IOException
*/
private static IndexWriter getWriter(String name) throws IOException{
StringBuffer path = new StringBuffer(_baseIndexPath);
path.append(name);
String index_path = path.toString();
File rp = new File(index_path);
if(!rp.exists())
rp.mkdirs();
int wc = 0;
//waiting for the lock of indexes
while(wc<10 && IndexReader.isLocked(index_path)){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return null;
}
wc++;
}
path.append(File.separator);
path.append(SEGMENTS);
File segments = new File(path.toString());
try{
boolean bCreate = !segments.exists();
return new IndexWriter(index_path,new StandardAnalyzer(),bCreate);
}finally{
path = null;
segments = null;
rp = null;
}
}
private final static String SEGMENTS = "segments";
/**
* ���ʶ���ij�����Ե�ֵ
* @param obj
* @param field
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
private static String getField(Object obj, String field)
throws IllegalAccessException,
InvocationTargetException,
NoSuchMethodException
{
try{
Object fieldValue = PropertyUtils.getNestedProperty(obj, field);
if(fieldValue instanceof String)
return (String)fieldValue;
if(fieldValue instanceof Date)
return Long.toString(((Date)fieldValue).getTime());
return String.valueOf(fieldValue);
}catch(NestedNullException e){}
return null;
}
private static StandardAnalyzer analyzer = new StandardAnalyzer();
protected static final Field Keyword(String name, String value) {
return new Field(name, value, Field.Store.YES, Field.Index.UN_TOKENIZED);
}
protected static final Field Text(String name, String value){
return new Field(name, value, Field.Store.YES, Field.Index.TOKENIZED);
}
protected static final Field UnStored(String name, String value){
return new Field(name, value, Field.Store.NO, Field.Index.TOKENIZED);
}
/**
* ��˽�з����IJ���
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
//Ƕ�����Զ�ȡ
DiaryBean log = new DiaryBean();
/*
log.setAuthor("Winter Lau");
System.out.println(getField(log, "author"));
log.setOwner(new UserBean(123));
log.getOwner().setNickname("����");
System.out.println(getField(log, "owner.nickname"));
Class ft = PropertyUtils.getPropertyType(log, "owner.id");
Object fv = ConvertUtils.convert("119", ft);
PropertyUtils.setNestedProperty(log, "owner.id", fv);
System.out.println(getField(log, "owner.id"));
*/
setNestedProperty(log,"site.title", "JavaYou");
setNestedProperty(log,"site.friendlyName", "Java������");
System.out.println(getNestedPropertyType(log,"site.id").getName());
System.out.println(log.getSite().getTitle());
System.out.println(log.getSite().getFriendlyName());
}
}