diff --git a/defaults/yacy.init b/defaults/yacy.init
index 92367510b..26bae7040 100644
--- a/defaults/yacy.init
+++ b/defaults/yacy.init
@@ -932,10 +932,12 @@ search.result.show.ranking = false
# search navigators: comma-separated list of default values for search navigation.
-# can be temporary different if search string is given with differen navigation values
+# By default navigators keys are sorted by descending counts. To sort by ascending displayed labels, add the :label suffix (example : hosts:label).
+# The sort direction can also be specified with the :asc or :desc suffixes (example : hosts:label:desc)
+# can be temporary different if search string is given with different navigation values
# assigning no value(s) means that no navigation is shown
search.navigation=location,hosts,authors,namespace,topics,filetype,protocol,language
-#search.navigation=location,hosts,authors,namespace,topics,filetype,protocol,language,collections,date,year,year:dates_in_content_dts:Event
+#search.navigation=location,hosts:label,authors,namespace,topics,filetype,protocol,language,collections,date,year,year:dates_in_content_dts:Event
# max number of items displayed in search navigators
search.navigation.maxcount=100
diff --git a/htroot/ConfigSearchPage_p.html b/htroot/ConfigSearchPage_p.html
index 05178410e..4d591002a 100644
--- a/htroot/ConfigSearchPage_p.html
+++ b/htroot/ConfigSearchPage_p.html
@@ -139,16 +139,36 @@
#{search.navigation.plugin}#
diff --git a/htroot/yacysearchtrailer.java b/htroot/yacysearchtrailer.java
index ae5db2ed1..119b8b259 100644
--- a/htroot/yacysearchtrailer.java
+++ b/htroot/yacysearchtrailer.java
@@ -44,6 +44,8 @@ import net.yacy.search.EventTracker;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
import net.yacy.search.navigator.Navigator;
+import net.yacy.search.navigator.NavigatorSortDirection;
+import net.yacy.search.navigator.NavigatorSortType;
import net.yacy.search.query.QueryParams;
import net.yacy.search.query.SearchEvent;
import net.yacy.search.query.SearchEventCache;
@@ -343,8 +345,28 @@ public class yacysearchtrailer {
prop.put("navs_" + ni + "_displayname", navi.getDisplayName());
prop.put("navs_" + ni + "_name", naviname);
prop.put("navs_" + ni + "_count", navi.size());
+
+ final int navSort;
+ if(navi.getSort() == null) {
+ navSort = 0;
+ } else {
+ if(navi.getSort().getSortType() == NavigatorSortType.COUNT) {
+ if(navi.getSort().getSortDir() == NavigatorSortDirection.DESC) {
+ navSort = 0;
+ } else {
+ navSort = 1;
+ }
+ } else {
+ if(navi.getSort().getSortDir() == NavigatorSortDirection.DESC) {
+ navSort = 2;
+ } else {
+ navSort = 3;
+ }
+ }
+ }
+ prop.put("navs_" + ni + "_navSort", navSort);
- navigatorIterator = navi.keys(false);
+ navigatorIterator = navi.navigatorKeys();
int i = 0, pos = 0, neg = 0;
String nav, rawNav;
while (i < theSearch.getQuery().getStandardFacetsMaxCount() && navigatorIterator.hasNext()) {
diff --git a/source/net/yacy/cora/federate/FederateSearchManager.java b/source/net/yacy/cora/federate/FederateSearchManager.java
index 132ecdd85..31204b663 100644
--- a/source/net/yacy/cora/federate/FederateSearchManager.java
+++ b/source/net/yacy/cora/federate/FederateSearchManager.java
@@ -222,7 +222,7 @@ public class FederateSearchManager {
this.switchboard.getRanking(),
"",//userAgent
0.0d, 0.0d, 0.0d,
- new String[0]);
+ new HashSet<>());
return query(query);
}
diff --git a/source/net/yacy/peers/Protocol.java b/source/net/yacy/peers/Protocol.java
index 537bda1de..cdf1ba15d 100644
--- a/source/net/yacy/peers/Protocol.java
+++ b/source/net/yacy/peers/Protocol.java
@@ -1296,7 +1296,7 @@ public final class Protocol {
// evaluate facets
if(useSolrFacets) {
- for (String field: event.query.facetfields) {
+ for (String field: event.query.facetfields.keySet()) {
FacetField facet = rsp[0].getFacetField(field);
ReversibleScoreMap result = new ClusteredScoreMap(UTF8.insensitiveUTF8Comparator);
List values = facet == null ? null : facet.getValues();
diff --git a/source/net/yacy/search/navigator/FileTypeNavigator.java b/source/net/yacy/search/navigator/FileTypeNavigator.java
index bf89438e0..576da21c1 100644
--- a/source/net/yacy/search/navigator/FileTypeNavigator.java
+++ b/source/net/yacy/search/navigator/FileTypeNavigator.java
@@ -38,8 +38,8 @@ import net.yacy.search.schema.CollectionSchema;
*/
public class FileTypeNavigator extends StringNavigator implements Navigator {
- public FileTypeNavigator(String title, CollectionSchema field) {
- super(title, field);
+ public FileTypeNavigator(final String title, final CollectionSchema field, final NavigatorSort sort) {
+ super(title, field, sort);
}
@Override
diff --git a/source/net/yacy/search/navigator/HostNavigator.java b/source/net/yacy/search/navigator/HostNavigator.java
index ab950799c..7b3dd6e86 100644
--- a/source/net/yacy/search/navigator/HostNavigator.java
+++ b/source/net/yacy/search/navigator/HostNavigator.java
@@ -34,9 +34,9 @@ import net.yacy.search.schema.CollectionSchema;
* www.host.org and host.org as same url
*/
public class HostNavigator extends StringNavigator implements Navigator {
-
- public HostNavigator(String title, CollectionSchema field) {
- super(title, field);
+
+ public HostNavigator(final String title, final CollectionSchema field, final NavigatorSort sort) {
+ super(title, field, sort);
}
@Override
diff --git a/source/net/yacy/search/navigator/LanguageNavigator.java b/source/net/yacy/search/navigator/LanguageNavigator.java
index 74d1b4152..1d12c2758 100644
--- a/source/net/yacy/search/navigator/LanguageNavigator.java
+++ b/source/net/yacy/search/navigator/LanguageNavigator.java
@@ -39,13 +39,11 @@ public class LanguageNavigator extends StringNavigator implements Navigator {
* Default constructor, using the default YaCy Solr field language_s.
*
* @param title the navigator display name
+ * @param sort the sort properties to apply when iterating over keys with the
+ * {@link #navigatorKeys()} function
*/
- public LanguageNavigator(String title) {
- super(title, CollectionSchema.language_s);
- }
-
- public LanguageNavigator(String title, CollectionSchema field) {
- super(title, field);
+ public LanguageNavigator(final String title, final NavigatorSort sort) {
+ super(title, CollectionSchema.language_s, sort);
}
/**
diff --git a/source/net/yacy/search/navigator/NameSpaceNavigator.java b/source/net/yacy/search/navigator/NameSpaceNavigator.java
index 20eca84f8..632c9121f 100644
--- a/source/net/yacy/search/navigator/NameSpaceNavigator.java
+++ b/source/net/yacy/search/navigator/NameSpaceNavigator.java
@@ -32,8 +32,8 @@ import net.yacy.kelondro.data.meta.URIMetadataNode;
*/
public class NameSpaceNavigator extends StringNavigator implements Navigator {
- public NameSpaceNavigator(String title) {
- super(title, null);
+ public NameSpaceNavigator(final String title, final NavigatorSort sort) {
+ super(title, null, sort);
}
@Override
diff --git a/source/net/yacy/search/navigator/Navigator.java b/source/net/yacy/search/navigator/Navigator.java
index ec8dc4feb..5a2b00d12 100644
--- a/source/net/yacy/search/navigator/Navigator.java
+++ b/source/net/yacy/search/navigator/Navigator.java
@@ -22,6 +22,7 @@
*/
package net.yacy.search.navigator;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -105,4 +106,16 @@ public interface Navigator extends ScoreMap {
* @param listener an eventual object which wants to listen to successful updates on this navigator score map
*/
public void setUpdatesListener(final ScoreMapUpdatesListener listener);
+
+ /**
+ * Creates and returns a sorted view of this navigator keys, sorted by the navigator order (for example by descending counts, or by ascending display names)
+ *
+ * @return an iterator accessing the navigator keys
+ */
+ public Iterator navigatorKeys();
+
+ /**
+ * @return the sort properties of the navigator
+ */
+ public NavigatorSort getSort();
}
diff --git a/source/net/yacy/search/navigator/NavigatorPlugins.java b/source/net/yacy/search/navigator/NavigatorPlugins.java
index ca5b1aeec..350c78e3d 100644
--- a/source/net/yacy/search/navigator/NavigatorPlugins.java
+++ b/source/net/yacy/search/navigator/NavigatorPlugins.java
@@ -22,18 +22,30 @@
*/
package net.yacy.search.navigator;
+import static net.yacy.search.query.SearchEvent.log;
+
+import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
+
import net.yacy.crawler.CrawlSwitchboard;
-import static net.yacy.search.query.SearchEvent.log;
import net.yacy.search.schema.CollectionSchema;
/**
* Class to create and manipulate search navigator plugin list
*/
public class NavigatorPlugins {
-
+
+ /**
+ * Separator for specific properties in each navigator configuration
+ */
+ public static final String NAV_PROPS_CONFIG_SEPARATOR = ":";
+
/**
* List of available navigators
* @return Map key=navigatorCfgname, value=std.DisplayName
@@ -51,24 +63,120 @@ public class NavigatorPlugins {
defaultnavplugins.put("keywords", "Keywords");
return defaultnavplugins;
}
+
+ /**
+ * @param navConfig a navigator configuration String
+ * @return the name identifying the navigator, or null when navConfig is null
+ */
+ public static final String getNavName(final String navConfig) {
+ String name = navConfig;
+ if(navConfig != null) {
+ final int navConfigPopsIndex = navConfig.indexOf(NAV_PROPS_CONFIG_SEPARATOR);
+ if(navConfigPopsIndex >= 0) {
+ name = navConfig.substring(0, navConfigPopsIndex);
+ }
+ }
+ return name;
+ }
+
+ /**
+ * @param navName a navigator name
+ * @return the default sort properties to apply for the given navigator
+ */
+ public static final NavigatorSort getDefaultSort(final String navName) {
+ if("year".equals(navName)) {
+ return NavigatorSort.LABEL_DESC;
+ }
+ return NavigatorSort.COUNT_DESC;
+ }
+
+ /**
+ *
+ * Parse a navigator configuration entry and return the sort properties to
+ * apply.
+ *
+ *
+ * Supported formats :
+ *
+ *
"navName" : descending sort by count values (except for the year navigator, where the default is by descending displayed labels)
+ *
"navName:count" : descending sort by count values
+ *
"navName:label" : ascending sort by displayed labels
+ *
"navName:count:asc" : ascending sort by count values
+ *
"navName:count:desc" : descending sort by count values
+ *
"navName:label:asc" : ascending sort by displayed labels
+ *
"navName:label:desc" : descending sort by displayed labels
+ *
+ *
+ *
+ * @param navConfig a navigator configuration String
+ * @return return the sort properties of the navigator
+ */
+ public static final NavigatorSort parseNavSortConfig(final String navConfig) {
+ return parseNavSortConfig(navConfig, getDefaultSort(getNavName(navConfig)));
+ }
+
+ /**
+ *
+ * Parse a navigator configuration entry and return the sort properties to
+ * apply.
+ *
+ *
+ * Supported formats :
+ *
+ *
"navName" : apply provided default sort/li>
+ *
"navName:count" : descending sort by count values
+ *
"navName:label" : ascending sort by displayed labels
+ *
"navName:count:asc" : ascending sort by count values
+ *
"navName:count:desc" : descending sort by count values
+ *
"navName:label:asc" : ascending sort by displayed labels
+ *
"navName:label:desc" : descending sort by displayed labels
+ *
+ *
+ *
+ * @param navConfig a navigator configuration String
+ * @param defaultSort the default sort properties to apply when the
+ * configuration String does not specify sort properties
+ * @return return the sort properties of the navigator
+ */
+ public static final NavigatorSort parseNavSortConfig(final String navConfig, final NavigatorSort defaultSort) {
+ if (navConfig == null) {
+ return defaultSort;
+ }
+ final Set navProperties = new HashSet<>();
+ Collections.addAll(navProperties, navConfig.split(NAV_PROPS_CONFIG_SEPARATOR));
+ NavigatorSort sort = defaultSort;
+ if (navProperties.contains(NavigatorSortType.LABEL.toString().toLowerCase(Locale.ROOT))) {
+ sort = NavigatorSort.LABEL_ASC; // default label sort
+ if (navProperties.contains(NavigatorSortDirection.DESC.toString().toLowerCase(Locale.ROOT))) {
+ sort = NavigatorSort.LABEL_DESC;
+ }
+ } if (navProperties.contains(NavigatorSortType.COUNT.toString().toLowerCase(Locale.ROOT))) {
+ sort = NavigatorSort.COUNT_DESC; // default count sort
+ if (navProperties.contains(NavigatorSortDirection.ASC.toString().toLowerCase(Locale.ROOT))) {
+ sort = NavigatorSort.COUNT_ASC;
+ }
+ }
+ return sort;
+ }
+
/**
- * Creates map of active search navigators from comma separated config string
- * @param navcfg comma separated string of navigator names
+ * Creates map of active search navigators from navigator config strings
+ * @param navConfigs navigator configuration strings
* @return map key=navigatorname, value=navigator.plugin reference
*/
- static public Map initFromCfgString(final String navcfg) {
+ public static Map initFromCfgStrings(final Set navConfigs) {
- LinkedHashMap navigatorPlugins = new LinkedHashMap();
- if (navcfg == null || navcfg.isEmpty()) return navigatorPlugins;
- String[] navnames = navcfg.split(",");
- for (String navname : navnames) {
- if (navname.contains("authors")) {
- navigatorPlugins.put("authors", new StringNavigator("Authors", CollectionSchema.author_sxt));
- }
-
- if (navname.contains("collections")) {
- RestrictedStringNavigator tmpnav = new RestrictedStringNavigator("Collection", CollectionSchema.collection_sxt);
+ final LinkedHashMap navigatorPlugins = new LinkedHashMap<>();
+ if(navConfigs == null) {
+ return navigatorPlugins;
+ }
+ for (final String navConfig : navConfigs) {
+ final String navName = getNavName(navConfig);
+ if ("authors".equals(navName)) {
+ navigatorPlugins.put("authors", new StringNavigator("Authors", CollectionSchema.author_sxt, parseNavSortConfig(navConfig)));
+ } else if ("collections".equals(navName)) {
+ RestrictedStringNavigator tmpnav = new RestrictedStringNavigator("Collection", CollectionSchema.collection_sxt, parseNavSortConfig(navConfig));
// exclude default internal collection names
tmpnav.addForbidden("dht");
tmpnav.addForbidden("robot_" + CrawlSwitchboard.CRAWL_PROFILE_AUTOCRAWL_DEEP);
@@ -82,46 +190,47 @@ public class NavigatorPlugins {
tmpnav.addForbidden("robot_" + CrawlSwitchboard.CRAWL_PROFILE_SNIPPET_GLOBAL_MEDIA);
tmpnav.addForbidden("robot_" + CrawlSwitchboard.CRAWL_PROFILE_SURROGATE);
navigatorPlugins.put("collections", tmpnav);
- }
-
- if (navname.contains("filetype")) {
- navigatorPlugins.put("filetype", new FileTypeNavigator("Filetype", CollectionSchema.url_file_ext_s));
- }
-
- if (navname.contains("hosts")) {
- navigatorPlugins.put("hosts", new HostNavigator("Provider", CollectionSchema.host_s));
- }
-
- if (navname.contains("language")) {
- navigatorPlugins.put("language", new LanguageNavigator("Language"));
- }
-
- if (navname.contains("namespace")) {
- navigatorPlugins.put("namespace", new NameSpaceNavigator("Wiki Name Space"));
- }
-
- // YearNavigator with possible def of :fieldname:title in configstring
- if (navname.contains("year")) {
- if ((navname.indexOf(':')) > 0) { // example "year:dates_in_content_dts:Events"
- String[] navfielddef = navname.split(":");
- try {
- // year:fieldname:title
- CollectionSchema field = CollectionSchema.valueOf(navfielddef[1]);
- if (navfielddef.length > 2) {
- navigatorPlugins.put(navfielddef[1], new YearNavigator(navfielddef[2], field));
- } else {
- navigatorPlugins.put(navfielddef[1], new YearNavigator("Year-" + navfielddef[1], field));
- }
- } catch (java.lang.IllegalArgumentException ex) {
- log.severe("wrong navigator name in config: \"" + navname + "\" " + ex.getMessage());
- }
- } else { // "year" only use default last_modified
- navigatorPlugins.put("year", new YearNavigator("Year", CollectionSchema.last_modified));
+ } else if ("filetype".equals(navName)) {
+ navigatorPlugins.put("filetype", new FileTypeNavigator("Filetype", CollectionSchema.url_file_ext_s,
+ parseNavSortConfig(navConfig)));
+ } else if("hosts".equals(navName)) {
+ navigatorPlugins.put("hosts",
+ new HostNavigator("Provider", CollectionSchema.host_s, parseNavSortConfig(navConfig)));
+ } else if ("language".equals(navName)) {
+ navigatorPlugins.put("language", new LanguageNavigator("Language", parseNavSortConfig(navConfig)));
+ } else if ("namespace".equals(navName)) {
+ navigatorPlugins.put("namespace", new NameSpaceNavigator("Wiki Name Space", parseNavSortConfig(navConfig)));
+ } else if ("year".equals(navName)) {
+ // YearNavigator with possible def of :fieldname:title in configstring
+ final LinkedHashSet navProperties = new LinkedHashSet<>();
+ Collections.addAll(navProperties, navConfig.split(NAV_PROPS_CONFIG_SEPARATOR));
+
+ /* Remove sort related properties */
+ for(final NavigatorSortType sortType : NavigatorSortType.values()) {
+ navProperties.remove(sortType.toString().toLowerCase(Locale.ROOT));
+ }
+ for(final NavigatorSortDirection sortDir : NavigatorSortDirection.values()) {
+ navProperties.remove(sortDir.toString().toLowerCase(Locale.ROOT));
+ }
+ final String[] navfielddef = navProperties.toArray(new String[navProperties.size()]);
+
+ if (navfielddef.length > 1) {
+ try {
+ // year:fieldname:title
+ CollectionSchema field = CollectionSchema.valueOf(navfielddef[1]);
+ if (navfielddef.length > 2) {
+ navigatorPlugins.put(navfielddef[1], new YearNavigator(navfielddef[2], field, parseNavSortConfig(navConfig)));
+ } else {
+ navigatorPlugins.put(navfielddef[1], new YearNavigator("Year-" + navfielddef[1], field, parseNavSortConfig(navConfig)));
+ }
+ } catch (final java.lang.IllegalArgumentException ex) {
+ log.severe("wrong navigator name in config: \"" + navConfig + "\" " + ex.getMessage());
+ }
+ } else { // "year" only use default last_modified
+ navigatorPlugins.put("year", new YearNavigator("Year", CollectionSchema.last_modified, parseNavSortConfig(navConfig)));
}
- }
-
- if (navname.contains("keywords")) {
- navigatorPlugins.put("keywords", new TokenizedStringNavigator("Keywords", CollectionSchema.keywords));
+ } else if ("keywords".equals(navName)) {
+ navigatorPlugins.put("keywords", new TokenizedStringNavigator("Keywords", CollectionSchema.keywords, parseNavSortConfig(navConfig)));
}
}
return navigatorPlugins;
diff --git a/source/net/yacy/search/navigator/NavigatorSort.java b/source/net/yacy/search/navigator/NavigatorSort.java
new file mode 100644
index 000000000..2f86b1033
--- /dev/null
+++ b/source/net/yacy/search/navigator/NavigatorSort.java
@@ -0,0 +1,98 @@
+// NavigatorSort.java
+// ---------------------------
+// Copyright 2019 by luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// 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 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
+
+package net.yacy.search.navigator;
+
+/**
+ * Enumeration of navigator sort properties.
+ */
+public enum NavigatorSort {
+
+ /**
+ * Descending sort on count values.
+ */
+ COUNT_DESC(NavigatorSortType.COUNT, NavigatorSortDirection.DESC),
+
+ /**
+ * Ascending sort on count values.
+ */
+ COUNT_ASC(NavigatorSortType.COUNT, NavigatorSortDirection.ASC),
+
+ /**
+ * Descending sort on displayed labels.
+ */
+ LABEL_DESC(NavigatorSortType.LABEL, NavigatorSortDirection.DESC),
+
+ /**
+ * Ascending sort on displayed labels.
+ */
+ LABEL_ASC(NavigatorSortType.LABEL, NavigatorSortDirection.ASC);
+
+ /**
+ * The type of sort to apply when iterating over a navigator keys with the
+ * {@link Navigator#navigatorKeys()} function
+ */
+ private final NavigatorSortType sortType;
+
+ /**
+ * The direction of the sort to apply when iterating over a navigator keys with
+ * the {@link Navigator#navigatorKeys()} function
+ */
+ private final NavigatorSortDirection sortDir;
+
+ /**
+ * @param sortType The type of sort to apply when iterating over a navigator
+ * keys with the {@link Navigator#navigatorKeys(boolean)}
+ * function
+ * @param sortDir The direction of the sort to apply when iterating over a
+ * navigator keys with the
+ * {@link Navigator#navigatorKeys(boolean)} function
+ */
+ private NavigatorSort(final NavigatorSortType sortType, final NavigatorSortDirection sortDir) {
+ if (sortType == null) {
+ this.sortType = NavigatorSortType.COUNT;
+ } else {
+ this.sortType = sortType;
+ }
+ if (sortDir == null) {
+ this.sortDir = NavigatorSortDirection.DESC;
+ } else {
+ this.sortDir = sortDir;
+ }
+ }
+
+ /**
+ * @return The type of sort to apply when iterating over a navigator keys with
+ * the {@link Navigator#navigatorKeys(boolean)} function
+ */
+ public NavigatorSortType getSortType() {
+ return this.sortType;
+ }
+
+ /**
+ * @return The direction of the sort to apply when iterating over a navigator
+ * keys with the {@link Navigator#navigatorKeys(boolean)} function
+ */
+ public NavigatorSortDirection getSortDir() {
+ return this.sortDir;
+ }
+}
diff --git a/source/net/yacy/search/navigator/NavigatorSortDirection.java b/source/net/yacy/search/navigator/NavigatorSortDirection.java
new file mode 100644
index 000000000..07a0bc843
--- /dev/null
+++ b/source/net/yacy/search/navigator/NavigatorSortDirection.java
@@ -0,0 +1,33 @@
+// NavigatorSortDirection.java
+// ---------------------------
+// Copyright 2019 by luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// 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 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
+
+package net.yacy.search.navigator;
+
+/**
+ * Enumeration of sort directions for navigators.
+ */
+public enum NavigatorSortDirection {
+ /** Ascending order */
+ ASC,
+ /** Descending order */
+ DESC
+}
diff --git a/source/net/yacy/search/navigator/NavigatorSortType.java b/source/net/yacy/search/navigator/NavigatorSortType.java
new file mode 100644
index 000000000..f14e22709
--- /dev/null
+++ b/source/net/yacy/search/navigator/NavigatorSortType.java
@@ -0,0 +1,33 @@
+// NavigatorSortType.java
+// ---------------------------
+// Copyright 2019 by luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// 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 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
+
+package net.yacy.search.navigator;
+
+/**
+ * Enumeration of types of sort for navigators.
+ */
+public enum NavigatorSortType {
+ /** Sort by navigator displayed labels */
+ LABEL,
+ /** Sort by navigator counts */
+ COUNT
+}
diff --git a/source/net/yacy/search/navigator/RestrictedStringNavigator.java b/source/net/yacy/search/navigator/RestrictedStringNavigator.java
index d6d44a3bc..bac5f31e0 100644
--- a/source/net/yacy/search/navigator/RestrictedStringNavigator.java
+++ b/source/net/yacy/search/navigator/RestrictedStringNavigator.java
@@ -37,8 +37,8 @@ public class RestrictedStringNavigator extends StringNavigator implements Naviga
Set allowed; // complete list of keys, if empty all keys are allowed
Set forbidden; // keys to exclude
- public RestrictedStringNavigator(String title, CollectionSchema field) {
- super(title, field);
+ public RestrictedStringNavigator(final String title, final CollectionSchema field, final NavigatorSort sort) {
+ super(title, field, sort);
this.allowed = new HashSet();
this.forbidden = new HashSet();
}
diff --git a/source/net/yacy/search/navigator/StringNavigator.java b/source/net/yacy/search/navigator/StringNavigator.java
index c5c122251..9eea57d70 100644
--- a/source/net/yacy/search/navigator/StringNavigator.java
+++ b/source/net/yacy/search/navigator/StringNavigator.java
@@ -22,9 +22,14 @@
*/
package net.yacy.search.navigator;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+
import net.yacy.cora.sorting.ConcurrentScoreMap;
import net.yacy.cora.sorting.ReversibleScoreMap;
import net.yacy.kelondro.data.meta.URIMetadataNode;
@@ -35,16 +40,41 @@ import net.yacy.search.schema.CollectionSchema;
* Search navigator for simple string entries based on ScoreMap to count and
* order the result list by counted occurence
*/
-public class StringNavigator extends ConcurrentScoreMap implements Navigator {
+public class StringNavigator extends ConcurrentScoreMap implements Navigator {
public String title;
protected final CollectionSchema field;
+
+ /**
+ * The sort properties to apply when iterating over keys with the
+ * {@link #navigatorKeys()} function
+ */
+ private final NavigatorSort sort;
+
+ /**
+ * Constructor applying a descending sort by counts as defaut.
+ * @param title the navigator title
+ * @param field the indexed field to count
+ */
+ public StringNavigator(final String title, final CollectionSchema field) {
+ this(title, field, NavigatorSort.COUNT_DESC);
+ }
- public StringNavigator(String title, CollectionSchema field) {
- super();
- this.title = title;
- this.field = field;
- }
+ /**
+ * @param title the navigator title
+ * @param field the indexed field to count
+ * @param sort the sort properties to apply when iterating over keys with the
+ * {@link #navigatorKeys()} function
+ */
+ public StringNavigator(final String title, final CollectionSchema field, final NavigatorSort sort) {
+ this.title = title;
+ this.field = field;
+ if(sort == null) {
+ this.sort = NavigatorSort.COUNT_DESC;
+ } else {
+ this.sort = sort;
+ }
+ }
@Override
public String getDisplayName() {
@@ -153,4 +183,26 @@ public class StringNavigator extends ConcurrentScoreMap implements Navi
}
return "";
}
+
+ @Override
+ public Iterator navigatorKeys() {
+ if(this.sort.getSortType() == NavigatorSortType.LABEL) {
+ final ArrayList keys = new ArrayList<>(this.map.keySet());
+
+ Comparator keyComparator = Comparator.comparing(this::getElementDisplayName);
+ if(this.sort.getSortDir() == NavigatorSortDirection.DESC) {
+ keyComparator = keyComparator.reversed();
+ }
+ Collections.sort(keys, keyComparator);
+
+
+ return keys.iterator();
+ }
+ return keys(this.sort.getSortDir() == NavigatorSortDirection.ASC);
+ }
+
+ @Override
+ public NavigatorSort getSort() {
+ return this.sort;
+ }
}
diff --git a/source/net/yacy/search/navigator/TokenizedStringNavigator.java b/source/net/yacy/search/navigator/TokenizedStringNavigator.java
index cd8325a01..4e25f9880 100644
--- a/source/net/yacy/search/navigator/TokenizedStringNavigator.java
+++ b/source/net/yacy/search/navigator/TokenizedStringNavigator.java
@@ -36,8 +36,8 @@ import net.yacy.search.schema.CollectionSchema;
*/
public class TokenizedStringNavigator extends StringNavigator implements Navigator {
- public TokenizedStringNavigator(String title, CollectionSchema field) {
- super(title, field);
+ public TokenizedStringNavigator(String title, CollectionSchema field, final NavigatorSort sort) {
+ super(title, field, sort);
}
/**
diff --git a/source/net/yacy/search/navigator/YearNavigator.java b/source/net/yacy/search/navigator/YearNavigator.java
index 30eef72e0..cb7975fd7 100644
--- a/source/net/yacy/search/navigator/YearNavigator.java
+++ b/source/net/yacy/search/navigator/YearNavigator.java
@@ -24,11 +24,9 @@ package net.yacy.search.navigator;
import java.util.Calendar;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
-import java.util.TreeSet;
import net.yacy.cora.federate.solr.SolrType;
import net.yacy.cora.sorting.ReversibleScoreMap;
import net.yacy.kelondro.data.meta.URIMetadataNode;
@@ -54,8 +52,8 @@ import net.yacy.search.schema.CollectionSchema;
*/
public class YearNavigator extends StringNavigator implements Navigator {
- public YearNavigator(String title, CollectionSchema field) {
- super(title, field);
+ public YearNavigator(final String title, final CollectionSchema field, final NavigatorSort sort) {
+ super(title, field, sort == null ? NavigatorSort.LABEL_DESC : sort);
if (field.getType() != SolrType.date) throw new IllegalArgumentException("field is not of type Date");
}
@@ -119,32 +117,6 @@ public class YearNavigator extends StringNavigator implements Navigator {
}
}
- /**
- * YearNavigator returns keys in asc or desc order instead of ordered by
- * score
- *
- * @param up true = asc
- * @return key alphabetically ordered
- */
- @Override
- public Iterator keys(boolean up) {
- TreeSet years;
- if (up) {
- years = new TreeSet();
- } else {
- years = new TreeSet(Collections.reverseOrder());
- }
-
- // make sure keys with high score are included (display may be limited in size)
- // Iterator it = this.iterator();
- Iterator it = super.keys(false);
-
- while (it.hasNext()) {
- years.add(it.next());
- }
- return years.iterator();
- }
-
/**
* For date_in_content_dts it return true if form:YEAR and to:YEAR is part
diff --git a/source/net/yacy/search/query/QueryParams.java b/source/net/yacy/search/query/QueryParams.java
index 325123dc9..770d769f8 100644
--- a/source/net/yacy/search/query/QueryParams.java
+++ b/source/net/yacy/search/query/QueryParams.java
@@ -34,10 +34,10 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.regex.Matcher;
@@ -77,6 +77,8 @@ import net.yacy.kelondro.util.Bitfield;
import net.yacy.kelondro.util.SetTools;
import net.yacy.peers.Seed;
import net.yacy.search.index.Segment;
+import net.yacy.search.navigator.NavigatorPlugins;
+import net.yacy.search.navigator.NavigatorSort;
import net.yacy.search.ranking.RankingProfile;
import net.yacy.search.schema.CollectionConfiguration;
import net.yacy.search.schema.CollectionSchema;
@@ -89,6 +91,18 @@ public final class QueryParams {
/** The default maximum number of date elements in the date navigator */
public static final int FACETS_DATE_MAXCOUNT_DEFAULT = 640;
+ /**
+ * The Solr facet limit to apply when resorting or filtering is done in a YaCy
+ * search navigator. For example sort by ascending counts or by descending
+ * indexed terms are not supported with Solr 6.6 (but should be on Solr 7 JSON
+ * Facet API - see
+ * https://lucene.apache.org/solr/guide/7_6/json-facet-api.html). The limit
+ * defined here is set large enough so that resorting can be done by the YaCy
+ * Navigator itself. We don't set the facet to unlimited to prevent a too high
+ * memory usage.
+ */
+ private static final int FACETS_MAXCOUNT_FOR_RESORT_ON_SEARCH_NAV = 100000;
+
public enum Searchdom {
LOCAL, CLUSTER, GLOBAL;
@@ -188,7 +202,9 @@ public final class QueryParams {
public long searchtime, urlretrievaltime, snippetcomputationtime; // time to perform the search, to get all the urls, and to compute the snippets
public final String userAgent;
protected double lat, lon, radius;
- public LinkedHashSet facetfields;
+
+ /** Map from facet/navigator name to sort properties */
+ public Map facetfields;
private SolrQuery cachedQuery;
private CollectionConfiguration solrSchema;
public final int timezoneOffset;
@@ -228,7 +244,7 @@ public final class QueryParams {
final double lat,
final double lon,
final double radius,
- final String[] search_navigation
+ final Set navConfigs
) {
this.queryGoal = queryGoal;
this.modifier = modifier;
@@ -286,7 +302,7 @@ public final class QueryParams {
this.snippetCacheStrategy = snippetCacheStrategy;
this.clienthost = host;
this.remotepeer = null;
- this.starttime = Long.valueOf(System.currentTimeMillis());
+ this.starttime = System.currentTimeMillis();
this.maxtime = 10000;
this.indexSegment = indexSegment;
this.userAgent = userAgent;
@@ -296,23 +312,23 @@ public final class QueryParams {
this.lat = Math.floor(lat * this.kmNormal) / this.kmNormal;
this.lon = Math.floor(lon * this.kmNormal) / this.kmNormal;
this.radius = Math.floor(radius * this.kmNormal + 1) / this.kmNormal;
- this.facetfields = new LinkedHashSet();
+ this.facetfields = new HashMap<>();
this.solrSchema = indexSegment.fulltext().getDefaultConfiguration();
- for (String navkey: search_navigation) {
- CollectionSchema f = defaultfacetfields.get(navkey);
+ for (final String navConfig: navConfigs) {
+ CollectionSchema f = defaultfacetfields.get(NavigatorPlugins.getNavName(navConfig));
// handle special field, authors_sxt (add to facet w/o contains check, as authors_sxt is not enabled (is copyfield))
// dto. for coordinate_p_0_coordinate is not enabled but used for location facet (because coordinate_p not valid for facet field)
- if (f != null && (solrSchema.contains(f) || f.name().equals("author_sxt") || f.name().equals("coordinate_p_0_coordinate") ))
- this.facetfields.add(f.getSolrFieldName());
+ if (f != null && (solrSchema.contains(f) || f == CollectionSchema.author_sxt || f == CollectionSchema.coordinate_p_0_coordinate))
+ this.facetfields.put(f.getSolrFieldName(), NavigatorPlugins.parseNavSortConfig(navConfig));
}
if (LibraryProvider.autotagging != null) for (Tagging v: LibraryProvider.autotagging.getVocabularies()) {
if (v.isFacet()) {
- this.facetfields.add(CollectionSchema.VOCABULARY_PREFIX + v.getName() + CollectionSchema.VOCABULARY_TERMS_SUFFIX);
+ this.facetfields.put(CollectionSchema.VOCABULARY_PREFIX + v.getName() + CollectionSchema.VOCABULARY_TERMS_SUFFIX, NavigatorSort.COUNT_DESC);
}
}
for (String context: ProbabilisticClassifier.getContextNames()) {
- this.facetfields.add(CollectionSchema.VOCABULARY_PREFIX + context + CollectionSchema.VOCABULARY_TERMS_SUFFIX);
+ this.facetfields.put(CollectionSchema.VOCABULARY_PREFIX + context + CollectionSchema.VOCABULARY_TERMS_SUFFIX, NavigatorSort.COUNT_DESC);
}
this.cachedQuery = null;
this.standardFacetsMaxCount = FACETS_STANDARD_MAXCOUNT_DEFAULT;
@@ -686,6 +702,61 @@ public final class QueryParams {
return params;
}
+ /**
+ * Fill the Solr parameters with the relevant values to apply the search
+ * navigator sort properties.
+ *
+ * @param params the Solr parameters to modify
+ * @param a Solr field name
+ * @param navSort navigator sort properties to apply
+ */
+ private void fillSolrParamWithNavSort(final SolrQuery params, final String solrFieldName,
+ final NavigatorSort navSort) {
+ if (params != null && solrFieldName != null && navSort != null) {
+ switch (navSort) {
+ case COUNT_ASC:
+ params.setParam("f." + solrFieldName + ".facet.sort", FacetParams.FACET_SORT_COUNT);
+ /*
+ * Ascending count is not supported with Solr 6.6 (but should be on Solr 7 JSON
+ * Facet API https://lucene.apache.org/solr/guide/7_6/json-facet-api.html) So we
+ * use a here a high limit and ascending resorting will be done by YaCy
+ * Navigator
+ */
+ params.setParam("f." + solrFieldName + ".facet.limit",
+ String.valueOf(FACETS_MAXCOUNT_FOR_RESORT_ON_SEARCH_NAV));
+ break;
+ case LABEL_DESC:
+ /*
+ * Descending index order is not supported with Solr 6.6 (but should be on Solr
+ * 7 JSON Facet API
+ * https://lucene.apache.org/solr/guide/7_6/json-facet-api.html) So we use a
+ * here a high limit and descending resorting will be done by YaCy Navigator
+ */
+ params.setParam("f." + solrFieldName + ".facet.sort", FacetParams.FACET_SORT_INDEX);
+ params.setParam("f." + solrFieldName + ".facet.limit",
+ String.valueOf(FACETS_MAXCOUNT_FOR_RESORT_ON_SEARCH_NAV));
+ break;
+ case LABEL_ASC:
+ params.setParam("f." + solrFieldName + ".facet.sort", FacetParams.FACET_SORT_INDEX);
+ break;
+ default:
+ /* Nothing to add for COUNT_DESC which is the default for Solr */
+ break;
+ }
+
+ if (CollectionSchema.language_s.getSolrFieldName().equals(solrFieldName)
+ || CollectionSchema.url_file_ext_s.getSolrFieldName().equals(solrFieldName)
+ || CollectionSchema.collection_sxt.getSolrFieldName().equals(solrFieldName)) {
+ /*
+ * For these search navigators additional filtering or resorting is done in the navigator itself.
+ * So we use a here a high limit so that the navigator apply its rules without missing elements.
+ */
+ params.setParam("f." + solrFieldName + ".facet.limit",
+ String.valueOf(FACETS_MAXCOUNT_FOR_RESORT_ON_SEARCH_NAV));
+ }
+ }
+ }
+
private SolrQuery getBasicParams(final boolean getFacets, final List fqs) {
final SolrQuery params = new SolrQuery();
params.setParam("defType", "edismax");
@@ -713,15 +784,19 @@ public final class QueryParams {
params.setFacetLimit(this.standardFacetsMaxCount);
params.setFacetSort(FacetParams.FACET_SORT_COUNT);
params.setParam(FacetParams.FACET_METHOD, FacetParams.FACET_METHOD_enum); // fight the fieldcache
- for (String field: this.facetfields) params.addFacetField("{!ex=" + field + "}" + field); // params.addFacetField("{!ex=" + field + "}" + field);
- if (this.facetfields.contains(CollectionSchema.dates_in_content_dts.name())) {
+ for (final Entry entry : this.facetfields.entrySet()) {
+ params.addFacetField("{!ex=" + entry.getKey() + "}" + entry.getKey()); // params.addFacetField("{!ex=" + field + "}" + field);
+ fillSolrParamWithNavSort(params, entry.getKey(), entry.getValue());
+ }
+ final NavigatorSort datesInContentSort = this.facetfields.get(CollectionSchema.dates_in_content_dts.name());
+ if (datesInContentSort != null) {
params.setParam(FacetParams.FACET_RANGE, CollectionSchema.dates_in_content_dts.name());
String start = new Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 3).toInstant().toString();
String end = new Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 3).toInstant().toString();
params.setParam("f." + CollectionSchema.dates_in_content_dts.getSolrFieldName() + ".facet.range.start", start);
params.setParam("f." + CollectionSchema.dates_in_content_dts.getSolrFieldName() + ".facet.range.end", end);
params.setParam("f." + CollectionSchema.dates_in_content_dts.getSolrFieldName() + ".facet.range.gap", "+1DAY");
- params.setParam("f." + CollectionSchema.dates_in_content_dts.getSolrFieldName() + ".facet.sort", "index");
+ fillSolrParamWithNavSort(params, CollectionSchema.dates_in_content_dts.getSolrFieldName(), datesInContentSort);
params.setParam("f." + CollectionSchema.dates_in_content_dts.getSolrFieldName() + ".facet.limit", Integer.toString(this.dateFacetMaxCount)); // the year constraint should cause that limitation already
}
//for (String k: params.getParameterNames()) {ArrayList al = new ArrayList<>(); for (String s: params.getParams(k)) al.add(s); System.out.println("Parameter: " + k + "=" + al.toString());}
diff --git a/source/net/yacy/search/query/SearchEvent.java b/source/net/yacy/search/query/SearchEvent.java
index 6ea109cc7..742c23ea0 100644
--- a/source/net/yacy/search/query/SearchEvent.java
+++ b/source/net/yacy/search/query/SearchEvent.java
@@ -352,15 +352,34 @@ public final class SearchEvent implements ScoreMapUpdatesListener {
this.maxExpectedRemoteReferences = new AtomicInteger(0);
this.expectedRemoteReferences = new AtomicInteger(0);
this.excludeintext_image = Switchboard.getSwitchboard().getConfigBool("search.excludeintext.image", true);
+
// prepare configured search navigation
- final String navcfg = Switchboard.getSwitchboard().getConfig("search.navigation", "");
- this.locationNavigator = navcfg.contains("location") ? new ConcurrentScoreMap(this) : null;
- this.protocolNavigator = navcfg.contains("protocol") ? new ConcurrentScoreMap(this) : null;
- this.dateNavigator = navcfg.contains("date") ? new ConcurrentScoreMap(this) : null;
- this.topicNavigatorCount = navcfg.contains("topics") ? MAX_TOPWORDS : 0;
+ final Set navConfigs = Switchboard.getSwitchboard().getConfigSet("search.navigation");
+
+ boolean locationNavEnabled = false;
+ boolean protocolNavEnabled = false;
+ boolean topicsNavEnabled = false;
+ boolean dateNavEnabled = false;
+ for(final String navConfig : navConfigs) {
+ final String navName = NavigatorPlugins.getNavName(navConfig);
+ if("location".equals(navName)) {
+ locationNavEnabled = true;
+ } else if("protocol".equals(navName)) {
+ protocolNavEnabled = true;
+ } else if("topics".equals(navName)) {
+ topicsNavEnabled = true;
+ } else if("date".equals(navName)) {
+ dateNavEnabled = true;
+ }
+ }
+
+ this.locationNavigator = locationNavEnabled ? new ConcurrentScoreMap<>(this) : null;
+ this.protocolNavigator = protocolNavEnabled ? new ConcurrentScoreMap<>(this) : null;
+ this.dateNavigator = dateNavEnabled ? new ConcurrentScoreMap<>(this) : null;
+ this.topicNavigatorCount = topicsNavEnabled ? MAX_TOPWORDS : 0;
this.vocabularyNavigator = new TreeMap>();
// prepare configured search navigation (plugins)
- this.navigatorPlugins = NavigatorPlugins.initFromCfgString(navcfg);
+ this.navigatorPlugins = NavigatorPlugins.initFromCfgStrings(navConfigs);
if(this.navigatorPlugins != null) {
for(final Navigator nav : this.navigatorPlugins.values()) {
nav.setUpdatesListener(this);
diff --git a/source/net/yacy/server/serverSwitch.java b/source/net/yacy/server/serverSwitch.java
index 69fabc3e0..817f3d14d 100644
--- a/source/net/yacy/server/serverSwitch.java
+++ b/source/net/yacy/server/serverSwitch.java
@@ -446,8 +446,7 @@ public class serverSwitch {
/**
* get a configuration parameter set
- * @param key
- * @param dflt a default list
+ * @param key name of the configuration parameter
* @return a set of strings which had been separated by comma in the setting
*/
public Set getConfigSet(final String key) {