/*
* Copyright 2002-2005 Uwyn bvba/sprl <info[remove] at uwyn dot com>
* Distributed under the terms of the GNU Lesser General Public
* License, v2.1 or later
*
* $Id: Search.java 2003 2005-06-04 14:33:27Z gbevin $
*/
package com.uwyn.drone.webui.elements.pub;
import java.util.*;
import org.apache.lucene.search.*;
import com.uwyn.drone.DroneConfig;
import com.uwyn.drone.core.Bot;
import com.uwyn.drone.core.BotsRunner;
import com.uwyn.drone.core.Channel;
import com.uwyn.drone.tools.LocaleProvider;
import com.uwyn.drone.tools.LocaleProviderConfig;
import com.uwyn.drone.tools.SearchTool;
import com.uwyn.rife.config.Config;
import com.uwyn.rife.engine.Element;
import com.uwyn.rife.site.ConstrainedProperty;
import com.uwyn.rife.site.PagedNavigation;
import com.uwyn.rife.site.Validation;
import com.uwyn.rife.site.ValidationError;
import com.uwyn.rife.template.Template;
import com.uwyn.rife.tools.ExceptionUtils;
import com.uwyn.rife.tools.Localization;
import com.uwyn.rife.tools.SortListComparables;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.DateField;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
public class Search extends Element
{
private static final int LIMIT = 100;
private static final int SPAN = 5;
public static final SimpleDateFormat INPUT_DATE_FORMAT_SHORT = new SimpleDateFormat("dd/MM/yyyy");
public static final SimpleDateFormat LUCENE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
public SimpleDateFormat mDateFormat;
public SimpleDateFormat mOutputTimeFormat;
public SimpleDateFormat mOutputDateFormat;
public LocaleProvider mLocaleProvider = LocaleProviderConfig.SINGLETON;
private Template mTemplate = null;
private HashMap mNickColors = new HashMap();
private int mColorCounter = 0;
private String mCurrentDate = null;
private String mCurrentBotname = null;
private String mCurrentServername = null;
private String mCurrentChannelname = null;
static
{
INPUT_DATE_FORMAT_SHORT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
LUCENE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
}
public void setLocaleProvider(LocaleProvider localeProvider)
{
mLocaleProvider = localeProvider;
}
public void initialize()
{
mTemplate = getHtmlTemplate("drone.pub.search");
Locale locale = mLocaleProvider.retrieveLocale(this, mTemplate);
mDateFormat = new SimpleDateFormat("yyyyMMdd", locale);
mOutputTimeFormat = new SimpleDateFormat("HH:mm", locale);
mOutputDateFormat = new SimpleDateFormat("dd-MMMM-yyyy", locale);
mDateFormat.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
mOutputTimeFormat.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
mOutputDateFormat.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
}
public void processElement()
{
SearchBean bean = (SearchBean)getNamedInputBean("SearchBean");
if (!bean.isValidSearch())
{
bean = (SearchBean)getNamedInputBean("OffsetBean");
}
if (bean.isValidSearch())
{
doSearch(bean, getInputInt("offset", 0));
return;
}
print(mTemplate);
}
private void doSearch(SearchBean submission, int offset)
{
assert submission != null;
BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
SearchTool search_tool = new SearchTool();
BooleanQuery query = search_tool.getSearchQuery(submission);
setNamedOutputBean("OffsetBean", submission);
if (submission.isValidSearch() && query != null)
{
Hits hits = null;
try
{
MultiReader reader = null;
ParallelMultiSearcher searcher = null;
List searchers = new ArrayList();
List readers = new ArrayList();
Collection bots = BotsRunner.getRepInstance().getBots();
Iterator iter = bots.iterator();
while (iter.hasNext())
{
Bot bot = (Bot)iter.next();
Iterator channels_it = bot.getJoinedChannels().iterator();
while (channels_it.hasNext())
{
Channel channel = ((Channel)channels_it.next());
StringBuffer key = new StringBuffer();
key
.append(Config.getRepInstance().getString("LUCENE_DIR"))
.append(File.separator)
.append(bot.getName())
.append("-")
.append(bot.getServer().getServerName())
.append("-")
.append(channel.getName().substring(1));
if (submission.getChannel() == null ||
submission.getChannel().equals(""))
{
IndexReader r = IndexReader.open(key.toString());
IndexSearcher s = new IndexSearcher(r);
searchers.add(s);
readers.add(r);
}
else
{
String submissionChannel = submission.getChannel().substring(submission.getChannel().indexOf("#"));
if (channel.getName().equals(submissionChannel))
{
IndexReader r = IndexReader.open(key.toString());
IndexSearcher s = new IndexSearcher(r);
searchers.add(s);
readers.add(r);
}
}
}
}
IndexSearcher[] searcherArray = (IndexSearcher[])searchers.toArray(new IndexSearcher[0]);
IndexReader[] readerArray = (IndexReader[])readers.toArray(new IndexReader[0]);
searcher = new ParallelMultiSearcher(searcherArray);
reader = new MultiReader(readerArray);
SortField dateSort = new SortField("momentDateSort", SortField.AUTO, true);
SortField timeSort = new SortField("momentTimeSort", SortField.AUTO, false);
hits = searcher.search(query, new Sort(new SortField[] { dateSort, timeSort }));
// only highlight in the message
Query keywordQuery = search_tool.getKeywordQuery();
Highlighter highlighter = null;
if (keywordQuery != null)
{
Query rewritten_query = keywordQuery.rewrite(reader);
SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span class=\"highlighted\">", "</span>");
highlighter = new Highlighter(formatter, new QueryScorer(rewritten_query));
}
for (int i = offset; i < LIMIT + offset; i++)
{
if (i >= hits.length())
{
// handle partial full pages
break;
}
Document doc = hits.doc(i);
// pull in our values
String nickname = doc.getField("nickname").stringValue();
String message = doc.getField("message").stringValue();
String channelname = doc.getField("channel").stringValue();
String botname = doc.getField("botname").stringValue();
String servername = doc.getField("servername").stringValue();
Date moment = DateField.stringToDate(doc.getField("moment").stringValue());
String day = mOutputDateFormat.format(moment);
String time = mOutputTimeFormat.format(moment);
String outputday = mDateFormat.format(moment);
// stolen directly from ChannelLog
String nickcolor = (String)mNickColors.get(nickname);
if (null == nickcolor)
{
nickcolor = ChannelLog.NICK_COLORS[mColorCounter++%ChannelLog.NICK_COLORS.length];
mNickColors.put(nickname, nickcolor);
}
mTemplate.setValue("bgcolor", nickcolor);
channelname = "#" + channelname;
// output a header if we're no longer in our little
// section of the world, i.e. date, server, channel, or
// bot changed
if (mCurrentDate == null || !mCurrentDate.equals(day) ||
mCurrentServername == null || !mCurrentServername.equals(servername) ||
mCurrentBotname == null || !mCurrentBotname.equals(botname) ||
mCurrentChannelname == null || !mCurrentChannelname.equals(channelname))
{
mTemplate.setValue("day", day);
mTemplate.setValue("botname", botname);
mTemplate.setValue("servername", servername);
mTemplate.setValue("channelname", channelname);
try
{
SearchBean bean = new SearchBean();
bean.setKeyword(submission.getKeyword());
bean.setChannel(botname + " - " + channelname);
bean.setDate(INPUT_DATE_FORMAT_SHORT.format(mDateFormat.parse(outputday)));
setNamedOutputBean("SearchBean", bean);
}
catch (ParseException e)
{
Logger
.getLogger("com.uwyn.drone.webui.elements.pub")
.severe(ExceptionUtils.getExceptionStackTrace(e));
}
setOutput("day", outputday);
setOutput("channelname", channelname);
setOutput("botname", botname);
setExitQuery(mTemplate, "show_channel_log", null, null);
mTemplate.setBlock("day_header", "day_header");
mCurrentDate = day;
mCurrentServername = servername;
mCurrentBotname = botname;
mCurrentChannelname = channelname;
}
else
{
mTemplate.setValue("day_header", "");
}
// output the message
mTemplate.setValue("time", time);
// translate the \u0001ACTION command which corresponds to
// /me so that the user's nickname is used instead
StringBuffer message_buf = new StringBuffer();
if (message.startsWith(ChannelLog.IRC_ACTION))
{
message_buf
.append(nickname)
.append(message.substring(ChannelLog.IRC_ACTION.length()));
}
else
{
message_buf.append(message);
}
String encoded_message = encodeHtml(message_buf.toString());
if (highlighter != null)
{
TokenStream tokenStream = new StandardAnalyzer().tokenStream("message", new StringReader(encoded_message));
String highlighted = highlighter.getBestFragments(tokenStream, encoded_message, 25, "...");
if (!highlighted.equals(""))
{
encoded_message = highlighted;
}
}
encoded_message = ChannelLog.convertUrl(encoded_message, ChannelLog.URL_HIGHLIGHT);
Matcher email_matcher = ChannelLog.EMAIL_HIGHLIGHT.matcher(encoded_message);
encoded_message = email_matcher.replaceAll("<a href=\"mailto:$1\">$1</a>");
mTemplate.setValue("nickname", nickname);
mTemplate.setValue("message", encoded_message);
mTemplate.appendBlock("messages", "message");
}
searcher.close();
reader.close();
}
catch (IOException e)
{
Logger
.getLogger("com.uwyn.drone.webui.elements.pub")
.severe(ExceptionUtils.getExceptionStackTrace(e));
}
// finalize the results
mTemplate.appendBlock("results", "results");
if (hits != null &&
hits.length() > LIMIT)
{
PagedNavigation.generateNavigation(this, mTemplate, hits.length(), LIMIT, offset, SPAN);
mTemplate.setBlock("ranged_table", "ranged_table");
}
}
generateForm(mTemplate, submission);
print(mTemplate);
}
public void doSearch()
{
//reset offset
setOutput("offset", 0);
clearNamedOutputBean("OffsetBean");
doSearch((SearchBean)getSubmissionBean(SearchBean.class), 0);
}
public static class SearchBean extends Validation
{
private String mKeyword = null;
private String mDate = null;
private String mBeginDate = null;
private String mEndDate = null;
private String mUser = null;
private String mChannel = null;
protected void activateValidation()
{
BotsRunner runner = BotsRunner.getRepInstance();
if (runner != null)
{
Collection bots = runner.getBots();
Iterator iter = bots.iterator();
List channels = new ArrayList();
while (iter.hasNext())
{
Bot bot = (Bot)iter.next();
Iterator channelsIter = bot.getJoinedChannels().iterator();
while (channelsIter.hasNext())
{
String name = bot.getName() + " - " + ((Channel)channelsIter.next()).getName();
channels.add(name);
}
}
new SortListComparables().sort(channels);
addConstraint(new ConstrainedProperty("channel")
.inList((String[])channels.toArray(new String[0])));
addConstraint(new ConstrainedProperty("keyword").notNull(true).notEmpty(true));
}
}
public boolean isValidSearch()
{
if (mKeyword == null ||
(mKeyword != null && mKeyword.equals("")))
{
addValidationError(new ValidationError.INVALID("keyword"));
return false;
}
return true;
}
public void setChannel(String channel) { mChannel = channel; }
public String getChannel() { return mChannel; }
public void setKeyword(String keyword) { mKeyword = keyword; }
public String getKeyword() { return mKeyword; }
public void setDate(String date) { mDate = date; }
public String getDate() { return mDate; }
public void setBeginDate(String beginDate) { mBeginDate = beginDate; }
public String getBeginDate() { return mBeginDate; }
public void setEndDate(String endDate) { mEndDate = endDate; }
public String getEndDate() { return mEndDate; }
public void setUser(String user) { mUser = user; }
public String getUser() { return mUser; }
}
}