/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.usergrid.persistence.graph.serialization.impl.shard.impl; import java.util.*; import org.apache.usergrid.persistence.core.astyanax.ColumnParser; import org.apache.usergrid.persistence.core.astyanax.ColumnSearch; import org.apache.usergrid.persistence.core.astyanax.ScopedRowKey; import org.apache.usergrid.persistence.core.scope.ApplicationScope; import org.apache.usergrid.persistence.core.shard.SmartShard; import org.apache.usergrid.persistence.graph.SearchByEdgeType; import org.apache.usergrid.persistence.graph.serialization.impl.shard.Shard; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.netflix.astyanax.Serializer; import com.netflix.astyanax.model.Column; import com.netflix.astyanax.util.RangeBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Searcher to be used when performing the search. Performs I/O transformation as well as parsing for the iterator. If * there are more row keys available to seek, the iterator will return true * * @param <R> The row type * @param <C> The column type * @param <T> The parsed return type */ public abstract class EdgeSearcher<R, C, T> implements ColumnParser<C, T>, ColumnSearch<T>{ private static final Logger logger = LoggerFactory.getLogger( EdgeSearcher.class ); protected final Optional<T> last; protected final Optional<Long> lastTimestamp; protected final long maxTimestamp; protected final ApplicationScope scope; protected final Collection<Shard> shards; protected final SearchByEdgeType.Order order; protected final Comparator<T> comparator; protected EdgeSearcher( final ApplicationScope scope, final Collection<Shard> shards, final SearchByEdgeType.Order order, final Comparator<T> comparator, final long maxTimestamp, final Optional<T> last, final Optional<Long> lastTimestamp) { Preconditions.checkArgument(shards.size() > 0 , "Cannot search with no possible shards"); this.scope = scope; this.maxTimestamp = maxTimestamp; this.order = order; this.shards = shards; this.last = last; this.lastTimestamp = lastTimestamp; this.comparator = comparator; } public List<ScopedRowKey<R>> getRowKeys() { if(logger.isTraceEnabled()) { logger.trace("Shards: {}", shards); } List<ScopedRowKey<R>> rowKeys = new ArrayList<>(shards.size()); for(Shard shard : shards){ final ScopedRowKey< R> rowKey = ScopedRowKey .fromKey( scope.getApplication(), generateRowKey(shard.getShardIndex() ) ); rowKeys.add( rowKey ); } if(logger.isTraceEnabled()) { logger.trace("Resulting Shards: {}", rowKeys); } return rowKeys; } public List<SmartShard> getRowKeysWithShardEnd(){ if(logger.isTraceEnabled()) { logger.trace("Shards: {}", shards); } final List<SmartShard> rowKeysWithShardEnd = new ArrayList<>(shards.size()); for(Shard shard : shards){ final ScopedRowKey< R> rowKey = ScopedRowKey .fromKey( scope.getApplication(), generateRowKey(shard.getShardIndex() ) ); final T shardEnd; if(shard.getShardEnd().isPresent()){ shardEnd = createEdge((C) shard.getShardEnd().get(), false); // convert DirectedEdge to Edge }else{ shardEnd = null; } rowKeysWithShardEnd.add(new SmartShard(rowKey, shard.getShardIndex(), shardEnd, shard.isDeleted())); } if(logger.isTraceEnabled()) { logger.trace("Resulting Smart Shards: {}", rowKeysWithShardEnd); } return rowKeysWithShardEnd; } @Override public boolean skipFirst( final T first ) { if(!last.isPresent()){ return false; } return last.get().equals( first ); } @Override public T parseColumn( final Column<C> column ) { final C edge = column.getName(); return createEdge( edge, column.getBooleanValue() ); } @Override public void buildRange(final RangeBuilder rangeBuilder, final T start, T end) { final boolean ascending = order == SearchByEdgeType.Order.ASCENDING; if ( start != null){ C sourceEdge = createColumn( start ); if(ascending && last.isPresent() && comparator.compare(last.get(), start) < 0){ sourceEdge = createColumn( last.get() ); }else if (!ascending && last.isPresent() && comparator.compare(last.get(), start) > 0){ sourceEdge = createColumn( last.get() ); } rangeBuilder.setStart( sourceEdge, getSerializer() ); }else{ setTimeScan( rangeBuilder ); } if( end != null){ C endEdge = createColumn( end ); rangeBuilder.setEnd( endEdge, getSerializer() ); } setRangeOptions( rangeBuilder ); } @Override public void buildRange( final RangeBuilder rangeBuilder ) { //set our start range since it was supplied to us if ( last.isPresent() ) { C sourceEdge = createColumn( last.get() ); rangeBuilder.setStart( sourceEdge, getSerializer() ); }else { setTimeScan( rangeBuilder ); } setRangeOptions( rangeBuilder ); } private void setRangeOptions(final RangeBuilder rangeBuilder){ //if we're ascending, this is opposite what cassandra sorts, so set the reversed flag final boolean reversed = order == SearchByEdgeType.Order.ASCENDING; rangeBuilder.setReversed( reversed ); } /** * Get the comparator * @return */ public Comparator<T> getComparator() { return comparator; } public SearchByEdgeType.Order getOrder(){ return order; } public Optional<Long> getLastTimestamp() { return lastTimestamp; } /** * Get the column's serializer */ protected abstract Serializer<C> getSerializer(); /** * Create a row key for this search to use * * @param shard The shard to use in the row key */ protected abstract R generateRowKey( final long shard ); /** * Set the start column to begin searching from. The last is provided */ protected abstract C createColumn( final T last ); /** * Set the time scan into the range builder * @param rangeBuilder */ protected abstract void setTimeScan(final RangeBuilder rangeBuilder); /** * Create an edge to return to the user based on the directed edge provided * * @param column The column name * @param marked The marked flag in the column value */ protected abstract T createEdge( final C column, final boolean marked ); }