diff options
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/query/properties')
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; |