/**
* Copyright (C) 2010 Peter Karich <jetwick_@_pannous_._info>
*
* 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 de.jetwick.ui;
import de.jetwick.es.JetwickQuery;
import org.elasticsearch.search.facet.Facet;
import org.elasticsearch.search.facet.Facets;
import org.elasticsearch.action.search.SearchResponse;
import de.jetwick.ui.util.FacetHelper;
import de.jetwick.ui.util.LabeledLink;
import de.jetwick.util.MapEntry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxFallbackLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
import org.elasticsearch.search.facet.filter.FilterFacet;
import org.elasticsearch.search.facet.terms.TermsFacet;
import static de.jetwick.tw.TweetDetector.*;
import static de.jetwick.es.ElasticTweetSearch.*;
/**
*
* @author Peter Karich, peat_hal 'at' users 'dot' sourceforge 'dot' net
*/
public class FacetPanel extends Panel {
private Map<String, String> tr = new LinkedHashMap<String, String>();
private Set<String> alreadyFiltered = new LinkedHashSet<String>();
private Set<String> alreadyExcluded = new LinkedHashSet<String>();
private List<Entry<String, List<FacetHelper>>> normalFacetFields = new ArrayList<Entry<String, List<FacetHelper>>>();
private ListView tagView;
public FacetPanel(String id) {
super(id);
// No Massive Tweeter
tr.put(USER, "Tweeters");
tr.put("loc", "Location");
tr.put(DATE, "Date");
tr.put(LANG, "Language");
tr.put(IS_RT, "Content");
tr.put(IS_RT + ":true", "retweet");
tr.put(IS_RT + ":false", "original");
tr.put(DUP_COUNT, "Duplicates");
tr.put(FILTER_NO_DUPS, "without");
tr.put(FILTER_ONLY_DUPS, "only duplicated");
tr.put(URL_COUNT, "Links");
tr.put(FILTER_URL_ENTRY, "with");
tr.put(FILTER_NO_URL_ENTRY, "without");
tr.put(RT_COUNT, "Retweets");
tr.put(RT_COUNT + ":[5 TO *]", "5 and more");
tr.put(RT_COUNT + ":[20 TO *]", "20 and more");
tr.put(RT_COUNT + ":[50 TO *]", "50 and more");
tr.put(QUALITY, "Spam");
tr.put(FILTER_NO_SPAM, "without");
tr.put(FILTER_SPAM, "only spam");
tr.put(LANG + ":" + UNKNOWN_LANG, "Other");
tr.put(LANG + ":" + DE, "Deutsch");
tr.put(LANG + ":" + EN, "English");
tr.put(LANG + ":" + NL, "Nederlandse");
tr.put(LANG + ":" + RU, "Pусский");
tr.put(LANG + ":" + ES, "Español");
// backward compatibility
tr.put(LANG + ":sp", "Español");
tr.put(LANG + ":" + FR, "Français");
tr.put(LANG + ":" + PT, "Português");
tagView = new ListView("filterNames", normalFacetFields) {
@Override
public void populateItem(final ListItem item) {
final Entry<String, List<FacetHelper>> entry = (Entry<String, List<FacetHelper>>) item.getModelObject();
String dtVal = translate(entry.getKey());
String filter = getFilterName(entry.getKey());
if (filter != null) {
item.add(new LabeledLink("filterName", "< " + dtVal) {
@Override
public void onClick(AjaxRequestTarget target) {
onRemoveAllFilter(target, entry.getKey());
}
}.add(new AttributeAppender("title", new Model("Remove all filters from '" + dtVal + "'"), " ")));
} else
item.add(new Label("filterName", dtVal));
item.add(new ListView("filterValues", entry.getValue()) {
@Override
protected void populateItem(ListItem li) {
final FacetHelper h = (FacetHelper) li.getModelObject();
final boolean selected = isAlreadyFiltered(h.getFilter());
final boolean excluded = isAlreadyExcluded(h.getFilter());
Link link = new IndicatingAjaxFallbackLink("filterValueLink") {
@Override
public void onClick(AjaxRequestTarget target) {
if (selected)
onFilterChange(target, h.key, h.value, null);
else
onFilterChange(target, h.key, h.value, false);
}
};
Link excludeLink = new IndicatingAjaxFallbackLink("filterExcludeLink") {
@Override
public void onClick(AjaxRequestTarget target) {
if (excluded)
onFilterChange(target, h.key, h.value, null);
else
onFilterChange(target, h.key, h.value, true);
}
};
li.add(new Label("filterCount", " (" + h.count + ")"));
li.add(link);
li.add(excludeLink);
/* exlude does not work for filter queries like RT_COUNT.contains(h.key)*/
if (USER.contains(h.key) || LANG.contains(h.key))
excludeLink.setVisible(true);
else
excludeLink.setVisible(false);
if (excluded)
link.add(new AttributeAppender("class", new Model("filter-ex"), " "));
// change style if filter is selected
if (selected)
link.add(new AttributeAppender("class", new Model("filter-rm"), " "));
else
link.add(new AttributeAppender("class", new Model("filter-add"), " "));
// TODO strike through if filter is excluded
link.add(new Label("filterValue", h.displayName));
// not clickable if filter would result in 0 docs
if (h.count == 0) {
link.setEnabled(false);
link.add(new AttributeAppender("class", new Model("gray"), " "));
}
}
});
}
};
add(tagView);
}
public String getFilterName(String name) {
name += ":";
for (String filter : alreadyFiltered) {
if (filter.startsWith(name)) {
return filter.substring(name.length());
}
}
name = "-" + name;
for (String filter : alreadyExcluded) {
if (filter.startsWith(name)) {
return filter.substring(name.length());
}
}
return null;
}
public void update(SearchResponse rsp, JetwickQuery query) {
normalFacetFields.clear();
if (rsp != null) {
for (Entry<String, List<FacetHelper>> entry : createFacetsFields(rsp)) {
if (entry != null) {
normalFacetFields.add(entry);
}
}
}
alreadyFiltered = new LinkedHashSet<String>();
alreadyExcluded = new LinkedHashSet<String>();
for (Entry<String, Object> e : query.getFilterQueries()) {
if (e.getKey().startsWith("-"))
alreadyExcluded.add(e.getKey() + ":" + e.getValue());
else
alreadyFiltered.add(e.getKey() + ":" + e.getValue());
}
}
public void onFilterChange(AjaxRequestTarget target, String key, Object val, Boolean selected) {
}
public void onRemoveAllFilter(AjaxRequestTarget target, String key) {
}
private boolean isAlreadyFiltered(String filter) {
return alreadyFiltered.contains(filter);
}
private boolean isAlreadyExcluded(String filter) {
return alreadyExcluded.contains(filter);
}
private String translate(String str) {
String val = tr.get(str);
if (val == null) {
int index = str.indexOf(USER + ":");
if (index >= 0)
return str.substring(index + USER.length() + 1);
return str;
}
return val;
}
/**
* Make sure that the facets appear in the order we defined via filterToIndex
*/
public List<Entry<String, List<FacetHelper>>> createFacetsFields(SearchResponse rsp) {
final int MAX_VAL = 7;
Map<String, Integer> filterToIndex = new LinkedHashMap<String, Integer>() {
{
put(LANG, 1);
put(USER, 2);
put(RT_COUNT, 3);
put(URL_COUNT, 4);
put(IS_RT, 5);
put(DUP_COUNT, 6);
put(QUALITY, 7);
}
};
List<Entry<String, List<FacetHelper>>> ret = new ArrayList<Entry<String, List<FacetHelper>>>();
for (int ii = 0; ii < MAX_VAL + 1; ii++) {
ret.add(null);
}
if (rsp == null)
return ret;
Facets facets = rsp.facets();
if (facets == null)
return ret;
for (Facet facet : facets.facets()) {
if (facet instanceof TermsFacet) {
TermsFacet ff = (TermsFacet) facet;
Integer integ = filterToIndex.get(ff.getName());
if (integ != null && ff.entries() != null) {
List<FacetHelper> list = new ArrayList<FacetHelper>();
String key = ff.getName();
for (TermsFacet.Entry e : ff.entries()) {
String term = e.getTerm();
if ("T".equals(term))
term = "true";
else if ("F".equals(term))
term = "false";
// exclude smaller zero?
list.add(new FacetHelper(key, term,
translate(key + ":" + term), e.getCount()));
}
ret.set(integ, new MapEntry(ff.getName(), list));
}
} else if (facet instanceof FilterFacet) {
FilterFacet ff = (FilterFacet) facet;
String name = ff.getName();
int firstIndex = name.indexOf(":");
if (firstIndex < 0)
continue;
String key = name.substring(0, firstIndex);
if (DATE.equals(key))
continue;
String val = name.substring(firstIndex + 1);
// exclude smaller zero?
Long count = ff.count();
if (count == null)
count = 0L;
Integer index = filterToIndex.get(key);
if (index == null)
continue;
Entry<String, List<FacetHelper>> facetEntry = ret.get(index);
List<FacetHelper> list;
if (facetEntry == null) {
facetEntry = new MapEntry(key, new ArrayList<FacetHelper>());
ret.set(index, facetEntry);
}
list = facetEntry.getValue();
list.add(new FacetHelper(key, val, translate(name), count));
}
}
return ret;
}
}