aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/query/properties
diff options
context:
space:
mode:
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/query/properties')
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/DefaultProperties.java40
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java58
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java132
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java296
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/QueryPropertyAliases.java33
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/RequestContextProperties.java41
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/SubProperties.java67
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/package-info.java7
8 files changed, 674 insertions, 0 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/DefaultProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/DefaultProperties.java
new file mode 100644
index 00000000000..01c861b879e
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/DefaultProperties.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.query.Properties;
+import com.yahoo.search.query.profile.types.FieldDescription;
+import com.yahoo.search.query.profile.types.QueryProfileType;
+
+import java.util.Map;
+
+/**
+ * Default values for properties that are meant to be customized in query profiles.
+ * @author tonytv
+ */
+public final class DefaultProperties extends Properties {
+ public static final CompoundName MAX_OFFSET = new CompoundName("maxOffset");
+ public static final CompoundName MAX_HITS = new CompoundName("maxHits");
+
+
+ public static final QueryProfileType argumentType = new QueryProfileType("DefaultProperties");
+ static {
+ argumentType.setBuiltin(true);
+
+ argumentType.addField(new FieldDescription(MAX_OFFSET.toString(), "integer"));
+ argumentType.addField(new FieldDescription(MAX_HITS.toString(), "integer"));
+
+ argumentType.freeze();
+ }
+
+ @Override
+ public Object get(CompoundName name, Map<String, String> context, com.yahoo.processing.request.Properties substitution) {
+ if (MAX_OFFSET.equals(name)) {
+ return 1000;
+ } else if (MAX_HITS.equals(name)) {
+ return 400;
+ } else {
+ return super.get(name, context, substitution);
+ }
+ }
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java
new file mode 100644
index 00000000000..cc2c08c5504
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.query.Properties;
+
+import java.util.Map;
+
+/**
+ * A properties implementation which translates the incoming name to its standard name
+ * if it is a registered alias.
+ * <p>
+ * Aliases are case insensitive. One standard name may have multiple aliases.
+ * <p>
+ * This is multithread safe or not depending on the status of the passed map of aliases.
+ * Cloning will not deep copy the set of aliases.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class PropertyAliases extends Properties {
+
+ /** A map from aliases to standard names */
+ private final Map<String,CompoundName> aliases;
+
+ /**
+ * Creates an instance with a set of aliases. The given aliases will be used directly by this class.
+ * To make this class immutable and thread safe, relinquish ownership of the parameter map.
+ */
+ public PropertyAliases(Map<String,CompoundName> aliases) {
+ this.aliases=aliases;
+ }
+
+ /**
+ * Returns the standard name for an alias, or the given name if it is not a registered alias
+ *
+ * @param nameOrAlias the name to check if is an alias
+ * @return the real name if an alias or the input name itself
+ */
+ protected CompoundName unalias(CompoundName nameOrAlias) {
+ CompoundName properName = aliases.get(nameOrAlias.getLowerCasedName());
+ return (properName != null) ? properName : nameOrAlias;
+ }
+
+ public @Override Map<String, Object> listProperties(CompoundName property,Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ return super.listProperties(unalias(property),context,substitution);
+ }
+
+ public @Override Object get(CompoundName name,Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ return super.get(unalias(name),context,substitution);
+ }
+
+ public @Override void set(CompoundName name,Object value,Map<String,String> context) {
+ super.set(unalias(name),value,context);
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java
new file mode 100644
index 00000000000..820c4fc8ea3
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java
@@ -0,0 +1,132 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.query.Properties;
+import com.yahoo.search.result.Hit;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * A Map backing of Properties.
+ * <p>
+ * When this is cloned it will deep copy not only the model object map, but also each
+ * clonable member inside the map.
+ * <p>
+ * Subclassing is supported, a hook can be implemented to provide conditional inclusion in the map.
+ * By default - all properties are accepted, so set is never propagated.
+ * <p>
+ * This class is not multithread safe.
+ *
+ * @author bratseth
+ */
+public class PropertyMap extends Properties {
+
+ private static Logger log=Logger.getLogger(PropertyMap.class.getName());
+
+ /** The properties of this */
+ private Map<CompoundName, Object> properties = new LinkedHashMap<>();
+
+ public void set(CompoundName name, Object value, Map<String,String> context) {
+ if (shouldSet(name, value))
+ properties.put(name, value);
+ else
+ super.set(name, value, context);
+ }
+
+ /**
+ * Return true if this value should be set in this map, false if the set should be propagated instead
+ * This default implementation always returns true.
+ */
+ protected boolean shouldSet(CompoundName name,Object value) { return true; }
+
+ public @Override Object get(CompoundName name, Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ if ( ! properties.containsKey(name)) return super.get(name,context,substitution);
+ return properties.get(name);
+ }
+
+ /**
+ * Returns a direct reference to the map containing the properties set in this instance.
+ */
+ public Map<CompoundName, Object> propertyMap() {
+ return properties;
+ }
+
+ public @Override PropertyMap clone() {
+ PropertyMap clone = (PropertyMap)super.clone();
+ clone.properties = new HashMap<>();
+ for (Map.Entry<CompoundName, Object> entry : this.properties.entrySet()) {
+ Object cloneValue = clone(entry.getValue());
+ if (cloneValue == null)
+ cloneValue = entry.getValue(); // Shallow copy objects which does not support cloning
+ clone.properties.put(entry.getKey(), cloneValue);
+ }
+ return clone;
+ }
+
+ /** Clones this object if it is clonable, and the clone is public. Returns null if not */
+ public static Object clone(Object object) {
+ if (object==null) return null;
+ if (! ( object instanceof Cloneable) ) return null;
+ if (object instanceof Object[])
+ return arrayClone((Object[])object);
+ else
+ return objectClone(object);
+ }
+
+ private static Object arrayClone(Object[] object) {
+ Object[] arrayClone= Arrays.copyOf(object, object.length);
+ // deep clone
+ for (int i=0; i<arrayClone.length; i++) {
+ Object elementClone=clone(arrayClone[i]);
+ if (elementClone!=null)
+ arrayClone[i]=elementClone;
+ }
+ return arrayClone;
+ }
+
+ private static Object objectClone(Object object) {
+ if (object instanceof Hit) {
+ return ((Hit) object).clone();
+ } else if (object instanceof LinkedList) {
+ return ((LinkedList) object).clone();
+ }
+ try {
+ Method cloneMethod=object.getClass().getMethod("clone");
+ return cloneMethod.invoke(object);
+ }
+ catch (NoSuchMethodException e) {
+ log.warning("'" + object + "' is Cloneable, but has no clone method - will use the same instance in all requests");
+ return null;
+ }
+ catch (IllegalAccessException e) {
+ log.warning("'" + object + "' is Cloneable, but clone method cannot be accessed - will use the same instance in all requests");
+ return null;
+ }
+ catch (InvocationTargetException e) {
+ throw new RuntimeException("Exception cloning '" + object + "'",e);
+ }
+ }
+
+ @Override
+ public Map<String, Object> listProperties(CompoundName path, Map<String, String> context, com.yahoo.processing.request.Properties substitution) {
+ Map<String, Object> map = super.listProperties(path, context, substitution);
+
+ for (Map.Entry<CompoundName, Object> entry : properties.entrySet()) {
+ if ( ! entry.getKey().hasPrefix(path)) continue;
+ CompoundName propertyName = entry.getKey().rest(path.size());
+ if (propertyName.isEmpty()) continue;
+ map.put(propertyName.toString(), entry.getValue());
+ }
+ return map;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
new file mode 100644
index 00000000000..cd4e02dc768
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
@@ -0,0 +1,296 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.Query;
+import com.yahoo.search.query.*;
+import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
+import com.yahoo.search.query.profile.types.FieldDescription;
+import com.yahoo.search.query.profile.types.QueryProfileType;
+import com.yahoo.search.query.profile.types.QueryProfileTypeRegistry;
+import com.yahoo.search.query.ranking.Diversity;
+import com.yahoo.search.query.ranking.MatchPhase;
+import com.yahoo.tensor.Tensor;
+
+import java.util.Map;
+
+/**
+ * Maps between the query model and text properties.
+ * This can be done simpler by using reflection but the performance penalty was not worth it,
+ * especially since we should be conservative in adding things to the query model.
+ *
+ * @author bratseth
+ */
+public class QueryProperties extends Properties {
+
+ private static final String MODEL_PREFIX = Model.MODEL + ".";
+ private static final String RANKING_PREFIX = Ranking.RANKING + ".";
+ private static final String PRESENTATION_PREFIX = Presentation.PRESENTATION + ".";
+
+ public static final CompoundName[] PER_SOURCE_QUERY_PROPERTIES = new CompoundName[] {
+ new CompoundName(MODEL_PREFIX + Model.QUERY_STRING),
+ new CompoundName(MODEL_PREFIX + Model.TYPE),
+ new CompoundName(MODEL_PREFIX + Model.FILTER),
+ new CompoundName(MODEL_PREFIX + Model.DEFAULT_INDEX),
+ new CompoundName(MODEL_PREFIX + Model.LANGUAGE),
+ new CompoundName(MODEL_PREFIX + Model.ENCODING),
+ new CompoundName(MODEL_PREFIX + Model.SOURCES),
+ new CompoundName(MODEL_PREFIX + Model.SEARCH_PATH),
+ new CompoundName(MODEL_PREFIX + Model.RESTRICT),
+ new CompoundName(RANKING_PREFIX + Ranking.LOCATION),
+ new CompoundName(RANKING_PREFIX + Ranking.PROFILE),
+ new CompoundName(RANKING_PREFIX + Ranking.SORTING),
+ new CompoundName(RANKING_PREFIX + Ranking.FRESHNESS),
+ new CompoundName(RANKING_PREFIX + Ranking.QUERYCACHE),
+ new CompoundName(RANKING_PREFIX + Ranking.LIST_FEATURES),
+ new CompoundName(PRESENTATION_PREFIX + Presentation.BOLDING),
+ new CompoundName(PRESENTATION_PREFIX + Presentation.SUMMARY),
+ new CompoundName(PRESENTATION_PREFIX + Presentation.REPORT_COVERAGE),
+ new CompoundName(PRESENTATION_PREFIX + Presentation.FORMAT),
+ new CompoundName(PRESENTATION_PREFIX + Presentation.SUMMARY_FIELDS),
+ Query.HITS,
+ Query.OFFSET,
+ Query.TRACE_LEVEL,
+ Query.TIMEOUT,
+ Query.NO_CACHE,
+ Query.GROUPING_SESSION_CACHE };
+
+ private Query query;
+ private final CompiledQueryProfileRegistry profileRegistry;
+
+ public QueryProperties(Query query, CompiledQueryProfileRegistry profileRegistry) {
+ this.query = query;
+ this.profileRegistry = profileRegistry;
+ }
+
+ public void setParentQuery(Query query) {
+ this.query=query;
+ super.setParentQuery(query);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Object get(final CompoundName key, Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ if (key.size()==2 && key.first().equals(Model.MODEL)) {
+ if (key.last().equals(Model.QUERY_STRING)) return query.getModel().getQueryString();
+ if (key.last().equals(Model.TYPE)) return query.getModel().getType();
+ if (key.last().equals(Model.FILTER)) return query.getModel().getFilter();
+ if (key.last().equals(Model.DEFAULT_INDEX)) return query.getModel().getDefaultIndex();
+ if (key.last().equals(Model.LANGUAGE)) return query.getModel().getLanguage();
+ if (key.last().equals(Model.ENCODING)) return query.getModel().getEncoding();
+ if (key.last().equals(Model.SOURCES)) return query.getModel().getSources();
+ if (key.last().equals(Model.SEARCH_PATH)) return query.getModel().getSearchPath();
+ if (key.last().equals(Model.RESTRICT)) return query.getModel().getRestrict();
+ }
+ else if (key.first().equals(Ranking.RANKING)) {
+ if (key.size()==2) {
+ if (key.last().equals(Ranking.LOCATION)) return query.getRanking().getLocation();
+ if (key.last().equals(Ranking.PROFILE)) return query.getRanking().getProfile();
+ if (key.last().equals(Ranking.SORTING)) return query.getRanking().getSorting();
+ if (key.last().equals(Ranking.FRESHNESS)) return query.getRanking().getFreshness();
+ if (key.last().equals(Ranking.QUERYCACHE)) return query.getRanking().getQueryCache();
+ if (key.last().equals(Ranking.LIST_FEATURES)) return query.getRanking().getListFeatures();
+ }
+ else if (key.size()>=3 && key.get(1).equals(Ranking.MATCH_PHASE)) {
+ if (key.size() == 3) {
+ MatchPhase matchPhase = query.getRanking().getMatchPhase();
+ if (key.last().equals(MatchPhase.ATTRIBUTE)) return matchPhase.getAttribute();
+ if (key.last().equals(MatchPhase.ASCENDING)) return matchPhase.getAscending();
+ if (key.last().equals(MatchPhase.MAX_HITS)) return matchPhase.getMaxHits();
+ if (key.last().equals(MatchPhase.MAX_FILTER_COVERAGE)) return matchPhase.getMaxFilterCoverage();
+ } else if (key.size() >= 4 && key.get(2).equals(Ranking.DIVERSITY)) {
+ Diversity diversity = query.getRanking().getMatchPhase().getDiversity();
+ if (key.size() == 4) {
+ if (key.last().equals(Diversity.ATTRIBUTE)) return diversity.getAttribute();
+ if (key.last().equals(Diversity.MINGROUPS)) return diversity.getMinGroups();
+ } else if ((key.size() == 5) && key.get(3).equals(Diversity.CUTOFF)) {
+ if (key.last().equals(Diversity.FACTOR)) return diversity.getCutoffFactor();
+ if (key.last().equals(Diversity.STRATEGY)) return diversity.getCutoffStrategy();
+ }
+ }
+ }
+ else if (key.size()>2) {
+ // pass the portion after "ranking.features/properties" down
+ if (key.get(1).equals(Ranking.FEATURES)) return query.getRanking().getFeatures().getObject(key.rest().rest().toString());
+ if (key.get(1).equals(Ranking.PROPERTIES)) return query.getRanking().getProperties().get(key.rest().rest().toString());
+ }
+ }
+ else if (key.size()==2 && key.first().equals(Presentation.PRESENTATION)) {
+ if (key.last().equals(Presentation.BOLDING)) return query.getPresentation().getBolding();
+ if (key.last().equals(Presentation.SUMMARY)) return query.getPresentation().getSummary();
+ if (key.last().equals(Presentation.REPORT_COVERAGE)) return query.getPresentation().getReportCoverage();
+ if (key.last().equals(Presentation.FORMAT)) return query.getPresentation().getFormat();
+ if (key.last().equals(Presentation.TIMING)) return query.getPresentation().getTiming();
+ if (key.last().equals(Presentation.SUMMARY_FIELDS)) return query.getPresentation().getSummaryFields();
+ }
+ else if (key.first().equals("rankfeature") || key.first().equals("featureoverride")) { // featureoverride is deprecated
+ return query.getRanking().getFeatures().getObject(key.rest().toString());
+ } else if (key.first().equals("rankproperty")) {
+ return query.getRanking().getProperties().get(key.rest().toString());
+ } else if (key.size()==1) {
+ if (key.equals(Query.HITS)) return query.getHits();
+ if (key.equals(Query.OFFSET)) return query.getOffset();
+ if (key.equals(Query.TRACE_LEVEL)) return query.getTraceLevel();
+ if (key.equals(Query.TIMEOUT)) return query.getTimeout();
+ if (key.equals(Query.NO_CACHE)) return query.getNoCache();
+ if (key.equals(Query.GROUPING_SESSION_CACHE)) return query.getGroupingSessionCache();
+ if (key.toString().equals(Model.MODEL)) return query.getModel();
+ if (key.toString().equals(Ranking.RANKING)) return query.getRanking();
+ if (key.toString().equals(Presentation.PRESENTATION)) return query.getPresentation();
+ }
+ return super.get(key,context,substitution);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void set(final CompoundName key,Object value,Map<String,String> context) {
+ // Note: The defaults here are never used
+ try {
+ if (key.size()==2 && key.first().equals(Model.MODEL)) {
+ if (key.last().equals(Model.QUERY_STRING))
+ query.getModel().setQueryString(asString(value, ""));
+ else if (key.last().equals(Model.TYPE))
+ query.getModel().setType(asString(value, "ANY"));
+ else if (key.last().equals(Model.FILTER))
+ query.getModel().setFilter(asString(value, ""));
+ else if (key.last().equals(Model.DEFAULT_INDEX))
+ query.getModel().setDefaultIndex(asString(value, ""));
+ else if (key.last().equals(Model.LANGUAGE))
+ query.getModel().setLanguage(asString(value, ""));
+ else if (key.last().equals(Model.ENCODING))
+ query.getModel().setEncoding(asString(value,""));
+ else if (key.last().equals(Model.SEARCH_PATH))
+ query.getModel().setSearchPath(asString(value,""));
+ else if (key.last().equals(Model.SOURCES))
+ query.getModel().setSources(asString(value,""));
+ else if (key.last().equals(Model.RESTRICT))
+ query.getModel().setRestrict(asString(value,""));
+ else
+ throwIllegalParameter(key.last(),Model.MODEL);
+ }
+ else if (key.first().equals(Ranking.RANKING)) {
+ if (key.size()==2) {
+ if (key.last().equals(Ranking.LOCATION))
+ query.getRanking().setLocation(asString(value,""));
+ else if (key.last().equals(Ranking.PROFILE))
+ query.getRanking().setProfile(asString(value,""));
+ else if (key.last().equals(Ranking.SORTING))
+ query.getRanking().setSorting(asString(value,""));
+ else if (key.last().equals(Ranking.FRESHNESS))
+ query.getRanking().setFreshness(asString(value, ""));
+ else if (key.last().equals(Ranking.QUERYCACHE))
+ query.getRanking().setQueryCache(asBoolean(value, false));
+ else if (key.last().equals(Ranking.LIST_FEATURES))
+ query.getRanking().setListFeatures(asBoolean(value,false));
+ }
+ else if (key.size()>=3 && key.get(1).equals(Ranking.MATCH_PHASE)) {
+ if (key.size() == 3) {
+ MatchPhase matchPhase = query.getRanking().getMatchPhase();
+ if (key.last().equals(MatchPhase.ATTRIBUTE)) {
+ matchPhase.setAttribute(asString(value, null));
+ } else if (key.last().equals(MatchPhase.ASCENDING)) {
+ matchPhase.setAscending(asBoolean(value, false));
+ } else if (key.last().equals(MatchPhase.MAX_HITS)) {
+ matchPhase.setMaxHits(asLong(value, null));
+ } else if (key.last().equals(MatchPhase.MAX_FILTER_COVERAGE)) {
+ matchPhase.setMaxFilterCoverage(asDouble(value, 1.0));
+ }
+ } else if (key.size() > 3 && key.get(2).equals(Ranking.DIVERSITY)) {
+ Diversity diversity = query.getRanking().getMatchPhase().getDiversity();
+ if (key.last().equals(Diversity.ATTRIBUTE)) {
+ diversity.setAttribute(asString(value, null));
+ } else if (key.last().equals(Diversity.MINGROUPS)) {
+ diversity.setMinGroups(asLong(value, null));
+ } else if ((key.size() > 4) && key.get(3).equals(Diversity.CUTOFF)) {
+ if (key.last().equals(Diversity.FACTOR)) {
+ diversity.setCutoffFactor(asDouble(value, 10.0));
+ } else if (key.last().equals(Diversity.STRATEGY)) {
+ diversity.setCutoffStrategy(asString(value, "loose"));
+ }
+ }
+ }
+ }
+ else if (key.size()>2) {
+ String restKey = key.rest().rest().toString();
+ if (key.get(1).equals(Ranking.FEATURES))
+ setRankingFeature(query, restKey, toSpecifiedType(restKey, value, profileRegistry.getTypeRegistry().getComponent("features")));
+ else if (key.get(1).equals(Ranking.PROPERTIES))
+ query.getRanking().getProperties().put(restKey, toSpecifiedType(restKey, value, profileRegistry.getTypeRegistry().getComponent("properties")));
+ else
+ throwIllegalParameter(key.rest().toString(),Ranking.RANKING);
+ }
+ }
+ else if (key.size()==2 && key.first().equals(Presentation.PRESENTATION)) {
+ if (key.last().equals(Presentation.BOLDING))
+ query.getPresentation().setBolding(asBoolean(value, true));
+ else if (key.last().equals(Presentation.SUMMARY))
+ query.getPresentation().setSummary(asString(value, ""));
+ else if (key.last().equals(Presentation.REPORT_COVERAGE))
+ query.getPresentation().setReportCoverage(asBoolean(value,true));
+ else if (key.last().equals(Presentation.FORMAT))
+ query.getPresentation().setFormat(asString(value,""));
+ else if (key.last().equals(Presentation.TIMING))
+ query.getPresentation().setTiming(asBoolean(value, true));
+ else if (key.last().equals(Presentation.SUMMARY_FIELDS))
+ query.getPresentation().setSummaryFields(asString(value,""));
+ else
+ throwIllegalParameter(key.last(), Presentation.PRESENTATION);
+ }
+ else if (key.first().equals("rankfeature") || key.first().equals("featureoverride") ) { // featureoverride is deprecated
+ setRankingFeature(query, key.rest().toString(), toSpecifiedType(key.rest().toString(), value, profileRegistry.getTypeRegistry().getComponent("features")));
+ } else if (key.first().equals("rankproperty")) {
+ query.getRanking().getProperties().put(key.rest().toString(), toSpecifiedType(key.rest().toString(), value, profileRegistry.getTypeRegistry().getComponent("properties")));
+ } else if (key.size()==1) {
+ if (key.equals(Query.HITS))
+ query.setHits(asInteger(value,10));
+ else if (key.equals(Query.OFFSET))
+ query.setOffset(asInteger(value,0));
+ else if (key.equals(Query.TRACE_LEVEL))
+ query.setTraceLevel(asInteger(value,0));
+ else if (key.equals(Query.TIMEOUT))
+ query.setTimeout(value.toString());
+ else if (key.equals(Query.NO_CACHE))
+ query.setNoCache(asBoolean(value,false));
+ else if (key.equals(Query.GROUPING_SESSION_CACHE))
+ query.setGroupingSessionCache(asBoolean(value, false));
+ else
+ super.set(key,value,context);
+ }
+ else
+ super.set(key,value,context);
+ }
+ catch (Exception e) { // Make sure error messages are informative. This should be moved out of this properties implementation
+ if (e.getMessage().startsWith("Could not set"))
+ throw e;
+ else
+ throw new IllegalArgumentException("Could not set '" + key + "' to '" + value + "'", e);
+ }
+ }
+
+ private void setRankingFeature(Query query, String key, Object value) {
+ if (value instanceof Tensor)
+ query.getRanking().getFeatures().put(key, (Tensor)value);
+ else
+ query.getRanking().getFeatures().put(key, asString(value, ""));
+ }
+
+ private Object toSpecifiedType(String key, Object value, QueryProfileType type) {
+ if ( ! ( value instanceof String)) return value; // already typed
+ if (type == null) return value; // no type info -> keep as string
+ FieldDescription field = type.getField(key);
+ if (field == null) return value; // ditto
+ return field.getType().convertFrom(value, profileRegistry);
+ }
+
+ private void throwIllegalParameter(String key,String namespace) {
+ throw new IllegalArgumentException("'" + key + "' is not a valid property in '" + namespace +
+ "'. See the search api for valid keys starting by '" + namespace + "'.");
+ }
+
+ @Override
+ public final Query getParentQuery() {
+ return query;
+ }
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/QueryPropertyAliases.java b/container-search/src/main/java/com/yahoo/search/query/properties/QueryPropertyAliases.java
new file mode 100644
index 00000000000..15544e8ff4c
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/QueryPropertyAliases.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.processing.request.CompoundName;
+
+import java.util.Map;
+
+/**
+ * Property aliases which contains some hardcoded unaliasing of prefixes of
+ * rankfeature and rankproperty maps.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class QueryPropertyAliases extends PropertyAliases {
+
+ /**
+ * Creates an instance with a set of aliases. The given aliases will be used directly by this class.
+ * To make this class immutable and thread safe, relinquish ownership of the parameter map.
+ */
+ public QueryPropertyAliases(Map<String,CompoundName> aliases) {
+ super(aliases);
+ }
+
+ @Override
+ protected CompoundName unalias(CompoundName nameOrAlias) {
+ if (nameOrAlias.first().equalsIgnoreCase("rankfeature"))
+ return nameOrAlias.rest().prepend("ranking", "features");
+ else if (nameOrAlias.first().equalsIgnoreCase("rankproperty"))
+ return nameOrAlias.rest().prepend("ranking", "properties");
+ return super.unalias(nameOrAlias);
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/RequestContextProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/RequestContextProperties.java
new file mode 100644
index 00000000000..c97f4daf6d4
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/RequestContextProperties.java
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.query.Properties;
+
+import java.util.Map;
+
+/**
+ * Turns get(name) into get(name,request) using the request given at construction time.
+ * This is used to allow the query's request to be supplied to all property requests
+ * without forcing users of the query.properties() to supply this explicitly.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class RequestContextProperties extends Properties {
+
+ private final Map<String,String> requestMap;
+
+ public RequestContextProperties(Map<String, String> properties) {
+ this.requestMap=properties;
+ }
+
+ @Override
+ public Object get(CompoundName name,Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ return super.get(name,context==null ? requestMap : context,substitution);
+ }
+
+ @Override
+ public void set(CompoundName name,Object value,Map<String,String> context) {
+ super.set(name,value,context==null ? requestMap : context);
+ }
+
+ @Override
+ public Map<String, Object> listProperties(CompoundName path,Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ return super.listProperties(path,context==null ? requestMap : context,substitution);
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/SubProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/SubProperties.java
new file mode 100644
index 00000000000..7f5c2ec2558
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/SubProperties.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.properties;
+
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.processing.request.Properties;
+
+import java.util.Map;
+
+/**
+ * A wrapper around a chain of property objects that prefixes all gets/sets with a given path
+ *
+ * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a>
+ */
+public class SubProperties extends com.yahoo.search.query.Properties {
+
+ final private CompoundName pathPrefix;
+ final private Properties parent;
+
+ public SubProperties(String pathPrefix, Properties properties) {
+ this(new CompoundName(pathPrefix),properties);
+ }
+
+ public SubProperties(CompoundName pathPrefix, Properties properties) {
+ this.pathPrefix = pathPrefix;
+ this.parent = properties;
+ }
+
+ @Override
+ public Object get(CompoundName key, Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ if(key == null) return null;
+ Object result = parent.get(getPathPrefix() + "." + key,context,substitution);
+ if(result == null) {
+ return super.get(key,context,substitution);
+ } else {
+ return result;
+ }
+ }
+
+ @Override
+ public void set(CompoundName key, Object obj, Map<String,String> context) {
+ if(key == null) return;
+ parent.set(getPathPrefix() + "." + key, obj, context);
+ }
+
+ @Override
+ public Map<String, Object> listProperties(CompoundName path,Map<String,String> context,
+ com.yahoo.processing.request.Properties substitution) {
+ Map<String, Object> map = super.listProperties(path,context,substitution);
+ if(path.isEmpty()) {
+ map.putAll(parent.listProperties(getPathPrefix(),context,substitution));
+ } else {
+ map.putAll(parent.listProperties(getPathPrefix() + "." + path,context,substitution));
+ }
+ return map;
+ }
+
+ public CompoundName getPathPrefixCompound() {
+ return pathPrefix;
+ }
+
+ /** Returns getPatchPrefixCompound.toString() */
+ public String getPathPrefix() {
+ return getPathPrefixCompound().toString();
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/package-info.java b/container-search/src/main/java/com/yahoo/search/query/properties/package-info.java
new file mode 100644
index 00000000000..047a5494e53
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.search.query.properties;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;