/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.search.aggregations; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.support.AggregationPath; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; /** * An internal implementation of {@link Aggregation}. Serves as a base class for all aggregation implementations. */ public abstract class InternalAggregation implements Aggregation, ToXContent, NamedWriteable { public static class ReduceContext { private final BigArrays bigArrays; private final ScriptService scriptService; private final boolean isFinalReduce; public ReduceContext(BigArrays bigArrays, ScriptService scriptService, boolean isFinalReduce) { this.bigArrays = bigArrays; this.scriptService = scriptService; this.isFinalReduce = isFinalReduce; } /** * Returns <code>true</code> iff the current reduce phase is the final reduce phase. This indicates if operations like * pipeline aggregations should be applied or if specific features like <tt>minDocCount</tt> should be taken into account. * Operations that are potentially loosing information can only be applied during the final reduce phase. */ public boolean isFinalReduce() { return isFinalReduce; } public BigArrays bigArrays() { return bigArrays; } public ScriptService scriptService() { return scriptService; } } protected final String name; protected final Map<String, Object> metaData; private final List<PipelineAggregator> pipelineAggregators; /** * Constructs an get with a given name. * * @param name The name of the get. */ protected InternalAggregation(String name, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) { this.name = name; this.pipelineAggregators = pipelineAggregators; this.metaData = metaData; } /** * Read from a stream. */ protected InternalAggregation(StreamInput in) throws IOException { name = in.readString(); metaData = in.readMap(); pipelineAggregators = in.readNamedWriteableList(PipelineAggregator.class); } @Override public final void writeTo(StreamOutput out) throws IOException { out.writeString(name); out.writeGenericValue(metaData); out.writeNamedWriteableList(pipelineAggregators); doWriteTo(out); } protected abstract void doWriteTo(StreamOutput out) throws IOException; @Override public String getName() { return name; } /** * Reduces the given aggregations to a single one and returns it. In <b>most</b> cases, the assumption will be the all given * aggregations are of the same type (the same type as this aggregation). For best efficiency, when implementing, * try reusing an existing instance (typically the first in the given list) to save on redundant object * construction. */ public final InternalAggregation reduce(List<InternalAggregation> aggregations, ReduceContext reduceContext) { InternalAggregation aggResult = doReduce(aggregations, reduceContext); if (reduceContext.isFinalReduce()) { for (PipelineAggregator pipelineAggregator : pipelineAggregators) { aggResult = pipelineAggregator.reduce(aggResult, reduceContext); } } return aggResult; } public abstract InternalAggregation doReduce(List<InternalAggregation> aggregations, ReduceContext reduceContext); /** * Get the value of specified path in the aggregation. * * @param path * the path to the property in the aggregation tree * @return the value of the property */ public Object getProperty(String path) { AggregationPath aggPath = AggregationPath.parse(path); return getProperty(aggPath.getPathElementsAsStringList()); } public abstract Object getProperty(List<String> path); /** * Read a size under the assumption that a value of 0 means unlimited. */ protected static int readSize(StreamInput in) throws IOException { final int size = in.readVInt(); return size == 0 ? Integer.MAX_VALUE : size; } /** * Write a size under the assumption that a value of 0 means unlimited. */ protected static void writeSize(int size, StreamOutput out) throws IOException { if (size == Integer.MAX_VALUE) { size = 0; } out.writeVInt(size); } @Override public Map<String, Object> getMetaData() { return metaData; } public List<PipelineAggregator> pipelineAggregators() { return pipelineAggregators; } /** * Returns a string representing the type of the aggregation. This type is added to * the aggregation name in the response, so that it can later be used by REST clients * to determine the internal type of the aggregation. */ protected String getType() { return getWriteableName(); } @Override public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { if (params.paramAsBoolean(RestSearchAction.TYPED_KEYS_PARAM, false)) { // Concatenates the type and the name of the aggregation (ex: top_hits#foo) builder.startObject(String.join(TYPED_KEYS_DELIMITER, getType(), getName())); } else { builder.startObject(getName()); } if (this.metaData != null) { builder.field(CommonFields.META.getPreferredName()); builder.map(this.metaData); } doXContentBody(builder, params); builder.endObject(); return builder; } public abstract XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException; @Override public int hashCode() { return Objects.hash(name, metaData, pipelineAggregators, doHashCode()); } /** * Opportunity for subclasses to the {@link #hashCode()} for this * class. **/ protected int doHashCode() { return System.identityHashCode(this); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (obj.getClass() != getClass()) { return false; } InternalAggregation other = (InternalAggregation) obj; return Objects.equals(name, other.name) && Objects.equals(pipelineAggregators, other.pipelineAggregators) && Objects.equals(metaData, other.metaData) && doEquals(obj); } // norelease: make this abstract when all InternalAggregations implement this method /** * Opportunity for subclasses to add criteria to the {@link #equals(Object)} * method for this class. * * This method can safely cast <code>obj</code> to the subclass since the * {@link #equals(Object)} method checks that <code>obj</code> is the same * class as <code>this</code> */ protected boolean doEquals(Object obj) { return this == obj; } }