package com.devicehive.dao.riak;
/*
* #%L
* DeviceHive Dao Riak Implementation
* %%
* Copyright (C) 2016 DataArt
* %%
* 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.
* #L%
*/
import com.basho.riak.client.api.RiakClient;
import com.basho.riak.client.api.cap.Quorum;
import com.basho.riak.client.api.cap.UnresolvedConflictException;
import com.basho.riak.client.api.commands.datatypes.CounterUpdate;
import com.basho.riak.client.api.commands.datatypes.UpdateCounter;
import com.basho.riak.client.api.commands.datatypes.UpdateDatatype;
import com.basho.riak.client.api.commands.indexes.*;
import com.basho.riak.client.api.commands.kv.DeleteValue;
import com.basho.riak.client.api.commands.kv.FetchValue;
import com.basho.riak.client.api.commands.kv.MultiFetch;
import com.basho.riak.client.api.commands.mapreduce.BucketMapReduce;
import com.basho.riak.client.core.RiakFuture;
import com.basho.riak.client.core.query.Location;
import com.basho.riak.client.core.query.Namespace;
import com.basho.riak.client.core.query.functions.Function;
import com.devicehive.application.RiakQuorum;
import com.devicehive.configuration.Constants;
import com.devicehive.exceptions.HivePersistenceLayerException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class RiakGenericDao {
protected static enum FilterOperator {
EQUAL("="), MORE(">"), LESS("<"),
MORE_EQUAL(">="), LESS_EQUAL("<="), NOT_EQUAL("!="),
REGEX("regex"), IN("in"), CONTAINS("contains");
private final String value;
private FilterOperator(String value) {
this.value = value;
}
}
protected static enum SortOrder {
ASC("asc"), DESC("desc");
private final String value;
private SortOrder(String value) {
this.value = value;
}
}
@Autowired
protected RiakClient client;
@Autowired
protected RiakQuorum quorum;
private final String MAP_REDUCE_FUNCTIONS_MODULE = "dhmr";
protected final Function REDUCE_SORT = Function.newErlangFunction(MAP_REDUCE_FUNCTIONS_MODULE, "reduce_sort");
protected final Function REDUCE_PAGINATION = Function.newErlangFunction(MAP_REDUCE_FUNCTIONS_MODULE, "reduce_pagination_filter");
protected final Function REDUCE_ADD_INDEX = Function.newErlangFunction(MAP_REDUCE_FUNCTIONS_MODULE, "reduce_add_index");
protected final Function REDUCE_DELETE_INDEX = Function.newErlangFunction(MAP_REDUCE_FUNCTIONS_MODULE, "reduce_delete_index");
protected final Function REDUCE_FILTER = Function.newErlangFunction(MAP_REDUCE_FUNCTIONS_MODULE, "reduce_filter");
protected final Function MAP_VALUES = Function.newErlangFunction(MAP_REDUCE_FUNCTIONS_MODULE, "map_values");
protected Long getId(Location location) {
return getId(location, 1);
}
protected Long getId(Location location, int count) {
CounterUpdate cu = new CounterUpdate(count);
UpdateCounter update = new UpdateCounter.Builder(location, cu)
.withOption(UpdateDatatype.Option.PW, Quorum.allQuorum())
.withReturnDatatype(true).build();
UpdateCounter.Response response;
try {
response = client.execute(update);
} catch (ExecutionException | InterruptedException e) {
throw new HivePersistenceLayerException(String.format("Unable to generate id for %s", location), e);
}
return response.getDatatype().view();
}
protected BucketMapReduce.Builder addPaging(BucketMapReduce.Builder builder, Integer take, Integer skip) {
if (take != null) {
int[] args = new int[2];
args[0] = skip != null ? skip : 0;
args[1] = args[0] + take;
return builder.withReducePhase(Function.newNamedJsFunction("Riak.reduceSlice"), args, true);
} else {
return builder;
}
}
protected BucketMapReduce.Builder addReducePaging(BucketMapReduce.Builder builder, Boolean keep, Integer count, Integer skip) {
if (skip == null) {
skip = 0;
}
if (count == null) {
count = Constants.DEFAULT_TAKE;
}
builder.withReducePhase(REDUCE_ADD_INDEX)
.withReducePhase(REDUCE_PAGINATION, new Object[]{skip, count})
.withReducePhase(REDUCE_DELETE_INDEX, true);
return builder;
}
protected BucketMapReduce.Builder addReducePaging(BucketMapReduce.Builder builder, Integer take, Integer skip) {
return addReducePaging(builder, true, take, skip);
}
protected BucketMapReduce.Builder addReduceFilter(BucketMapReduce.Builder builder, Boolean keep, String fieldName, FilterOperator operation, Object value) {
if ((fieldName == null) || (operation == null) || (value == null)) {
return builder;
} else {
return builder.withReducePhase(REDUCE_FILTER, new Object[]{fieldName, operation.value, value}, keep);
}
}
protected BucketMapReduce.Builder addReduceFilter(BucketMapReduce.Builder builder, String fieldName, FilterOperator operation, Object value) {
return addReduceFilter(builder, false, fieldName, operation, value);
}
protected BucketMapReduce.Builder addReduceSort(BucketMapReduce.Builder builder, Boolean keep, String sortField, SortOrder order) {
if ((sortField == null) || (order == null)) {
return builder;
} else {
return builder.withReducePhase(REDUCE_SORT, new Object[]{sortField, order.value}, keep);
}
}
protected BucketMapReduce.Builder addReduceSort(BucketMapReduce.Builder builder, String sortField, SortOrder order) {
return addReduceSort(builder, false, sortField, order);
}
protected BucketMapReduce.Builder addReduceSort(BucketMapReduce.Builder builder, Boolean keep, String sortField, Boolean isSortOrderAsc) {
SortOrder sortOrder;
if (isSortOrderAsc == null) {
sortOrder = SortOrder.ASC;
} else {
sortOrder = (isSortOrderAsc) ? SortOrder.ASC : SortOrder.DESC;
}
if ((sortField == null) || (sortField.isEmpty())) {
return addReduceSort(builder, keep, "id", sortOrder);
} else {
return addReduceSort(builder, keep, sortField, sortOrder);
}
}
protected BucketMapReduce.Builder addReduceSort(BucketMapReduce.Builder builder, String sortField, Boolean isSortOrderAsc) {
return addReduceSort(builder, false, sortField, isSortOrderAsc);
}
protected BucketMapReduce.Builder addMapValues(BucketMapReduce.Builder builder, Boolean keep) {
return builder.withMapPhase(MAP_VALUES, keep);
}
protected BucketMapReduce.Builder addMapValues(BucketMapReduce.Builder builder) {
return addMapValues(builder, false);
}
protected <T> T getOrNull(FetchValue.Response response, Class<T> clazz) throws UnresolvedConflictException {
if (response.hasValues()) {
return response.getValue(clazz);
}
return null;
}
protected int deleteById(Long id, Namespace ns) throws ExecutionException, InterruptedException {
Location location = new Location(ns, String.valueOf(id));
DeleteValue deleteOp = new DeleteValue.Builder(location).build();
client.execute(deleteOp);
return 1;
}
protected <T> T findBySecondaryIndex(String indexName, String value,
Namespace namespace, Class<T> clazz) {
if ((indexName == null) || (value == null)) {
return null;
}
BinIndexQuery biq = new BinIndexQuery.Builder(namespace, indexName, value).build();
try {
BinIndexQuery.Response response = client.execute(biq);
return fetchOne(response, clazz);
} catch (ExecutionException | InterruptedException e) {
throw new HivePersistenceLayerException("Cannot find by identity.", e);
}
}
protected <T> List<T> findAllBySecondaryIndex(String indexName, String value,
Namespace namespace, Class<T> clazz) {
if ((indexName == null) || (value == null)) {
return null;
}
BinIndexQuery biq = new BinIndexQuery.Builder(namespace, indexName, value).build();
try {
BinIndexQuery.Response response = client.execute(biq);
return fetchMultiple(response, clazz);
} catch (ExecutionException | InterruptedException e) {
throw new HivePersistenceLayerException("Cannot find by identity.", e);
}
}
protected <T, R> List<T> fetchMultiple(SecondaryIndexQuery.Response<R> response, Class<T> clazz)
throws ExecutionException, InterruptedException {
List<?> entries = response.getEntries();
if (entries.isEmpty()) {
return Collections.emptyList();
} else {
final List<Location> locations = entries.stream()
.map(entry -> ((SecondaryIndexQuery.Response.Entry<R>) entry).getRiakObjectLocation()).collect(Collectors.toList());
return fetchMultipleByLocations(locations, clazz);
}
}
protected <T, R> T fetchOne(SecondaryIndexQuery.Response<R> response, Class<T> clazz)
throws ExecutionException, InterruptedException {
List<?> entries = response.getEntries();
if (entries.isEmpty()) {
return null;
} else {
Location location = ((SecondaryIndexQuery.Response.Entry<R>) entries.get(0)).getRiakObjectLocation();
return fetchByLocation(location, clazz);
}
}
private <T> T fetchByLocation(Location location, Class<T> clazz) throws ExecutionException, InterruptedException {
FetchValue fv = new FetchValue.Builder(location).build();
FetchValue.Response response = client.execute(fv);
return response.getValue(clazz);
}
private <T> List<T> fetchMultipleByLocations(List<Location> locations, Class<T> clazz) throws ExecutionException, InterruptedException {
List<T> result = new ArrayList<>();
MultiFetch multiFetch = new MultiFetch.Builder()
.addLocations(locations)
.withOption(quorum.getReadQuorumOption(), quorum.getReadQuorum())
.build();
MultiFetch.Response mfr = client.execute(multiFetch);
for (RiakFuture<FetchValue.Response, Location> f : mfr.getResponses()) {
FetchValue.Response resp = f.get();
result.add(resp.getValue(clazz));
}
return result;
}
}