/* * Copyright [2017] Wikimedia Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.o19s.es.ltr.logging; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.search.SearchExtBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Stream; public class LoggingSearchExtBuilder extends SearchExtBuilder { public static final String NAME = "ltr_log"; private static final ObjectParser<LoggingSearchExtBuilder, Void> PARSER; private static final ParseField LOG_SPECS = new ParseField("log_specs"); static { PARSER = new ObjectParser<>(NAME, LoggingSearchExtBuilder::new); PARSER.declareObjectArray(LoggingSearchExtBuilder::setLogSpecs, LogSpec::parse, LOG_SPECS); } private List<LogSpec> logSpecs; public LoggingSearchExtBuilder() {} public LoggingSearchExtBuilder(StreamInput input) throws IOException { logSpecs = input.readList(LogSpec::new); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeList(logSpecs); } @Override public String getWriteableName() { return NAME; } public static LoggingSearchExtBuilder parse(XContentParser parser) throws IOException { try { LoggingSearchExtBuilder ext = PARSER.parse(parser, null); if (ext.logSpecs == null || ext.logSpecs.isEmpty()) { throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] should define at least one [" + LOG_SPECS + "]"); } return ext; } catch(IllegalArgumentException iae) { throw new ParsingException(parser.getTokenLocation(), iae.getMessage(), iae); } } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field(LOG_SPECS.getPreferredName(), logSpecs); return builder.endObject(); } public Stream<LogSpec> logSpecsStream() { return logSpecs.stream(); } private void setLogSpecs(List<LogSpec> logSpecs) { this.logSpecs = logSpecs; } public LoggingSearchExtBuilder addQueryLogging(String name, String namedQuery, boolean missingAsZero) { addLogSpec(new LogSpec(name, Objects.requireNonNull(namedQuery), missingAsZero)); return this; } public LoggingSearchExtBuilder addRescoreLogging(String name, int rescoreIndex, boolean missingAsZero) { addLogSpec(new LogSpec(name, rescoreIndex, missingAsZero)); return this; } private void addLogSpec(LogSpec spec) { if (logSpecs == null) { logSpecs = new ArrayList<>(); } logSpecs.add(spec); } @Override public int hashCode() { return Objects.hash(this.getClass(), logSpecs); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof LoggingSearchExtBuilder)) { return false; } LoggingSearchExtBuilder o = (LoggingSearchExtBuilder) obj; return Objects.equals(logSpecs, o.logSpecs); } public static class LogSpec implements Writeable, ToXContentObject { private static final ParseField LOGGER_NAME = new ParseField("name"); private static final ParseField NAMED_QUERY = new ParseField("named_query"); private static final ParseField RESCORE_INDEX = new ParseField("rescore_index"); private static final ParseField MISSING_AS_ZERO = new ParseField("missing_as_zero"); private static final ObjectParser<LogSpec, Void> PARSER; static { PARSER = new ObjectParser<>("spec", LogSpec::new); PARSER.declareString(LogSpec::setLoggerName, LOGGER_NAME); PARSER.declareString(LogSpec::setNamedQuery, NAMED_QUERY); PARSER.declareInt(LogSpec::setRescoreIndex, RESCORE_INDEX); PARSER.declareBoolean(LogSpec::setMissingAsZero, MISSING_AS_ZERO); } private String loggerName; private String namedQuery; private Integer rescoreIndex; private boolean missingAsZero; private LogSpec() {} LogSpec(@Nullable String loggerName, String namedQuery, boolean missingAsZero) { this.loggerName = loggerName; this.namedQuery = Objects.requireNonNull(namedQuery); this.missingAsZero = missingAsZero; } LogSpec(@Nullable String loggerName, int rescoreIndex, boolean missingAsZero) { this.loggerName = loggerName; this.rescoreIndex = rescoreIndex; this.missingAsZero = missingAsZero; } private LogSpec(StreamInput input) throws IOException { loggerName = input.readOptionalString(); namedQuery = input.readOptionalString(); rescoreIndex = input.readOptionalVInt(); missingAsZero = input.readBoolean(); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(loggerName); out.writeOptionalString(namedQuery); out.writeOptionalVInt(rescoreIndex); out.writeBoolean(missingAsZero); } private static LogSpec parse(XContentParser parser, Void context) throws IOException { try { LogSpec spec = PARSER.parse(parser, null); if (spec.namedQuery == null && spec.rescoreIndex == null) { throw new ParsingException(parser.getTokenLocation(), "Either " + "[" + NAMED_QUERY + "] or [" + RESCORE_INDEX + "] must be set."); } if (spec.rescoreIndex != null && spec.rescoreIndex < 0) { throw new ParsingException(parser.getTokenLocation(), "[" + RESCORE_INDEX + "] must be a non-negative integer."); } return spec; } catch (IllegalArgumentException iae) { throw new ParsingException(parser.getTokenLocation(), iae.getMessage(), iae); } } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); if (loggerName != null) { builder.field(LOGGER_NAME.getPreferredName(), loggerName); } if (namedQuery != null) { builder.field(NAMED_QUERY.getPreferredName(), namedQuery); } else if (rescoreIndex != null) { builder.field(RESCORE_INDEX.getPreferredName(), rescoreIndex); } if (missingAsZero) { builder.field(MISSING_AS_ZERO.getPreferredName(), missingAsZero); } return builder.endObject(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LogSpec logSpec = (LogSpec) o; if (missingAsZero != logSpec.missingAsZero) return false; if (loggerName != null ? !loggerName.equals(logSpec.loggerName) : logSpec.loggerName != null) return false; if (namedQuery != null ? !namedQuery.equals(logSpec.namedQuery) : logSpec.namedQuery != null) return false; return rescoreIndex != null ? rescoreIndex.equals(logSpec.rescoreIndex) : logSpec.rescoreIndex == null; } @Override public int hashCode() { int result = loggerName != null ? loggerName.hashCode() : 0; result = 31 * result + (namedQuery != null ? namedQuery.hashCode() : 0); result = 31 * result + (rescoreIndex != null ? rescoreIndex.hashCode() : 0); result = 31 * result + (missingAsZero ? 1 : 0); return result; } public String getNamedQuery() { return namedQuery; } private void setNamedQuery(String namedQuery) { this.namedQuery = namedQuery; } public Integer getRescoreIndex() { return rescoreIndex; } private void setRescoreIndex(Integer rescoreIndex) { this.rescoreIndex = rescoreIndex; } public String getLoggerName() { if (loggerName != null) { return loggerName; } return namedQuery != null ? namedQuery : "rescore[" + rescoreIndex + "]"; } private void setLoggerName(String loggerName) { this.loggerName = loggerName; } public boolean isMissingAsZero() { return missingAsZero; } private void setMissingAsZero(boolean missingAsZero) { this.missingAsZero = missingAsZero; } } }