All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.ehcache.store.BruteForceSearchManager Maven / Gradle / Ivy

Go to download

Ehcache is an open source, standards-based cache used to boost performance, offload the database and simplify scalability. Ehcache is robust, proven and full-featured and this has made it the most widely-used Java-based cache.

There is a newer version: 2.10.9.2
Show newest version
/**
 *  Copyright Terracotta, Inc.
 *
 *  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 net.sf.ehcache.store;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.ConfigurationHelper;
import net.sf.ehcache.config.SearchAttribute;
import net.sf.ehcache.config.Searchable;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Results;
import net.sf.ehcache.search.SearchException;
import net.sf.ehcache.search.aggregator.AggregatorInstance;
import net.sf.ehcache.search.attribute.AttributeExtractor;
import net.sf.ehcache.search.attribute.AttributeExtractorException;
import net.sf.ehcache.search.attribute.AttributeType;
import net.sf.ehcache.search.attribute.DynamicAttributesExtractor;
import net.sf.ehcache.search.expression.Criteria;
import net.sf.ehcache.search.impl.AggregateOnlyResult;
import net.sf.ehcache.search.impl.BaseResult;
import net.sf.ehcache.search.impl.DynamicSearchChecker;
import net.sf.ehcache.search.impl.GroupedResultImpl;
import net.sf.ehcache.search.impl.OrderComparator;
import net.sf.ehcache.search.impl.ResultImpl;
import net.sf.ehcache.search.impl.ResultsImpl;
import net.sf.ehcache.search.impl.SearchManager;
import net.sf.ehcache.transaction.SoftLockID;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import static net.sf.ehcache.search.expression.BaseCriteria.getExtractor;

/**
 * Brute force search implementation
 *
 * @author teck
 */
public class BruteForceSearchManager implements SearchManager {

    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

    /**
     * account for all search attributes
     */
    private final Set searchAttributes = new CopyOnWriteArraySet();
    private final Ehcache cache;
    private BruteForceSource bruteForceSource;


    /**
     * Create a BruteForceSearchManager
     * @param cache 
     */
    public BruteForceSearchManager(Ehcache cache) {
        this.cache = cache;
    }

    /**
     * Concrete search result with relevant inputs to aggregate functions (if any) 
     */
    private static final class ResultHolder implements Comparable {
        private final BaseResult result;
        private final List aggregatorInputs;
        private final OrderComparator comp;
        
        private ResultHolder(BaseResult res, List values, OrderComparator cmp) {
            result = res;
            aggregatorInputs = values;
            comp = cmp;
        }

        @Override
        public int compareTo(ResultHolder other) {
            return comp.compare(this.result, other.result);
        }
    }
    
    @Override
    public Results executeQuery(StoreQuery query, Map extractors, DynamicAttributesExtractor
            dynIndexer) {
        Criteria c = query.getCriteria();

        List> aggregators = query.getAggregatorInstances();

        final Set> groupByAttributes = query.groupByAttributes();
        final boolean isGroupBy = !groupByAttributes.isEmpty();
        boolean includeResults = query.requestsKeys() || query.requestsValues() || !query.requestedAttributes().isEmpty() || isGroupBy;

        boolean hasOrder = !query.getOrdering().isEmpty();

        final Map, ResultHolder> groupByResults = new HashMap, ResultHolder>();
        final Map>> groupByAggregators = new HashMap>>();

        Collection matches = new LinkedList();
        Map> eltExtractors = new HashMap>();

        for (Element element : bruteForceSource.elements()) {

            Map extractorSuperset = getCombinedExtractors(extractors, dynIndexer, element);
            eltExtractors.put(element.getObjectKey(), extractorSuperset);

            if (c.execute(element, extractorSuperset)) {
                if (!isGroupBy && !hasOrder && query.maxResults() >= 0 && matches.size() == query.maxResults()) {
                    break;
                }

                matches.add(element);
            }
        }

        Collection results = isGroupBy ? groupByResults.values() : new ArrayList();

        boolean anyMatches = !matches.isEmpty();
        OrderComparator comp = new OrderComparator(query.getOrdering());
        
        for (Element element : matches) {
            Map extractorSuperset = eltExtractors.get(element.getObjectKey());

            List resultAggs = new ArrayList(aggregators.size());
            for (AggregatorInstance agg: aggregators) {
                Attribute aggrAttr = agg.getAttribute();
                // placeholder input for count
                Object val = aggrAttr != null ? 
                    getExtractor(aggrAttr.getAttributeName(), extractorSuperset).attributeFor(element, aggrAttr.getAttributeName()) : null;
                resultAggs.add(val);
            }
            
            Map attributes = getAttributeValues(query.requestedAttributes(), extractorSuperset, element);
            Object[] sortAttributes = getSortAttributes(query, extractorSuperset, element);

            if (!isGroupBy) {
                results.add(new ResultHolder(new ResultImpl(element.getObjectKey(), element.getObjectValue(), query, attributes, sortAttributes), 
                            resultAggs, comp));
            } else {
                Map groupByValues = getAttributeValues(groupByAttributes, extractorSuperset, element);
                Set groupId = new HashSet(groupByValues.values());
                List> groupAggrs = groupByAggregators.get(groupId);
                if (groupAggrs == null) {
                    groupAggrs = new ArrayList>(aggregators.size());
                    for (AggregatorInstance aggr : aggregators) {
                        groupAggrs.add(aggr.createClone());
                    }
                    groupByAggregators.put(groupId, groupAggrs);
                }
                int i = 0;
                for (AggregatorInstance inst: groupAggrs) {
                    inst.accept(resultAggs.get(i++));
                }
                ResultHolder group = groupByResults.get(groupId);
                if (group == null) {
                    group = new ResultHolder(new GroupedResultImpl(query, attributes, sortAttributes, Collections.emptyList(),
                            groupByValues), Collections.emptyList(), comp);
                    groupByResults.put(groupId, group);
                }
            }
        }

        if (hasOrder || isGroupBy) {
            if (isGroupBy) {
                results = new ArrayList(results);
            }

            if (hasOrder) {
                Collections.sort((List)results);
            }
            // trim results to max length if necessary
            int max = query.maxResults();
            if (max >= 0 && (results.size() > max)) {
                results = ((List)results).subList(0, max);
            }
        }

        if (!aggregators.isEmpty()) {
            for (ResultHolder rh : results) {
                if (isGroupBy) {
                    GroupedResultImpl group = (GroupedResultImpl)rh.result;
                    Set groupId = new HashSet(group.getGroupByValues().values());
                    aggregators = groupByAggregators.get(groupId);
                    setResultAggregators(aggregators, group);
                } else {
                    int i = 0;
                    for (Object val: rh.aggregatorInputs) {
                        aggregators.get(i++).accept(val);
                    }
                }
            }
            if (includeResults && !isGroupBy) {
                // Set the same aggregate values for each result
                for (ResultHolder rh: results) {
                    setResultAggregators(aggregators, rh.result);
                }
            }
        }
        
        List output;
        
        if (!isGroupBy && anyMatches && !includeResults && !aggregators.isEmpty()) {
            // add one row in the results if the only thing included was aggregators and anything matched
            BaseResult aggOnly = new AggregateOnlyResult(query);
            setResultAggregators(aggregators, aggOnly);
            output = Collections.singletonList(aggOnly);
        } else {
            output = new ArrayList(results.size());
            for (ResultHolder rh: results) {
                output.add(rh.result);
            }
        }

        return new ResultsImpl(output, query.requestsKeys(), query.requestsValues(), !query.requestedAttributes().isEmpty(), anyMatches
                && !aggregators.isEmpty());
    }

    private void setResultAggregators(List> aggregators, BaseResult result)
    {
        List aggregateResults = new ArrayList();
        for (AggregatorInstance aggregator : aggregators) {
            aggregateResults.add(aggregator.aggregateResult());
        }

        if (!aggregateResults.isEmpty()) {
            result.setAggregateResults(aggregateResults);
        }
    }

    private Map getAttributeValues(Set> attributes, Map extractors, Element element) {
        final Map values;
        if (attributes.isEmpty()) {
            values = Collections.emptyMap();
        } else {
            values = new HashMap();
            for (Attribute attribute : attributes) {
                String name = attribute.getAttributeName();
                values.put(name, getExtractor(name, extractors).attributeFor(element, name));
            }
        }
        return values;
    }

    private Map getCombinedExtractors(Map configExtractors, DynamicAttributesExtractor
            dynIndexer, Element element) {
        Map combinedExtractors = new HashMap();
        combinedExtractors.putAll(configExtractors);

        if (dynIndexer != null) {
            Map dynamic = DynamicSearchChecker.getSearchAttributes(element, configExtractors.keySet(),
                    dynIndexer);

            for (final Map.Entry entry: dynamic.entrySet()) {
                AttributeExtractor old = combinedExtractors.put(entry.getKey(), new AttributeExtractor() {
                    @Override
                    public Object attributeFor(Element element, String attributeName) throws AttributeExtractorException {
                        if (!attributeName.equals(entry.getKey())) {
                            throw new AttributeExtractorException(String.format("Expected attribute name %s but got %s", entry.getKey(),
                                    attributeName));
                        }
                        return entry.getValue();
                    }
                });
                if (old != null) {
                    throw new AttributeExtractorException(String.format("Attribute name %s already used by configured extractors",
                            entry.getKey()));
                }
            }
        }
        return combinedExtractors;
    }

    private Object[] getSortAttributes(StoreQuery query, Map extractors, Element element) {
        Object[] sortAttributes;
        List orderings = query.getOrdering();
        if (orderings.isEmpty()) {
            sortAttributes = BruteForceSearchManager.EMPTY_OBJECT_ARRAY;
        } else {
            sortAttributes = new Object[orderings.size()];
            for (int i = 0; i < sortAttributes.length; i++) {
                String name = orderings.get(i).getAttribute().getAttributeName();
                sortAttributes[i] = getExtractor(name, extractors).attributeFor(element, name);
            }
        }

        return sortAttributes;
    }

    @Override
    public void clear(String cacheName, int segmentId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void put(String cacheName, int segmentId, Element element, byte[] key, Map extractors,
            DynamicAttributesExtractor dynamicIndexer) {
        if (extractors.isEmpty() && dynamicIndexer == null) {
            return;
        }

      boolean isXa = element.getObjectValue() instanceof SoftLockID;

      if (isXa) {
        SoftLockID sl = (SoftLockID) element.getObjectValue();
        element = sl.getOldElement();

        // No previous value committed - do not index
        if (element == null) { return; }
      }
      element = bruteForceSource.transformForIndexing(element);

      // Handle dynamic attribute extractor, if any
      Map dynAttrs = DynamicSearchChecker.getSearchAttributes(element, extractors.keySet(),
                                                                   dynamicIndexer);
      Set> attrs = new HashSet>(dynAttrs.size());
      for (Map.Entry attr : dynAttrs.entrySet()) {
          if (!AttributeType.isSupportedType(attr.getValue())) {
              throw new CacheException(String.format("Unsupported attribute type specified %s for dynamically extracted attribute %s",
                      attr.getClass().getName(), attr.getKey()));
          }
          attrs.add(new Attribute(attr.getKey()));
      }

      Searchable config = bruteForceSource.getSearchable();
      if (config == null) { return; }
      for (Map.Entry entry : extractors.entrySet()) {
        String name = entry.getKey();
        SearchAttribute sa = config.getSearchAttributes().get(name);
        Class c = ConfigurationHelper.getSearchAttributeType(sa, cache.getCacheConfiguration().getClassLoader());
        if (c == null) { continue; }

        AttributeExtractor extractor = entry.getValue();
        Object av = extractor.attributeFor(element, name);

        AttributeType schemaType = AttributeType.typeFor(c);
        AttributeType type = AttributeType.typeFor(name, av);

        String schemaTypeName = c.isEnum() ? c.getName() : schemaType.name();
        String typeName = AttributeType.ENUM == type ? ((Enum) av).getDeclaringClass().getName() : type.name();

        if (!typeName.equals(schemaTypeName)) { throw new SearchException(
                                                                    String
                                                                        .format("Expecting a %s value for attribute [%s] but was %s",
                                                                                schemaTypeName, name, typeName));
        }
      }

      searchAttributes.addAll(attrs);
    }

    @Override
    public void remove(String cacheName, Object key, int segmentId, boolean isRemoval) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Set getSearchAttributes(String cacheName) {
        return searchAttributes;
    }

    /**
     * Sets the BruteForceSource to be used for search
     *
     * @param bruteForceSource the source
     */
    public void setBruteForceSource(BruteForceSource bruteForceSource) {
        this.bruteForceSource = bruteForceSource;
    }

    /**
     * Add search attributes
     *
     * @param attributeSet the search attributes to add
     */
    void addSearchAttributes(Set> attributeSet) {
        searchAttributes.addAll(attributeSet);
    }
}