/*
* Copyright 2005-2008 the original author or authors.
*
* 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.
*/
package org.openuap.cms.search.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import org.compass.core.CompassAnalyzerHelper;
import org.compass.core.CompassCallback;
import org.compass.core.CompassDetachedHits;
import org.compass.core.CompassException;
import org.compass.core.CompassHits;
import org.compass.core.CompassQuery;
import org.compass.core.CompassQueryBuilder;
import org.compass.core.CompassSession;
import org.compass.core.CompassToken;
import org.compass.core.CompassQueryBuilder.CompassBooleanQueryBuilder;
import org.compass.core.CompassQueryBuilder.CompassMultiPropertyQueryStringBuilder;
import org.openuap.base.dao.search.DynamicCompassDaoSupport;
import org.openuap.base.util.StringUtil;
import org.openuap.base.util.context.PageBuilder;
import org.openuap.cms.cm.manager.ContentFieldManager;
import org.openuap.cms.cm.manager.ContentTableManager;
import org.openuap.cms.cm.model.ContentField;
import org.openuap.cms.cm.model.ContentTable;
import org.openuap.cms.node.manager.NodeManager;
import org.openuap.cms.node.model.Node;
import org.openuap.cms.search.SearchCommand;
import org.openuap.cms.search.SearchEngine;
import org.openuap.cms.search.SearchResults;
import org.openuap.cms.search.util.CompassHitUtils;
import org.springframework.util.StringUtils;
/**
* <p>
* 基于索引的搜索引擎实现.
* </p>
*
* <p>
* $Id: LuceneSearchEngineImpl.java 4017 2011-03-13 13:55:50Z orangeforjava $
* </p>
*
* @author Joseph
* @version 1.0
*/
public class LuceneSearchEngineImpl extends DynamicCompassDaoSupport implements
SearchEngine {
private NodeManager nodeManager;
private ContentTableManager contentTableManager;
private ContentFieldManager contentFieldManager;
/**
*
*/
@SuppressWarnings("unchecked")
public SearchResults doSearch(final SearchCommand searchCommand)
throws Exception {
//
return (SearchResults) getCompassTemplate()
.execute(new CompassCallback() {
public Object doInCompass(CompassSession session)
throws CompassException {
return performSearch(searchCommand, session);
}
});
}
/**
* 执行搜索
*
* @param searchCommand
* @param session
* @return
*/
protected SearchResults performSearch(SearchCommand searchCommand,
CompassSession session) {
//
long time = System.currentTimeMillis();
String num = searchCommand.getPageNum();
int page = searchCommand.getPage();
String highlights = searchCommand.getHighlights();
//
String[] aHighlight = null;
if (StringUtils.hasText(highlights)) {
aHighlight = highlights.split(",");
} else {
aHighlight = new String[] { "Content", "Title" };
}
String url = searchCommand.getUrl();
int hSize = aHighlight.length;
int start = 0;
// limit若为-1,代表返回所有结果
int limit = 20;
//
if (!num.equals("")) {
int pos = num.indexOf("page");
if (pos == 0) {
// such as page-15
String pageNum = num.substring(5);
int iPageNum = Integer.parseInt(pageNum);
limit = iPageNum;
start = new Integer((page - 1) * limit);
} else {
// such as limit 0,10
//
String[] se = num.split(",");
if (se != null) {
//
if (se.length == 2) {
start = Integer.parseInt(se[0]);
int end = Integer.parseInt(se[1]);
limit = end - start;
} else {
start = 0;
int end = Integer.parseInt(se[0]);
limit = end;
}
}
}
}
// 构造查询
CompassQuery query = buildQuery(searchCommand, session);
// 执行搜索
CompassHits hits = query.hits();
CompassDetachedHits detachedHits = null;
//
// System.out.println("Lucene hits:" + hits.getLength() + ";耗时:"
// + (System.currentTimeMillis() - time) + "ms");
//
PageBuilder pb = new PageBuilder(limit);
pb.setUrl(url);
if (limit == -1) {
detachedHits = hits.detach();
} else {
int hitsLength = hits.getLength();
pb.items(hitsLength);
pb.page(page);
start = pb.beginIndex();
int end = pb.endIndex();
int size = end - start + 1;
if (start > 0) {
for (int i = start - 1; i < end; i++) {
for (int j = 0; j < hSize; j++) {
String h = aHighlight[j];
try {
hits.highlighter(i).fragment(h);
} catch (CompassException e) {
//
}
}
// hits.highlighter(i).fragment("Content");
// hits.highlighter(i).fragment("Title");
}
detachedHits = hits.detach(start - 1, size);
} else {
detachedHits = hits.detach(0, 0);
}
}
time = System.currentTimeMillis() - time;
//转换内容为正确的Map类型
List rs = new ArrayList();
CompassHitUtils.convertHitsToMap(detachedHits.getHits(), rs,
aHighlight);
//
SearchResults sr = new SearchResults(rs, pb, time);
return sr;
}
/**
* 构造查询对象
*
* @param searchCommand
* @param session
* @return
*/
protected CompassQuery buildQuery(SearchCommand searchCommand,
CompassSession session) {
//
CompassQuery query = null;
// 充分的支撑标记
String where = searchCommand.getWhere();
String order = searchCommand.getOrder();
String fields = searchCommand.getFields();
String keyword = searchCommand.getKeyword();
String nodeId = searchCommand.getNodeId();
String nodeGUID = searchCommand.getNodeGUID();
String sTableId = searchCommand.getTableId();
String ignore = searchCommand.getIgnore();
String ignoreIndex = searchCommand.getIgnoreIndex();
boolean parseKeyword=searchCommand.isParseKeyword();
//
Long tableId = null;
String guid = "";
Node firstNode = null;
//
CompassQueryBuilder queryBuilder = session.queryBuilder();
CompassBooleanQueryBuilder keywordQueryBuilder = queryBuilder.bool();
CompassBooleanQueryBuilder nodeIdQueryBuilder = queryBuilder.bool();
// CompassBooleanQueryBuilder ignoreQueryBuilder = queryBuilder.bool();
CompassBooleanQueryBuilder whereQueryBuilder = queryBuilder.bool();
CompassBooleanQueryBuilder aliasQueryBuilder = queryBuilder.bool();
//
CompassBooleanQueryBuilder allQueryBuilder = queryBuilder.bool();
boolean hasNodeId = false;
// boolean hasIgnore = false;
boolean hasWhere = false;
boolean hasAlias = false;
boolean hasKeyword = false;
String alias = null;
String keywordQuery = "";
//
Vector<String> vIgnore = new Vector<String>();
// nodeId过滤
if (!ignore.equals("")) {
//
String[] inIds = ignore.split(",");
if (inIds != null) {
for (int i = 0; i < inIds.length; i++) {
vIgnore.add(inIds[i]);
}
}
}
if (nodeId.equals("")) {
// the nodeId is empty,you can assign the nodeGUID or TableID
if (nodeGUID.equals("")) {
if (!sTableId.equals("")) {
tableId = new Long(sTableId);
}
} else {
guid = nodeGUID.trim();
firstNode = nodeManager.getNodeByGuidFromCache(guid);
if (firstNode != null) {
// 此时只有一个结点,也就是说不存在所谓的忽略问题,因为忽略的本意是为了减少范围,如果按照这个意思,减少没了条件反而是搜索范围扩大了
// 而这个忽略的本意是一定要排除,而且是前提条件是必须存在给定的结点范围
// 没有范围就相当于无从忽略,忽略也就是无从谈起
tableId = firstNode.getTableId();
//
Long nid = firstNode.getNodeId();
hasNodeId = true;
nodeIdQueryBuilder = nodeIdQueryBuilder
.addMust(queryBuilder.term("nodeId", nid));
}
}
} else {
// the nodeId condition
//
if (nodeId.indexOf(",") > -1) {
// such as 1,2,3,5
String[] nodeIds = nodeId.split(",");
if (nodeIds != null) {
String firstNodeId = nodeIds[0];
firstNode = nodeManager.getNode(new Long(firstNodeId));
tableId = firstNode.getTableId();
for (int i = 0; i < nodeIds.length; i++) {
if (!vIgnore.contains(nodeIds[i])) {
hasNodeId = true;
nodeIdQueryBuilder = nodeIdQueryBuilder
.addShould(queryBuilder.term("nodeId",
nodeIds[i]));
}
}
}
} else if (nodeId.indexOf("all-") == 0) {
// such as all-10
String allNodeId = nodeId.substring(4);
firstNode = nodeManager.getNode(new Long(allNodeId));
tableId = firstNode.getTableId();
// get all child Node,include the self
Long iallNodeId = new Long(allNodeId);
List allNodeIdList = nodeManager.getAllChildNodeId(iallNodeId);
int size = allNodeIdList.size();
if (size > 0) {
for (int i = 0; i < size; i++) {
Long nid = (Long) allNodeIdList.get(i);
if (!vIgnore.contains(nid.toString())) {
hasNodeId = true;
nodeIdQueryBuilder = nodeIdQueryBuilder
.addShould(queryBuilder.term("nodeId",
allNodeIdList.get(i)));
}
}
}
} else {
// such as 38,single node
if (!vIgnore.contains(nodeId)) {
hasNodeId = true;
nodeIdQueryBuilder = nodeIdQueryBuilder
.addShould(queryBuilder.term("nodeId", nodeId));
}
//
Node n = nodeManager.getNode(new Long(nodeId));
tableId = n.getTableId();
}
}
//
// 关键字匹配
// TODO 改进,AND与OR应该允许从外部指定,否则影响结果完全是两个样子
if (keyword != null && !keyword.equals("")) {
//
String[] keywordAry = keyword.split("[\\s ]+");
//
//System.out.println("newKeyword=" + newKeyword);
//
if(StringUtil.hasText(keyword)){
hasKeyword = true;
}
//若不存在指定字段,则获取关键字字段作为默认字段
if (!StringUtils.hasText(fields)) {
if (tableId != null) {
ContentField field = contentFieldManager
.getKeywordsFieldFromCache(tableId);
if (field != null) {
fields = field.getFieldName();
}
}
}
if (StringUtils.hasText(fields)&&hasKeyword) {
String columns[] = fields.split(",");
if(parseKeyword){
keyword=this.getTerms(keyword, session);
}
if (columns != null) {
for (int i = 0; i < columns.length; i++) {
//keywordQuery += "(";
//
CompassMultiPropertyQueryStringBuilder mpq=queryBuilder.multiPropertyQueryString(keyword);
//
mpq.forceAnalyzer();
mpq.add(columns[i]);
mpq.useOrDefaultOperator();
CompassQuery kwordQuery=mpq.toQuery();
keywordQueryBuilder=keywordQueryBuilder.addShould(kwordQuery);
// for (String k : keywordAry) {
// if(StringUtil.hasText(k)){
// keywordQuery += columns[i] + ":" + k + " OR ";
// }
// }
//
// keywordQuery = keywordQuery.substring(0, keywordQuery
// .length() - 4);
// keywordQuery += ") OR ";
// keywordQueryBuilder = keywordQueryBuilder
// .addShould(queryBuilder.queryString(q)
// .toQuery());
}
// keywordQuery = keywordQuery.substring(0, keywordQuery
// .length() - 3);
// System.out.println("keywordQuery=" + keywordQueryBuilder.toQuery().toString());
}
} else {
// System.out.println("keyword="+keyword);
keywordQueryBuilder = keywordQueryBuilder.addShould(queryBuilder.multiPropertyQueryString(keyword).toQuery());
}
}
// 2)ignore process
// alias
if (tableId != null) {
ContentTable ct = contentTableManager
.getContentTableFromCache(tableId);
//
//
String publishTableName = "";
if (ct != null) {
String tableName = ct.getEntityName();
//
if (tableName != null && !tableName.trim().equals("")) {
// contentTableName = tableName;
publishTableName = tableName + "Publish";
} else {
publishTableName = "Publish_" + tableId;
}
}
if (!publishTableName.equals("")) {
//
hasAlias = true;
alias = publishTableName;
}
}
// where
boolean bWhereInject = false;
// 处理忽略条件ignoreIndex
if (StringUtils.hasText(ignoreIndex)) {
if (where == null) {
where = "";
}
String[] ignoreIndexArg = ignoreIndex.split(",");
for (String ignoreIndexId : ignoreIndexArg) {
where += " !indexId:" + ignoreIndexId + " AND";
}
where = where.substring(0, where.length() - 3);
}
// 需要特殊处理Where部分
if (where != null && !where.equals("")) {
//
// 判断是否为单一的否定条件,若是单一的否定条件,则需要把此条件并入到其它条件之中
hasWhere = true;
where = where.trim();
int andPos = where.indexOf("AND");
int orPos = where.indexOf("OR");
if (andPos < 0) {
// 没有组合条件
if (orPos < 0) {
if (where.charAt(0) == '!') {
// 此时为单一的否定条件,需要并入到其它条件之中,若没有其它条件,则不能使用此条件
bWhereInject = true;
}
} else {
// TODO 有OR条件此时要检查是不是都为否定条件
}
} else {
if (orPos < 0) {
// 有组合条件,但是此时要检查是不是都为否定条件
String[] whereAry = where.split("AND");
for (String w : whereAry) {
w = w.trim();
if (where.charAt(0) != '!') {
bWhereInject = false;
break;
} else {
bWhereInject = true;
}
}
} else {
// TODO
}
}
}
// alias
if (hasAlias) {
if (hasWhere && bWhereInject) {
// where合并
// String whereQuery =
// queryBuilder.queryString(where).toQuery().;
String aliasQuery = queryBuilder.alias(alias).toString()
+ " AND " + where + "";
allQueryBuilder = allQueryBuilder.addMust(queryBuilder
.queryString(aliasQuery).toQuery());
} else {
allQueryBuilder = allQueryBuilder.addMust(queryBuilder
.alias(alias));
}
}
// where
if (hasWhere && !bWhereInject) {
allQueryBuilder = allQueryBuilder.addMust(queryBuilder.queryString(
where).toQuery());
}
// keyword
//System.out.println("keywordQuery=("+keywordQuery+")");
if (hasKeyword) {
allQueryBuilder = allQueryBuilder.addMust(keywordQueryBuilder.toQuery());
}
// nodeId
if (hasNodeId) {
allQueryBuilder = allQueryBuilder.addMust(nodeIdQueryBuilder
.toQuery());
}
query = allQueryBuilder.toQuery();
// System.out.println("Lucene query=" + query.toString());
// 排序处理
if (order != null && !order.equals("")) {
// System.out.println("Lucene order=" + order);
String[] oAry = order.split(",");
for (int i = 0; i < oAry.length; i++) {
String o = oAry[i];
int pos = o.toUpperCase().lastIndexOf("DESC");
if (pos > -1) {
o = o.substring(0, pos);
query.addSort(o.trim(), CompassQuery.SortDirection.REVERSE);
} else {
int ascPos = o.toUpperCase().lastIndexOf("ASC");
if (ascPos > -1) {
o = o.substring(0, pos);
}
query.addSort(o.trim(), CompassQuery.SortDirection.AUTO);
}
}
} else {
query.addSort(CompassQuery.SortImplicitType.SCORE);
}
//
return query;
}
public void setContentTableManager(ContentTableManager contentTableManager) {
this.contentTableManager = contentTableManager;
}
public void setNodeManager(NodeManager nodeManager) {
this.nodeManager = nodeManager;
}
public PageBuilder doSearchCount(SearchCommand searchCommand)
throws Exception {
SearchResults sr = doSearch(searchCommand);
if (sr != null) {
return sr.getPageBuilder();
}
return null;
}
/**
* 进行分词
* @param query
* @param session
* @return
*/
protected String getTerms(String query,CompassSession session) {
StringBuilder terms=new StringBuilder();
CompassAnalyzerHelper helper=session.analyzerHelper();
CompassToken []tokens=helper.analyze(query);
for(CompassToken token:tokens){
terms.append(token.getTermText());
terms.append(" ");
}
return terms.toString();
}
public ContentFieldManager getContentFieldManager() {
return contentFieldManager;
}
public void setContentFieldManager(ContentFieldManager contentFieldManager) {
this.contentFieldManager = contentFieldManager;
}
}