/** * This software is licensed to you under the Apache License, Version 2.0 (the * "Apache License"). * * LinkedIn's contributions are made under the Apache License. If you contribute * to the Software, the contributions will be deemed to have been made under the * Apache License, unless you expressly indicate otherwise. Please do not make any * contributions that would be inconsistent with the Apache License. * * You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, this software * distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache * License for the specific language governing permissions and limitations for the * software governed under the Apache License. * * © 2012 LinkedIn Corp. All Rights Reserved. */ package com.senseidb.indexing.activity.facet; import java.io.IOException; import java.text.DecimalFormat; import java.util.HashSet; import java.util.Properties; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.ScoreDoc; import proj.zoie.api.ZoieSegmentReader; import com.browseengine.bobo.api.BoboIndexReader; import com.browseengine.bobo.api.BrowseSelection; import com.browseengine.bobo.api.FacetSpec; import com.browseengine.bobo.docidset.EmptyDocIdSet; import com.browseengine.bobo.docidset.RandomAccessDocIdSet; import com.browseengine.bobo.facets.FacetCountCollectorSource; import com.browseengine.bobo.facets.FacetHandler; import com.browseengine.bobo.facets.filter.FacetRangeFilter; import com.browseengine.bobo.facets.filter.RandomAccessFilter; import com.browseengine.bobo.sort.DocComparator; import com.browseengine.bobo.sort.DocComparatorSource; import com.senseidb.indexing.activity.CompositeActivityManager; import com.senseidb.indexing.activity.primitives.ActivityFloatValues; import com.senseidb.indexing.activity.primitives.ActivityIntValues; import com.senseidb.indexing.activity.primitives.ActivityLongValues; import com.senseidb.indexing.activity.primitives.ActivityPrimitiveValues; /** * Range facet handler for activity fields * @author vzhabiuk * */ public class ActivityRangeFacetHandler extends FacetHandler<int[]> { private static final String[] EMPTY_STRING_ARR = new String[0]; private static final Object[] EMPTY_OBJ_ARR = new Object[0]; private static final String DEFAULT_FORMATTING_STRING = "0000000000"; public static volatile boolean isSynchronized = false; protected ThreadLocal<DecimalFormat> formatter = new ThreadLocal<DecimalFormat>() { protected DecimalFormat initialValue() { return new DecimalFormat(DEFAULT_FORMATTING_STRING); } }; private final ActivityPrimitiveValues activityValues; protected final CompositeActivityManager compositeActivityManager; public ActivityRangeFacetHandler(String facetName, String fieldName, CompositeActivityManager compositeActivityManager, ActivityPrimitiveValues activityValues) { super(facetName, new HashSet<String>()); this.compositeActivityManager = compositeActivityManager; this.activityValues = activityValues; } public static FacetHandler valueOf(String facetName, String fieldName, CompositeActivityManager compositeActivityManager, ActivityPrimitiveValues activityValues) { if (isSynchronized) { return new SynchronizedActivityRangeFacetHandler(facetName, fieldName, compositeActivityManager, activityValues); } return new ActivityRangeFacetHandler(facetName, fieldName, compositeActivityManager, activityValues); } @Override public int[] load(BoboIndexReader reader) throws IOException { ZoieSegmentReader zoieReader = (ZoieSegmentReader)(reader.getInnerReader()); long[] uidArray = zoieReader.getUIDArray(); return compositeActivityManager.getActivityValues().precomputeArrayIndexes(uidArray); } @Override public RandomAccessFilter buildRandomAccessFilter(final String value, Properties selectionProperty) throws IOException { return new RandomAccessFilter() { @Override public RandomAccessDocIdSet getRandomAccessDocIdSet(BoboIndexReader reader) throws IOException { final int[] indexes = (int[]) ((BoboIndexReader)reader).getFacetData(_name); if (value == null || value.isEmpty()) { return EmptyDocIdSet.getInstance(); } final int[] intArray = activityValues instanceof ActivityIntValues ? ((ActivityIntValues) activityValues).getFieldValues() : null; final float[] floatArray = activityValues instanceof ActivityFloatValues ? ((ActivityFloatValues) activityValues).getFieldValues() : null; final long[] longArray = activityValues instanceof ActivityLongValues ? ((ActivityLongValues) activityValues).getFieldValues() : null; if (longArray == null) { int[] range = parseRaw(value); final int startValue = range[0]; final int endValue = range[1]; if (startValue >= endValue) { return EmptyDocIdSet.getInstance(); } return new RandomAccessDocIdSet() { @Override public DocIdSetIterator iterator() throws IOException { if (intArray != null) { return new ActivityRangeIntFilterIterator(intArray, indexes, startValue, endValue); } else { return new ActivityRangeFloatFilterIterator(floatArray, indexes, startValue, endValue); } } @Override public boolean get(int docId) { if (indexes[docId] == -1) return false; if (intArray != null) { int val = intArray[indexes[docId]]; return val >= startValue && val < endValue && val != Integer.MIN_VALUE; } float val = floatArray[indexes[docId]]; return val >= startValue && val < endValue && val != Integer.MIN_VALUE; } }; } else { final long[] longRange = longArray != null ? parseRawLong(value) : null; final long startValue = longRange[0]; final long endValue = longRange[1]; if (startValue >= endValue) { return EmptyDocIdSet.getInstance(); } return new RandomAccessDocIdSet() { @Override public DocIdSetIterator iterator() throws IOException { return new ActivityRangeLongFilterIterator(longArray, indexes, startValue, endValue); } @Override public boolean get(int docId) { if (indexes[docId] == -1) return false; long val = longArray[indexes[docId]]; return val >= startValue && val < endValue && val != Long.MIN_VALUE; } }; } } }; } @Override public FacetCountCollectorSource getFacetCountCollectorSource(BrowseSelection sel, FacetSpec fspec) { throw new UnsupportedOperationException("Facets on activity columns are unsupported"); } @Override public Object[] getRawFieldValues(BoboIndexReader reader, int id) { final int[] indexes = (int[]) ((BoboIndexReader)reader).getFacetData(_name); return indexes[id] != -1 ? new Object[] {activityValues.getValue(indexes[id])} : EMPTY_OBJ_ARR; } public int getIntActivityValue(int[] facetData, int id) { if (id < 0 || id >= facetData.length) { return Integer.MIN_VALUE; } return facetData[id] != -1 ? ((ActivityIntValues)activityValues).fieldValues[facetData[id]] : Integer.MIN_VALUE; } public long getLongActivityValue(int[] facetData, int id) { if (id < 0 || id >= facetData.length) { return Long.MIN_VALUE; } return facetData[id] != -1 ? ((ActivityLongValues)activityValues).fieldValues[facetData[id]] : Long.MIN_VALUE; } public float getFloatActivityValue(int[] facetData, int id) { if (id < 0 || id >= facetData.length) { return Integer.MIN_VALUE; } return facetData[id] != -1 ? ((ActivityFloatValues)activityValues).fieldValues[facetData[id]] : Float.MIN_VALUE; } @Override public String[] getFieldValues(BoboIndexReader reader, int id) { final int[] indexes = (int[]) ((BoboIndexReader)reader).getFacetData(_name); if ( indexes[id] == -1) return EMPTY_STRING_ARR; Number value = activityValues.getValue(indexes[id]); if (value.intValue() == Integer.MIN_VALUE || value.floatValue() == Float.MIN_VALUE || value.longValue() == Long.MIN_VALUE) { return EMPTY_STRING_ARR; } return new String[] {formatter.get().format(value)} ; } @Override public DocComparatorSource getDocComparatorSource() { final int[] intArray = activityValues instanceof ActivityIntValues ? ((ActivityIntValues) activityValues).getFieldValues() : null; final float[] floatArray = activityValues instanceof ActivityFloatValues ? ((ActivityFloatValues) activityValues).getFieldValues() : null; final long[] longArray = activityValues instanceof ActivityLongValues ? ((ActivityLongValues) activityValues).getFieldValues() : null; if (intArray != null) return new DocComparatorSource() { @Override public DocComparator getComparator(IndexReader reader, int docbase) throws IOException { final int[] indexes = (int[]) ((BoboIndexReader) reader).getFacetData(_name); return new DocComparator() { @Override public Comparable<Integer> value(ScoreDoc doc) { return indexes[doc.doc] != -1 ? intArray[indexes[doc.doc]] : 0; } @Override public int compare(ScoreDoc doc1, ScoreDoc doc2) { int val1 = indexes[doc1.doc] != -1 ? intArray[indexes[doc1.doc]] : 0; int val2 = indexes[doc2.doc] != -1 ? intArray[indexes[doc2.doc]] : 0; return (val1<val2 ? -1 : (val1==val2 ? 0 : 1)); } }; } }; if (longArray != null) return new DocComparatorSource() { @Override public DocComparator getComparator(IndexReader reader, int docbase) throws IOException { final int[] indexes = (int[]) ((BoboIndexReader) reader).getFacetData(_name); return new DocComparator() { @Override public Comparable<Long> value(ScoreDoc doc) { return indexes[doc.doc] != -1 ? longArray[indexes[doc.doc]] : 0; } @Override public int compare(ScoreDoc doc1, ScoreDoc doc2) { long val1 = indexes[doc1.doc] != -1 ? longArray[indexes[doc1.doc]] : 0; long val2 = indexes[doc2.doc] != -1 ? longArray[indexes[doc2.doc]] : 0; return (val1<val2 ? -1 : (val1==val2 ? 0 : 1)); } }; } }; return new DocComparatorSource() { @Override public DocComparator getComparator(IndexReader reader, int docbase) throws IOException { final int[] indexes = (int[]) ((BoboIndexReader) reader).getFacetData(_name); return new DocComparator() { @Override public Comparable<Float> value(ScoreDoc doc) { return indexes[doc.doc] != -1 ? floatArray[indexes[doc.doc]] : 0; } @Override public int compare(ScoreDoc doc1, ScoreDoc doc2) { float val1 = indexes[doc1.doc] != -1 ? floatArray[indexes[doc1.doc]] : 0; float val2 = indexes[doc2.doc] != -1 ? floatArray[indexes[doc2.doc]] : 0; return (val1<val2 ? -1 : (val1==val2 ? 0 : 1)); } }; } }; } public static int[] parseRaw(String rangeString) { String[] ranges = FacetRangeFilter.getRangeStrings(rangeString); String lower=ranges[0]; String upper=ranges[1]; String includeLower = ranges[2]; String includeUpper = ranges[3]; int start = 0; int end = 0; if ("*".equals(lower)) { start=Integer.MIN_VALUE; } else { start = Integer.parseInt(lower); if ("false".equals(includeLower)) { start++; } } if ("*".equals(upper)) { end=Integer.MAX_VALUE; } else { end = Integer.parseInt(upper); if ("true".equals(includeUpper)) { end++; } } return new int[]{start,end}; } public static long[] parseRawLong(String rangeString) { String[] ranges = FacetRangeFilter.getRangeStrings(rangeString); String lower=ranges[0]; String upper=ranges[1]; String includeLower = ranges[2]; String includeUpper = ranges[3]; long start = 0; long end = 0; if ("*".equals(lower)) { start=Long.MIN_VALUE; } else { start = Long.parseLong(lower); if ("false".equals(includeLower)) { start++; } } if ("*".equals(upper)) { end=Long.MAX_VALUE; } else { end = Long.parseLong(upper); if ("true".equals(includeUpper)) { end++; } } return new long[]{start,end}; } }