/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.scheduledexecutor.impl;
import com.hazelcast.core.HazelcastInstanceAware;
import com.hazelcast.core.Member;
import com.hazelcast.core.PartitionAware;
import com.hazelcast.internal.cluster.ClusterService;
import com.hazelcast.mapreduce.impl.HashMapAdapter;
import com.hazelcast.nio.Address;
import com.hazelcast.scheduledexecutor.IScheduledExecutorService;
import com.hazelcast.scheduledexecutor.IScheduledFuture;
import com.hazelcast.scheduledexecutor.NamedTask;
import com.hazelcast.scheduledexecutor.ScheduledTaskHandler;
import com.hazelcast.scheduledexecutor.impl.operations.GetAllScheduledOnMemberOperation;
import com.hazelcast.scheduledexecutor.impl.operations.GetAllScheduledOnPartitionOperationFactory;
import com.hazelcast.scheduledexecutor.impl.operations.ScheduleTaskOperation;
import com.hazelcast.scheduledexecutor.impl.operations.ShutdownOperation;
import com.hazelcast.spi.AbstractDistributedObject;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.OperationService;
import com.hazelcast.spi.partition.IPartitionService;
import com.hazelcast.util.FutureUtil;
import com.hazelcast.util.UuidUtil;
import com.hazelcast.util.function.Supplier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import static com.hazelcast.scheduledexecutor.impl.DistributedScheduledExecutorService.SERVICE_NAME;
import static com.hazelcast.util.ExceptionUtil.rethrow;
import static com.hazelcast.util.FutureUtil.logAllExceptions;
import static com.hazelcast.util.FutureUtil.waitWithDeadline;
import static com.hazelcast.util.Preconditions.checkNotNull;
@SuppressWarnings({"unchecked", "checkstyle:methodcount"})
public class ScheduledExecutorServiceProxy
extends AbstractDistributedObject<DistributedScheduledExecutorService>
implements IScheduledExecutorService {
private static final int GET_ALL_SCHEDULED_TIMEOUT = 10;
private static final int SHUTDOWN_TIMEOUT = 10;
private static final FutureUtil.ExceptionHandler WHILE_SHUTDOWN_EXCEPTION_HANDLER =
logAllExceptions("Exception while ScheduledExecutor Service shutdown", Level.FINEST);
private final String name;
ScheduledExecutorServiceProxy(String name, NodeEngine nodeEngine,
DistributedScheduledExecutorService service) {
super(nodeEngine, service);
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public String getServiceName() {
return DistributedScheduledExecutorService.SERVICE_NAME;
}
@Override
public IScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
ScheduledRunnableAdapter<?> callable = createScheduledRunnableAdapter(command);
return schedule(callable, delay, unit);
}
@Override
public <V> IScheduledFuture<V> schedule(Callable<V> command, long delay, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
String name = extractNameOrGenerateOne(command);
int partitionId = getTaskOrKeyPartitionId(command, name);
TaskDefinition<V> definition = new TaskDefinition<V>(
TaskDefinition.Type.SINGLE_RUN, name, command, delay, unit);
return submitOnPartitionSync(name, new ScheduleTaskOperation(getName(), definition), partitionId);
}
@Override
public IScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
String name = extractNameOrGenerateOne(command);
int partitionId = getTaskOrKeyPartitionId(command, name);
ScheduledRunnableAdapter<?> adapter = createScheduledRunnableAdapter(command);
TaskDefinition definition = new TaskDefinition(
TaskDefinition.Type.AT_FIXED_RATE, name, adapter, initialDelay, period, unit);
return submitOnPartitionSync(name, new ScheduleTaskOperation(getName(), definition), partitionId);
}
@Override
public IScheduledFuture<?> scheduleOnMember(Runnable command, Member member, long delay, TimeUnit unit) {
checkNotNull(member, "Member is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
return scheduleOnMembers(command, Collections.singleton(member), delay, unit).get(member);
}
@Override
public <V> IScheduledFuture<V> scheduleOnMember(Callable<V> command, Member member, long delay, TimeUnit unit) {
checkNotNull(member, "Member is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
return scheduleOnMembers(command, Collections.singleton(member), delay, unit).get(member);
}
@Override
public IScheduledFuture<?> scheduleOnMemberAtFixedRate(Runnable command, Member member, long initialDelay,
long period, TimeUnit unit) {
checkNotNull(member, "Member is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
return scheduleOnMembersAtFixedRate(command, Collections.singleton(member), initialDelay, period, unit).get(member);
}
@Override
public IScheduledFuture<?> scheduleOnKeyOwner(Runnable command, Object key, long delay, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
ScheduledRunnableAdapter<?> callable = createScheduledRunnableAdapter(command);
return scheduleOnKeyOwner(callable, key, delay, unit);
}
@Override
public <V> IScheduledFuture<V> scheduleOnKeyOwner(Callable<V> command, Object key, long delay, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(key, "Key is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
String name = extractNameOrGenerateOne(command);
int partitionId = getKeyPartitionId(key);
TaskDefinition definition = new TaskDefinition(
TaskDefinition.Type.SINGLE_RUN, name, command, delay, unit);
return submitOnPartitionSync(name, new ScheduleTaskOperation(getName(), definition), partitionId);
}
@Override
public IScheduledFuture<?> scheduleOnKeyOwnerAtFixedRate(Runnable command, Object key, long initialDelay,
long period, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(key, "Key is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
String name = extractNameOrGenerateOne(command);
int partitionId = getKeyPartitionId(key);
ScheduledRunnableAdapter<?> adapter = createScheduledRunnableAdapter(command);
TaskDefinition definition = new TaskDefinition(
TaskDefinition.Type.AT_FIXED_RATE, name, adapter, initialDelay, period, unit);
return submitOnPartitionSync(name, new ScheduleTaskOperation(getName(), definition), partitionId);
}
@Override
public Map<Member, IScheduledFuture<?>> scheduleOnAllMembers(Runnable command, long delay, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
return scheduleOnMembers(command, getNodeEngine().getClusterService().getMembers(), delay, unit);
}
@Override
public <V> Map<Member, IScheduledFuture<V>> scheduleOnAllMembers(Callable<V> command, long delay, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
return scheduleOnMembers(command, getNodeEngine().getClusterService().getMembers(), delay, unit);
}
@Override
public Map<Member, IScheduledFuture<?>> scheduleOnAllMembersAtFixedRate(Runnable command, long initialDelay, long period,
TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
return scheduleOnMembersAtFixedRate(command, getNodeEngine().getClusterService().getMembers(),
initialDelay, period, unit);
}
@Override
public Map<Member, IScheduledFuture<?>> scheduleOnMembers(Runnable command, Collection<Member> members, long delay,
TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(members, "Members is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
ScheduledRunnableAdapter callable = createScheduledRunnableAdapter(command);
return (Map<Member, IScheduledFuture<?>>) scheduleOnMembers(callable, members, delay, unit);
}
@Override
public <V> Map<Member, IScheduledFuture<V>> scheduleOnMembers(Callable<V> command, Collection<Member> members, long delay,
TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(members, "Members is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
String name = extractNameOrGenerateOne(command);
Map<Member, IScheduledFuture<V>> futures = new HashMap<Member, IScheduledFuture<V>>();
for (Member member : members) {
TaskDefinition<V> definition = new TaskDefinition<V>(
TaskDefinition.Type.SINGLE_RUN, name, command, delay, unit);
futures.put(member,
(IScheduledFuture<V>) submitOnMemberSync(name,
new ScheduleTaskOperation(getName(), definition), member));
}
return futures;
}
@Override
public Map<Member, IScheduledFuture<?>> scheduleOnMembersAtFixedRate(Runnable command, Collection<Member> members,
long initialDelay, long period, TimeUnit unit) {
checkNotNull(command, "Command is null");
checkNotNull(members, "Members is null");
checkNotNull(unit, "Unit is null");
attachHazelcastInstance(command);
String name = extractNameOrGenerateOne(command);
ScheduledRunnableAdapter<?> adapter = createScheduledRunnableAdapter(command);
Map<Member, IScheduledFuture<?>> futures = new HashMapAdapter<Member, IScheduledFuture<?>>();
for (Member member : members) {
TaskDefinition definition = new TaskDefinition(
TaskDefinition.Type.AT_FIXED_RATE, name, adapter, initialDelay, period, unit);
futures.put(member, submitOnMemberSync(name, new ScheduleTaskOperation(getName(), definition), member));
}
return futures;
}
@Override
public IScheduledFuture<?> getScheduledFuture(ScheduledTaskHandler handler) {
checkNotNull(handler, "Handler is null");
ScheduledFutureProxy proxy = new ScheduledFutureProxy(handler);
attachHazelcastInstance(proxy);
return proxy;
}
@Override
public <V> Map<Member, List<IScheduledFuture<V>>> getAllScheduledFutures() {
Map<Member, List<IScheduledFuture<V>>> accumulator = new LinkedHashMap<Member, List<IScheduledFuture<V>>>();
retrieveAllPartitionOwnedScheduled(accumulator);
retrieveAllMemberOwnedScheduled(accumulator);
return accumulator;
}
@Override
public void shutdown() {
NodeEngine nodeEngine = getNodeEngine();
Collection<Member> members = nodeEngine.getClusterService().getMembers();
OperationService operationService = nodeEngine.getOperationService();
Collection<Future> calls = new LinkedList<Future>();
for (Member member : members) {
if (member.localMember()) {
getService().shutdownExecutor(name);
} else {
Operation op = new ShutdownOperation(name);
calls.add(operationService.invokeOnTarget(SERVICE_NAME, op, member.getAddress()));
}
}
waitWithDeadline(calls, SHUTDOWN_TIMEOUT, TimeUnit.SECONDS, WHILE_SHUTDOWN_EXCEPTION_HANDLER);
}
private <V> void retrieveAllMemberOwnedScheduled(Map<Member, List<IScheduledFuture<V>>> accumulator) {
try {
InvokeOnMembers invokeOnMembers = new InvokeOnMembers(getNodeEngine(), getServiceName(),
new GetAllScheduledOnMemberOperationFactory(name),
getNodeEngine().getClusterService().getMembers());
accumulateTaskHandlersAsScheduledFutures(accumulator, invokeOnMembers.invoke());
} catch (Exception e) {
throw rethrow(e);
}
}
private <V> void retrieveAllPartitionOwnedScheduled(Map<Member, List<IScheduledFuture<V>>> accumulator) {
try {
accumulateTaskHandlersAsScheduledFutures(accumulator, getNodeEngine().getOperationService().invokeOnAllPartitions(
getServiceName(), new GetAllScheduledOnPartitionOperationFactory(name)));
} catch (Throwable t) {
throw rethrow(t);
}
}
@SuppressWarnings("unchecked")
private <V> void accumulateTaskHandlersAsScheduledFutures(Map<Member, List<IScheduledFuture<V>>> accumulator,
Map<?, ?> taskHandlersMap) {
ClusterService clusterService = getNodeEngine().getClusterService();
IPartitionService partitionService = getNodeEngine().getPartitionService();
for (Map.Entry<?, ?> entry : taskHandlersMap.entrySet()) {
Member owner;
Object key = entry.getKey();
if (key instanceof Number) {
owner = clusterService.getMember(partitionService.getPartitionOwner((Integer) key));
} else {
owner = (Member) key;
}
List<ScheduledTaskHandler> handlers = (List<ScheduledTaskHandler>) entry.getValue();
List<IScheduledFuture<V>> futures = new ArrayList<IScheduledFuture<V>>();
for (ScheduledTaskHandler handler : handlers) {
IScheduledFuture future = new ScheduledFutureProxy(handler);
attachHazelcastInstance(future);
futures.add(future);
}
if (accumulator.containsKey(owner)) {
List<IScheduledFuture<V>> memberFutures = accumulator.get(owner);
memberFutures.addAll(futures);
} else {
accumulator.put(owner, futures);
}
}
}
private <T> ScheduledRunnableAdapter<T> createScheduledRunnableAdapter(Runnable command) {
checkNotNull(command, "Command can't be null");
return new ScheduledRunnableAdapter<T>(command);
}
private <V> IScheduledFuture<V> createFutureProxy(int partitionId, String taskName) {
ScheduledFutureProxy proxy = new ScheduledFutureProxy(
ScheduledTaskHandlerImpl.of(partitionId, getName(), taskName));
proxy.setHazelcastInstance(getNodeEngine().getHazelcastInstance());
return proxy;
}
private <V> IScheduledFuture<V> createFutureProxy(Address address, String taskName) {
ScheduledFutureProxy proxy = new ScheduledFutureProxy(
ScheduledTaskHandlerImpl.of(address, getName(), taskName));
proxy.setHazelcastInstance(getNodeEngine().getHazelcastInstance());
return proxy;
}
private int getKeyPartitionId(Object key) {
return getNodeEngine().getPartitionService().getPartitionId(key);
}
private int getTaskOrKeyPartitionId(Callable task, Object key) {
if (task instanceof PartitionAware) {
Object newKey = ((PartitionAware) task).getPartitionKey();
if (newKey != null) {
key = newKey;
}
}
return getKeyPartitionId(key);
}
private int getTaskOrKeyPartitionId(Runnable task, Object key) {
if (task instanceof PartitionAware) {
Object newKey = ((PartitionAware) task).getPartitionKey();
if (newKey != null) {
key = newKey;
}
}
return getKeyPartitionId(key);
}
private String extractNameOrGenerateOne(Object command) {
String name = null;
if (command instanceof NamedTask) {
name = ((NamedTask) command).getName();
}
return name != null ? name : UuidUtil.newUnsecureUuidString();
}
private <V> IScheduledFuture<V> submitOnPartitionSync(String taskName, Operation op, int partitionId) {
op.setPartitionId(partitionId);
invokeOnPartition(op).join();
return createFutureProxy(partitionId, taskName);
}
private <V> IScheduledFuture<V> submitOnMemberSync(String taskName, Operation op, Member member) {
Address address = member.getAddress();
getOperationService().invokeOnTarget(getServiceName(), op, address).join();
return createFutureProxy(address, taskName);
}
private void attachHazelcastInstance(Object object) {
if (object instanceof HazelcastInstanceAware) {
((HazelcastInstanceAware) object).setHazelcastInstance(getNodeEngine().getHazelcastInstance());
}
}
private static class GetAllScheduledOnMemberOperationFactory implements Supplier<Operation> {
private final String schedulerName;
GetAllScheduledOnMemberOperationFactory(String schedulerName) {
this.schedulerName = schedulerName;
}
@Override
public Operation get() {
return new GetAllScheduledOnMemberOperation(schedulerName);
}
}
}