/**
* Copyright 2016 Nikita Koksharov
*
* 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 org.redisson.mapreduce;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import org.redisson.api.RBatch;
import org.redisson.api.RExecutorService;
import org.redisson.api.RFuture;
import org.redisson.api.RMapAsync;
import org.redisson.api.RObject;
import org.redisson.api.RedissonClient;
import org.redisson.api.mapreduce.RCollator;
import org.redisson.api.mapreduce.RMapReduceExecutor;
import org.redisson.api.mapreduce.RReducer;
import org.redisson.client.codec.Codec;
import org.redisson.connection.ConnectionManager;
import org.redisson.misc.RPromise;
import org.redisson.misc.TransferListener;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
* @param <M> mapper type
* @param <VIn> input value type
* @param <KOut> output key type
* @param <VOut> output value type
*/
abstract class MapReduceExecutor<M, VIn, KOut, VOut> implements RMapReduceExecutor<VIn, KOut, VOut> {
private final RedissonClient redisson;
private final RExecutorService executorService;
final String resultMapName;
final Codec objectCodec;
final String objectName;
final Class<?> objectClass;
private ConnectionManager connectionManager;
RReducer<KOut, VOut> reducer;
M mapper;
long timeout;
public MapReduceExecutor(RObject object, RedissonClient redisson, ConnectionManager connectionManager) {
this.objectName = object.getName();
this.objectCodec = object.getCodec();
this.objectClass = object.getClass();
this.redisson = redisson;
UUID id = UUID.randomUUID();
this.resultMapName = object.getName() + ":result:" + id;
this.executorService = redisson.getExecutorService(RExecutorService.MAPREDUCE_NAME);
this.connectionManager = connectionManager;
}
protected void check(Object task) {
if (task == null) {
throw new NullPointerException("Task is not defined");
}
if (task.getClass().isAnonymousClass()) {
throw new IllegalArgumentException("Task can't be created using anonymous class");
}
if (task.getClass().isMemberClass()
&& !Modifier.isStatic(task.getClass().getModifiers())) {
throw new IllegalArgumentException("Task class is an inner class and it should be static");
}
}
@Override
public Map<KOut, VOut> execute() {
return connectionManager.getCommandExecutor().get(executeAsync());
}
@Override
public RFuture<Map<KOut, VOut>> executeAsync() {
final RPromise<Map<KOut, VOut>> promise = connectionManager.newPromise();
final RFuture<Void> future = executeMapperAsync(resultMapName, null);
addCancelHandling(promise, future);
future.addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
RBatch batch = redisson.createBatch();
RMapAsync<KOut, VOut> resultMap = batch.getMap(resultMapName, objectCodec);
resultMap.readAllMapAsync().addListener(new TransferListener<Map<KOut, VOut>>(promise));
resultMap.deleteAsync();
batch.executeAsync();
}
});
return promise;
}
private <T> void addCancelHandling(final RPromise<T> promise, final RFuture<?> future) {
promise.addListener(new FutureListener<T>() {
@Override
public void operationComplete(Future<T> f) throws Exception {
if (promise.isCancelled()) {
future.cancel(true);
}
}
});
}
@Override
public void execute(String resultMapName) {
connectionManager.getCommandExecutor().get(executeAsync(resultMapName));
}
@Override
public RFuture<Void> executeAsync(String resultMapName) {
return executeMapperAsync(resultMapName, null);
}
private <R> RFuture<R> executeMapperAsync(String resultMapName, RCollator<KOut, VOut, R> collator) {
if (mapper == null) {
throw new NullPointerException("Mapper is not defined");
}
if (reducer == null) {
throw new NullPointerException("Reducer is not defined");
}
Callable<Object> task = createTask(resultMapName, (RCollator<KOut, VOut, Object>) collator);
return (RFuture<R>) executorService.submit(task);
}
protected abstract Callable<Object> createTask(String resultMapName, RCollator<KOut, VOut, Object> collator);
@Override
public <R> R execute(RCollator<KOut, VOut, R> collator) {
return connectionManager.getCommandExecutor().get(executeAsync(collator));
}
@Override
public <R> RFuture<R> executeAsync(final RCollator<KOut, VOut, R> collator) {
check(collator);
return executeMapperAsync(resultMapName, collator);
}
}