/*
* 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.internal.visor.compute;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.ignite.IgniteCompute;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.compute.ComputeJob;
import org.apache.ignite.compute.ComputeJobAdapter;
import org.apache.ignite.compute.ComputeJobContext;
import org.apache.ignite.compute.ComputeJobResult;
import org.apache.ignite.compute.ComputeJobResultPolicy;
import org.apache.ignite.compute.ComputeTask;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.util.lang.GridTuple3;
import org.apache.ignite.internal.util.typedef.CI1;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.visor.VisorOneNodeTask;
import org.apache.ignite.internal.visor.VisorTaskArgument;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.resources.JobContextResource;
import org.jetbrains.annotations.Nullable;
/**
* Task to run Visor tasks through http REST.
*/
@GridInternal
public class VisorGatewayTask implements ComputeTask<Object[], Object> {
/** */
private static final long serialVersionUID = 0L;
/** */
private static final int JOB_ARG_IDX = 3;
/** Array with additional length in arguments for specific nested types */
private static final Map<Class, Integer> TYPE_ARG_LENGTH = new HashMap<>(4);
static {
TYPE_ARG_LENGTH.put(Collection.class, 2);
TYPE_ARG_LENGTH.put(Set.class, 2);
TYPE_ARG_LENGTH.put(List.class, 2);
TYPE_ARG_LENGTH.put(Map.class, 3);
TYPE_ARG_LENGTH.put(IgniteBiTuple.class, 4);
TYPE_ARG_LENGTH.put(GridTuple3.class, 6);
}
/** Auto-injected grid instance. */
@IgniteInstanceResource
protected transient IgniteEx ignite;
/** {@inheritDoc} */
@Nullable @Override public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> subgrid,
@Nullable Object[] args) throws IgniteException {
assert args != null;
assert args.length >= 2;
return Collections.singletonMap(new VisorGatewayJob(args), ignite.localNode());
}
/** {@inheritDoc} */
@Override public ComputeJobResultPolicy result(ComputeJobResult res,
List<ComputeJobResult> rcvd) throws IgniteException {
// Task should handle exceptions in reduce method.
return ComputeJobResultPolicy.WAIT;
}
/** {@inheritDoc} */
@Nullable @Override public Object reduce(List<ComputeJobResult> results) throws IgniteException {
assert results.size() == 1;
ComputeJobResult res = F.first(results);
assert res != null;
IgniteException ex = res.getException();
if (ex != null)
throw ex;
return res.getData();
}
/**
* Job to run Visor tasks through http REST.
*/
private static class VisorGatewayJob extends ComputeJobAdapter {
/** */
private static final long serialVersionUID = 0L;
/** */
private static final byte[] ZERO_BYTES = new byte[0];
/** Auto-injected grid instance. */
@IgniteInstanceResource
protected transient IgniteEx ignite;
/** Auto-inject job context. */
@JobContextResource
protected transient ComputeJobContext jobCtx;
/** Arguments count. */
private final int argsCnt;
/** Future for spawned task. */
private transient IgniteFuture fut;
/**
* Create job with specified argument.
*
* @param args Job argument.
*/
VisorGatewayJob(@Nullable Object[] args) {
super(args);
assert args != null;
argsCnt = args.length;
}
/**
* Construct job argument.
*
* @param cls Class.
* @param startIdx Index of first value argument.
*/
@Nullable private Object toJobArgument(Class cls, int startIdx) throws ClassNotFoundException {
String arg = argument(startIdx);
boolean isList = cls == Collection.class || cls == List.class;
if (isList || cls == Set.class) {
Class<?> itemsCls = Class.forName(arg);
Collection<Object> res = isList ? new ArrayList<>() : new HashSet<>();
String items = argument(startIdx + 1);
if (items != null) {
for (String item : items.split(";"))
res.add(toObject(itemsCls, item));
}
return res;
}
if (cls == IgniteBiTuple.class) {
Class<?> keyCls = Class.forName(arg);
String valClsName = argument(startIdx + 1);
assert valClsName != null;
Class<?> valCls = Class.forName(valClsName);
return new IgniteBiTuple<>(toObject(keyCls, (String)argument(startIdx + 2)),
toObject(valCls, (String)argument(startIdx + 3)));
}
if (cls == Map.class) {
Class<?> keyCls = Class.forName(arg);
String valClsName = argument(startIdx + 1);
assert valClsName != null;
Class<?> valCls = Class.forName(valClsName);
Map<Object, Object> res = new HashMap<>();
String entries = argument(startIdx + 2);
if (entries != null) {
for (String entry : entries.split(";")) {
if (!entry.isEmpty()) {
String[] values = entry.split("=");
assert values.length >= 1;
res.put(toObject(keyCls, values[0]),
values.length > 1 ? toObject(valCls, values[1]) : null);
}
}
}
return res;
}
if (cls == GridTuple3.class) {
String v2ClsName = argument(startIdx + 1);
String v3ClsName = argument(startIdx + 2);
assert v2ClsName != null;
assert v3ClsName != null;
Class<?> v1Cls = Class.forName(arg);
Class<?> v2Cls = Class.forName(v2ClsName);
Class<?> v3Cls = Class.forName(v3ClsName);
return new GridTuple3<>(toObject(v1Cls, (String)argument(startIdx + 3)), toObject(v2Cls,
(String)argument(startIdx + 4)), toObject(v3Cls, (String)argument(startIdx + 5)));
}
return toObject(cls, arg);
}
/**
* Construct from string representation to target class.
*
* @param cls Target class.
* @return Object constructed from string.
*/
@Nullable private Object toObject(Class cls, String val) {
if (val == null || "null".equals(val))
return null;
if (String.class == cls)
return val;
if (Boolean.class == cls || Boolean.TYPE == cls)
return Boolean.parseBoolean(val);
if (Integer.class == cls || Integer.TYPE == cls)
return Integer.parseInt(val);
if (Long.class == cls || Long.TYPE == cls)
return Long.parseLong(val);
if (UUID.class == cls)
return UUID.fromString(val);
if (IgniteUuid.class == cls)
return IgniteUuid.fromString(val);
if (Byte.class == cls || Byte.TYPE == cls)
return Byte.parseByte(val);
if (Short.class == cls || Short.TYPE == cls)
return Short.parseShort(val);
if (Float.class == cls || Float.TYPE == cls)
return Float.parseFloat(val);
if (Double.class == cls || Double.TYPE == cls)
return Double.parseDouble(val);
if (BigDecimal.class == cls)
return new BigDecimal(val);
if (Collection.class == cls || List.class == cls)
return Arrays.asList(val.split(";"));
if (Set.class == cls)
return new HashSet<>(Arrays.asList(val.split(";")));
if (Object[].class == cls)
return val.split(";");
if (byte[].class == cls) {
String[] els = val.split(";");
if (els.length == 0 || (els.length == 1 && els[0].isEmpty()))
return ZERO_BYTES;
byte[] res = new byte[els.length];
for (int i = 0; i < els.length; i ++)
res[i] = Byte.valueOf(els[i]);
return res;
}
return val;
}
/**
* Check if class is not a complex bean.
*
* @param cls Target class.
* @return {@code True} if class is primitive or build-in java type or IgniteUuid.
*/
private static boolean isBuildInObject(Class cls) {
return cls.isPrimitive() || cls.getName().startsWith("java.") ||
IgniteUuid.class == cls || IgniteBiTuple.class == cls || GridTuple3.class == cls;
}
/**
* Extract Class object from arguments.
*
* @param idx Index of argument.
*/
private Class toClass(int idx) throws ClassNotFoundException {
Object arg = argument(idx); // Workaround generics: extract argument as Object to use in String.valueOf().
return Class.forName(String.valueOf(arg));
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override public Object execute() throws IgniteException {
if (fut != null)
return fut.get();
String nidsArg = argument(0);
String taskName = argument(1);
Object jobArgs = null;
if (argsCnt > 2) {
String argClsName = argument(2);
assert argClsName != null;
try {
Class<?> argCls = Class.forName(argClsName);
if (argCls == Void.class)
jobArgs = null;
else if (isBuildInObject(argCls))
jobArgs = toJobArgument(argCls, JOB_ARG_IDX);
else {
int beanArgsCnt = argsCnt - JOB_ARG_IDX;
for (Constructor ctor : argCls.getDeclaredConstructors()) {
Class[] types = ctor.getParameterTypes();
int args = types.length;
// Length of arguments that required to constructor by influence of nested complex objects.
int needArgs = args;
for (Class type: types)
// When constructor required specified types increase length of required arguments.
if (TYPE_ARG_LENGTH.containsKey(type))
needArgs += TYPE_ARG_LENGTH.get(type);
if (needArgs == beanArgsCnt) {
Object[] initArgs = new Object[args];
for (int i = 0, ctrIdx = 0; i < beanArgsCnt; i++, ctrIdx++) {
Class type = types[ctrIdx];
// Parse nested complex objects from arguments for specified types.
if (TYPE_ARG_LENGTH.containsKey(type)) {
initArgs[ctrIdx] = toJobArgument(toClass(JOB_ARG_IDX + i), JOB_ARG_IDX + 1 + i);
i += TYPE_ARG_LENGTH.get(type);
}
// In common case convert value to object.
else {
String val = argument(JOB_ARG_IDX + i);
initArgs[ctrIdx] = toObject(type, val);
}
}
jobArgs = ctor.newInstance(initArgs);
break;
}
}
if (jobArgs == null) {
Object[] args = new Object[beanArgsCnt];
for (int i = 0; i < beanArgsCnt; i++)
args[i] = argument(i + JOB_ARG_IDX);
throw new IgniteException("Failed to find constructor for task argument " +
"[taskName=" + taskName + ", argsCnt=" + args.length +
", args=" + Arrays.toString(args) + "]");
}
}
}
catch (Exception e) {
throw new IgniteException("Failed to construct job argument [taskName=" + taskName + "]", e);
}
}
final List<UUID> nids;
if (F.isEmpty(nidsArg) || "null".equals(nidsArg)) {
try {
if (VisorOneNodeTask.class.isAssignableFrom(Class.forName(taskName)))
nids = Collections.singletonList(ignite.localNode().id());
else {
Collection<ClusterNode> nodes = ignite.cluster().nodes();
nids = new ArrayList<>(nodes.size());
for (ClusterNode node : nodes)
nids.add(node.id());
}
}
catch (ClassNotFoundException e) {
throw new IgniteException("Failed to find task class:" + taskName, e);
}
}
else {
String[] items = nidsArg.split(";");
nids = new ArrayList<>(items.length);
for (String item : items) {
try {
nids.add(UUID.fromString(item));
} catch (IllegalArgumentException ignore) {
ignite.log().warning("Failed to parse node id [taskName=" + taskName + ", nid=" + item + "]");
}
}
}
IgniteCompute comp = ignite.compute(ignite.cluster().forNodeIds(nids));
fut = comp.executeAsync(taskName, new VisorTaskArgument<>(nids, jobArgs, false));
fut.listen(new CI1<IgniteFuture<Object>>() {
@Override public void apply(IgniteFuture<Object> f) {
jobCtx.callcc();
}
});
return jobCtx.holdcc();
}
}
}