/*
* 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.ignite.compute.gridify.aop;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.compute.ComputeJob;
import org.apache.ignite.compute.ComputeJobResult;
import org.apache.ignite.compute.ComputeLoadBalancer;
import org.apache.ignite.compute.ComputeTaskAdapter;
import org.apache.ignite.compute.ComputeTaskContinuousMapper;
import org.apache.ignite.compute.ComputeTaskSession;
import org.apache.ignite.compute.gridify.GridifyArgument;
import org.apache.ignite.compute.gridify.GridifyNodeFilter;
import org.apache.ignite.compute.gridify.GridifySetToSet;
import org.apache.ignite.compute.gridify.GridifySetToValue;
import org.apache.ignite.internal.util.gridify.GridifyArgumentBuilder;
import org.apache.ignite.internal.util.gridify.GridifyJobAdapter;
import org.apache.ignite.internal.util.gridify.GridifyRangeArgument;
import org.apache.ignite.internal.util.lang.GridPeerDeployAware;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.resources.LoadBalancerResource;
import org.apache.ignite.resources.LoggerResource;
import org.apache.ignite.resources.TaskContinuousMapperResource;
import org.apache.ignite.resources.TaskSessionResource;
import static org.apache.ignite.internal.util.gridify.GridifyUtils.UNKNOWN_SIZE;
/**
* Default gridify task which simply executes a method on remote node.
* <p/>
* See {@link org.apache.ignite.compute.gridify.Gridify} documentation for more information about execution of {@code gridified} methods.
* @see GridifySetToSet
* @see GridifySetToValue
*/
public class GridifyDefaultRangeTask extends ComputeTaskAdapter<GridifyRangeArgument, Collection<?>>
implements GridPeerDeployAware {
/** */
private static final long serialVersionUID = 0L;
/** Deploy class. */
@SuppressWarnings({"TransientFieldNotInitialized"})
private final transient Class<?> p2pCls;
/** Grid instance. */
@IgniteInstanceResource
private Ignite ignite;
/** Grid task session. */
@TaskSessionResource
private ComputeTaskSession ses;
/** Grid logger. */
@LoggerResource
private IgniteLogger log;
/** Load balancer. */
@LoadBalancerResource
private ComputeLoadBalancer balancer;
/** */
@SuppressWarnings({"UnusedDeclaration"})
@TaskContinuousMapperResource
private ComputeTaskContinuousMapper mapper;
/** */
private GridifyNodeFilter nodeFilter;
/** */
private int splitSize;
/** */
private int threshold;
/** */
private boolean limitedSplit;
/**
* @param cls Deployment class.
* @param nodeFilter Predicate node filter.
* @param threshold Parameter that defines the minimal value below which the
* execution will NOT be grid-enabled.
* @param splitSize Split size for job arguments.
* @param limitedSplit Indicates limitation for split algorithm.
*/
public GridifyDefaultRangeTask(Class<?> cls, GridifyNodeFilter nodeFilter, int threshold, int splitSize,
boolean limitedSplit) {
assert cls != null;
p2pCls = cls;
this.nodeFilter = nodeFilter;
this.threshold = threshold;
this.splitSize = splitSize;
this.limitedSplit = limitedSplit;
}
/** {@inheritDoc} */
@Override public Class<?> deployClass() {
return p2pCls;
}
/** {@inheritDoc} */
@Override public ClassLoader classLoader() {
return U.detectClassLoader(p2pCls);
}
/** {@inheritDoc} */
@Override public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> subgrid, GridifyRangeArgument arg) {
assert !subgrid.isEmpty() : "Subgrid should not be empty: " + subgrid;
assert ignite != null : "Grid instance could not be injected";
if (splitSize < threshold && splitSize != 0 && threshold != 0) {
throw new IgniteException("Incorrect Gridify annotation parameters. Value for parameter " +
"'splitSize' should not be less than parameter 'threshold' [splitSize=" + splitSize +
", threshold=" + threshold + ']');
}
Collection<ClusterNode> exclNodes = new LinkedList<>();
// Filter nodes.
if (nodeFilter != null) {
for (ClusterNode node : subgrid) {
if (!nodeFilter.apply(node, ses))
exclNodes.add(node);
}
if (exclNodes.size() == subgrid.size())
throw new IgniteException("Failed to execute on grid where all nodes excluded.");
}
int inputPerNode = splitSize;
// Calculate input elements size per node for default annotation splitSize parameter.
if (splitSize <= 0) {
// For iterable input splitSize will be assigned with threshold value.
if (threshold > 0 && arg.getInputSize() == UNKNOWN_SIZE)
inputPerNode = threshold;
// Otherwise, splitSize equals (inputSize / nodesCount)
else {
assert arg.getInputSize() != UNKNOWN_SIZE;
int gridSize = subgrid.size() - exclNodes.size();
gridSize = (gridSize <= 0 ? subgrid.size() : gridSize);
inputPerNode = calculateInputSizePerNode(gridSize, arg.getInputSize(), threshold, limitedSplit);
if (log.isDebugEnabled()) {
log.debug("Calculated input elements size per node [inputSize=" + arg.getInputSize() +
", gridSize=" + gridSize + ", threshold=" + threshold +
", limitedSplit=" + limitedSplit + ", inputPerNode=" + inputPerNode + ']');
}
}
}
GridifyArgumentBuilder argBuilder = new GridifyArgumentBuilder();
Iterator<?> inputIter = arg.getInputIterator();
while (inputIter.hasNext()) {
Collection<Object> nodeInput = new LinkedList<>();
for (int i = 0; i < inputPerNode && inputIter.hasNext(); i++)
nodeInput.add(inputIter.next());
// Create job argument.
GridifyArgument jobArg = argBuilder.createJobArgument(arg, nodeInput);
ComputeJob job = new GridifyJobAdapter(jobArg);
mapper.send(job, balancer.getBalancedNode(job, exclNodes));
}
// Map method can return null because job already sent by continuous mapper.
return null;
}
/** {@inheritDoc} */
@Override public final Collection<?> reduce(List<ComputeJobResult> results) {
assert results.size() >= 1;
Collection<Object> data = new ArrayList<>(results.size());
for (ComputeJobResult res : results) {
if (res.getException() != null)
throw res.getException();
data.add(res.getData());
}
return data;
}
/**
* Calculate count of elements from input to send in job as argument.
*
* @param gridSize Grid size.
* @param inputSize Input collection size.
* @param threshold Restricts the number of elements from input that used as job execution argument.
* @param limitedSplit Restricts split for {@link GridifySetToValue} annotation.
* @return Maximum count of elements from input to send in job as argument.
*/
private int calculateInputSizePerNode(int gridSize, int inputSize, int threshold, boolean limitedSplit) {
if (threshold > 0) {
assert inputSize > threshold;
int inputPerNode = (int)Math.ceil((double)inputSize / (double)gridSize);
while (inputSize % inputPerNode <= threshold)
inputPerNode++;
return inputPerNode;
}
// Use only one node for calculation.
if (limitedSplit && inputSize <= gridSize)
return inputSize;
int inputPerNode = (int)Math.ceil((double)inputSize / (double)gridSize);
while (inputSize % inputPerNode == 1)
inputPerNode++;
return inputPerNode;
}
}