All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.remote.RemoteExecutor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 HaiYang Li
 *
 * 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.landawn.abacus.remote;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

import com.landawn.abacus.http.ContentFormat;
import com.landawn.abacus.http.HttpClient;
import com.landawn.abacus.http.HttpSettings;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.parser.KryoParser;
import com.landawn.abacus.parser.ParserFactory;
import com.landawn.abacus.pool.KeyedObjectPool;
import com.landawn.abacus.pool.PoolFactory;
import com.landawn.abacus.pool.PoolableWrapper;
import com.landawn.abacus.util.AddrUtil;
import com.landawn.abacus.util.AsyncExecutor;
import com.landawn.abacus.util.ContinuableFuture;
import com.landawn.abacus.util.DependencyFinder;
import com.landawn.abacus.util.IOUtil;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Strings;
import com.landawn.abacus.util.stream.Stream;

// TODO: Auto-generated Javadoc
/**
 * Execute the code/method on remote severs, without deploying changes to the target servers first.
 * It's required to deploy com.landawn.abacus.da.http.JavaExecutionServlet under Tomcat or other servlet containers on target servers first.
 * Here is the sample for web.xml to deploy the servlet under Tomcat:
 *
 * 
 *  {@code
 *     
 *         Hello javaExecution
 *         javaExecution
 *         javaExecution
 *         com.landawn.abacus.remote.JavaExecutionServlet
 *     
 *
 *     
 *         javaExecution
 *         /javaExecution/*
 *     
 * }
 * 
* Anonymous classes are supported except the ones created by lambda. For example: *
 *    RemoteTask remoteTask = new RemoteTask() {
 *      public Object run(Object t) {
 *          // doing something.
 *          return null;
 *      }
 *    };
 *
 *    remoteExecutor.execute(remoteTask, param);
 * 
* * @author Haiyang Li * @since 0.8 */ public final class RemoteExecutor { private static final Logger logger = LoggerFactory.getLogger(RemoteExecutor.class); private static final Set filteredClassNameSet = N.asSet(); static { filteredClassNameSet.add("java.*"); filteredClassNameSet.add("javax.*"); filteredClassNameSet.add("com.landawn.abacus.*"); filteredClassNameSet.add("org.apache.*"); filteredClassNameSet.add("org.eclipse.*"); filteredClassNameSet.add("org.junit.*"); filteredClassNameSet.add("org.hamcrest.*"); filteredClassNameSet.add("org.mockito.*"); filteredClassNameSet.add("org.xerial.snappy.*"); filteredClassNameSet.add("net.jpountz.lz4.*"); filteredClassNameSet.add("org.joda.*"); filteredClassNameSet.add("org.springframework.*"); filteredClassNameSet.add("org.hibernate.*"); filteredClassNameSet.add("org.elasticsearch.*"); filteredClassNameSet.add("com.mysql.*"); filteredClassNameSet.add("org.postgresql.*"); filteredClassNameSet.add("oracle.jdbc.*"); filteredClassNameSet.add("com.microsoft.sqlserver.*"); filteredClassNameSet.add("com.ibm.db2.*"); filteredClassNameSet.add("org.hsqldb.*"); filteredClassNameSet.add("org.h2.*"); filteredClassNameSet.add("com.mongodb.*"); filteredClassNameSet.add("com.datastax.driver.*"); filteredClassNameSet.add("com.couchbase.*"); filteredClassNameSet.add("io.netty.*"); filteredClassNameSet.add("com.fasterxml.*"); filteredClassNameSet.add("net.spy.memcached.*"); filteredClassNameSet.add("redis.clients.*"); filteredClassNameSet.add("com.esotericsoftware.kryo.*"); filteredClassNameSet.add("ch.qos.logback.*"); filteredClassNameSet.add("org.slf4j.*"); filteredClassNameSet.add("org.json.*"); filteredClassNameSet.add("org.easymock.*"); filteredClassNameSet.add("org.codehaus.*"); filteredClassNameSet.add("org.aspectj.*"); filteredClassNameSet.add("org.javassist.*"); filteredClassNameSet.add("com.amazonaws.*"); filteredClassNameSet.add("com.alibaba.fastjson.*"); filteredClassNameSet.add("com.google.common.*"); // for Guava filteredClassNameSet.add("com.google.gson.*"); filteredClassNameSet.add("com.google.appengine.*"); filteredClassNameSet.add("com.google.inject.guice.*"); filteredClassNameSet.add("com.google.protobuf.*"); } private static final Map, LinkedHashMap> classBytesMapPool = new ConcurrentHashMap<>(); /** * To set the global properties. */ static final KeyedObjectPool> pool = PoolFactory.createKeyedObjectPool(8192); static final KryoParser kryoParser = ParserFactory.isKryoAvailable() ? ParserFactory.createKryoParser() : null; static final String hostName = IOUtil.getHostName(); /** * Get global property with the specified key. * * @param * @param key * @return */ public static T getProperty(Object key) { final PoolableWrapper wrapper = pool.get(key); return wrapper == null ? null : (T) wrapper.value(); } /** * Get global property with the specified key. * @param key * @param targetType * * @param * @return */ public static T getProperty(Object key, Class targetType) { final PoolableWrapper wrapper = pool.get(key); return N.convert(wrapper == null ? null : wrapper.value(), targetType); } /** * Set global property with the specified key and value. * * @param key * @param value */ public static void setProperty(Object key, Object value) { pool.put(key, PoolableWrapper.of(value)); } /** * Set global property with the specified key and value. * * @param key * @param value * @param liveTime * @param maxIdleTime */ public static void setProperty(Object key, Object value, long liveTime, long maxIdleTime) { pool.put(key, PoolableWrapper.of(value, liveTime, maxIdleTime)); } /** * remove global property with the specified key and value. * * @param key * @return */ public static Object removeProperty(Object key) { return pool.remove(key); } private final List httpClients; private final AsyncExecutor asyncExecutor; private final Predicate excludeClassByName; public RemoteExecutor(String servers) { this(servers, null); } public RemoteExecutor(String servers, Predicate classNamefilter) { this(AddrUtil.getServerList(servers), classNamefilter); } public RemoteExecutor(Collection servers) { this(servers, null); } /** * * @param servers * @param excludeClassByName exclude classes from the dependency and don't upload them to remote server. */ public RemoteExecutor(final Collection servers, final Predicate excludeClassByName) { httpClients = new ArrayList<>(servers.size()); for (String server : servers) { httpClients.add(HttpClient.create(server, HttpClient.DEFAULT_MAX_CONNECTION, Integer.MAX_VALUE, Integer.MAX_VALUE)); } asyncExecutor = new AsyncExecutor(Math.min(8, servers.size()), servers.size(), 180L, TimeUnit.SECONDS); this.excludeClassByName = new Predicate<>() { @Override public boolean test(String value) { if (excludeClassByName != null && excludeClassByName.test(value)) { return true; } for (String clsName : filteredClassNameSet) { if (value.matches(clsName)) { return true; } } return false; } }; } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @return */ public List execute(final Class> remoteTask) { return execute(remoteTask, (Object) null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List execute(final Class> remoteTask, final Object parameter) { return execute(remoteTask, parameter, null, Long.MAX_VALUE, null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @param totalExecutionTimeout * @param serverFilter filter the servers by the input parameter or other logic. There are two elements in the array. left is the target server url, right is the input parameter. * @return * @see com.landawn.abacus.http.HttpSettings */ public List execute(final Class> remoteTask, final Object parameter, final HttpSettings httpSettings, final long totalExecutionTimeout, final BiPredicate serverFilter) { return execute(remoteTask, parameter, null, httpSettings, totalExecutionTimeout, serverFilter); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List execute(final Class> remoteTask, final Map serverParameterMapper) { return execute(remoteTask, serverParameterMapper, null, Long.MAX_VALUE); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @param totalExecutionTimeout * @return * @see com.landawn.abacus.http.HttpSettings */ public List execute(final Class> remoteTask, final Map serverParameterMapper, final HttpSettings httpSettings, final long totalExecutionTimeout) { return execute(remoteTask, null, serverParameterMapper, httpSettings, totalExecutionTimeout, null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask must be equals to the instance obtained by remoteTask.getClass().newInstance(). That's to say no extra properties has been set after the object was created. * @return */ public List execute(final RemoteTask remoteTask) { return execute(remoteTask, (Object) null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask must be equals to the instance obtained by remoteTask.getClass().newInstance(). That's to say no extra properties has been set after the object was created. * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List execute(final RemoteTask remoteTask, final Object parameter) { return execute(remoteTask, parameter, null, Long.MAX_VALUE, null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask must be equals to the instance obtained by remoteTask.getClass().newInstance(). That's to say no extra properties has been set after the object was created. * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @param totalExecutionTimeout * @param serverFilter filter the servers by the input parameter or other logic. There are two elements in the array. left is the target server url, right is the input parameter. * @return * @see com.landawn.abacus.http.HttpSettings */ public List execute(final RemoteTask remoteTask, final Object parameter, final HttpSettings httpSettings, final long totalExecutionTimeout, final BiPredicate serverFilter) { return execute((Class>) remoteTask.getClass(), parameter, httpSettings, totalExecutionTimeout, serverFilter); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask must be equals to the instance obtained by remoteTask.getClass().newInstance(). That's to say no extra properties has been set after the object was created. * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List execute(final RemoteTask remoteTask, final Map serverParameterMapper) { return execute(remoteTask, serverParameterMapper, null, Long.MAX_VALUE); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask must be equals to the instance obtained by remoteTask.getClass().newInstance(). That's to say no extra properties has been set after the object was created. * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @param totalExecutionTimeout * @return * @see com.landawn.abacus.http.HttpSettings */ public List execute(final RemoteTask remoteTask, final Map serverParameterMapper, final HttpSettings httpSettings, final long totalExecutionTimeout) { return execute((Class>) remoteTask.getClass(), serverParameterMapper, httpSettings, totalExecutionTimeout); } /** * * @param * @param remoteTask * @param parameter * @param serverParameterMapper * @param httpSettings * @param totalExecutionTimeout * @param serverFilter * @return */ private List execute(final Class> remoteTask, final Object parameter, final Map serverParameterMapper, final HttpSettings httpSettings, final long totalExecutionTimeout, final BiPredicate serverFilter) { final long startTime = System.currentTimeMillis(); final List> futureResponses = asyncExecute(remoteTask, parameter, serverParameterMapper, httpSettings, serverFilter); final List result = new ArrayList<>(futureResponses.size()); RemoteExecutionResponse response = null; for (Future futureResponse : futureResponses) { final long remainingExecutionTime = totalExecutionTimeout - (System.currentTimeMillis() - startTime); try { response = futureResponse.get(remainingExecutionTime > 0 ? remainingExecutionTime : 1, TimeUnit.MILLISECONDS); } catch (Exception e) { response = new RemoteExecutionResponse(); response.setErrorCode(getClassName(e.getClass())); response.setErrorMessage(e.getMessage()); response.setRequestHost(hostName); response.setRequestTime(startTime); response.setElapsedTime(System.currentTimeMillis() - startTime); } result.add(response); } return result; } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @return */ public List> asyncExecute(final Class> remoteTask) { return asyncExecute(remoteTask, (Object) null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List> asyncExecute(final Class> remoteTask, final Object parameter) { return asyncExecute(remoteTask, parameter, null, null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @param serverFilter filter the servers by the input parameter or other logic. There are two elements in the array. left is the target server url, right is the input parameter. * @return * @see com.landawn.abacus.http.HttpSettings */ public List> asyncExecute(final Class> remoteTask, final Object parameter, final HttpSettings httpSettings, final BiPredicate serverFilter) { return asyncExecute(remoteTask, parameter, null, httpSettings, serverFilter); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List> asyncExecute(final Class> remoteTask, final Map serverParameterMapper) { return asyncExecute(remoteTask, serverParameterMapper, null); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @return * @see com.landawn.abacus.http.HttpSettings */ public List> asyncExecute(final Class> remoteTask, final Map serverParameterMapper, final HttpSettings httpSettings) { return asyncExecute(remoteTask, null, serverParameterMapper, httpSettings, null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @return */ public List> asyncExecute(final RemoteTask remoteTask) { return asyncExecute(remoteTask, (Object) null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List> asyncExecute(final RemoteTask remoteTask, final Object parameter) { return asyncExecute(remoteTask, parameter, null, null); } /** * Execute the task on different remote servers in parallel with parameter. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param parameter it must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @param serverFilter filter the servers by the input parameter or other logic. There are two elements in the array. left is the target server url, right is the input parameter. * @return * @see com.landawn.abacus.http.HttpSettings */ public List> asyncExecute(final RemoteTask remoteTask, final Object parameter, final HttpSettings httpSettings, final BiPredicate serverFilter) { return asyncExecute((Class>) remoteTask.getClass(), parameter, httpSettings, serverFilter); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @return */ public List> asyncExecute(final RemoteTask remoteTask, final Map serverParameterMapper) { return asyncExecute(remoteTask, serverParameterMapper, null); } /** * Execute the task on different remote servers in parallel with different element in parameters. * The types of parameter/return value only can be the classes exists in JDK or common libraries deployed on target servers. * It's one of the best practices to transfer big request parameter or response result by DataSet * * @param * @param remoteTask which has and only has the default constructor * @param serverParameterMapper the key is the target host/url and the value must be primitive types/string (array)/date/collection/map/bean... which can be serialized to JSON. * The element in the Collection or key/value in Map must be concrete types: primitive types wrapper/String/Date/Bean..., can't be Collection or Map again. * @param httpSettings refer to com.landawn.abacus.http.AbstractHttpClient.HttpSettings * @return * @see com.landawn.abacus.http.HttpSettings */ public List> asyncExecute(final RemoteTask remoteTask, final Map serverParameterMapper, final HttpSettings httpSettings) { return asyncExecute((Class>) remoteTask.getClass(), serverParameterMapper, httpSettings); } /** * * @param * @param remoteTask * @param parameter * @param serverParameterMapper * @param httpSettings * @param serverFilter * @return */ @SuppressWarnings({ "deprecation" }) private List> asyncExecute(final Class> remoteTask, final Object parameter, final Map serverParameterMapper, final HttpSettings httpSettings, final BiPredicate serverFilter) { if (kryoParser == null) { throw new RuntimeException("Kryo libraries are required"); } final HttpSettings newHttpSettings = httpSettings == null ? HttpSettings.create() : httpSettings.copy(); if (newHttpSettings.getContentFormat() == null || newHttpSettings.getContentFormat() == ContentFormat.NONE) { newHttpSettings.setContentFormat(ContentFormat.JSON); } if (!(newHttpSettings.getContentFormat() == ContentFormat.JSON || newHttpSettings.getContentFormat() == ContentFormat.KRYO)) { throw new IllegalArgumentException("Only format JSON/Kryo is supported"); } final RemoteExecutionRequest request0 = createRemoteRequest(remoteTask); if (serverParameterMapper == null) { request0.setParameter(parameter); } if (logger.isInfoEnabled()) { final long totalClassSizeInByte = Stream.of(request0.getClassBytesMap()).mapToLong(it -> it.getValue().length).sum(); final String msg = Stream.of(request0.getClassBytesMap()) .map(it -> it.getKey() + ": " + it.getValue().length) .join(Strings.ELEMENT_SEPARATOR, "Classes to be transferred to target servers for execution: ", ". Total size: " + totalClassSizeInByte); logger.info(msg); } final List> futureResponses = new ArrayList<>(httpClients.size()); String paramKey = null; for (int i = 0, len = httpClients.size(); i < len; i++) { final HttpClient httpClient = httpClients.get(i); if (serverParameterMapper == null) { if (serverFilter != null && serverFilter.test(httpClient.url(), parameter) == false) { continue; } } else { for (String key : serverParameterMapper.keySet()) { if (httpClient.url().equals(key) || httpClient.url().contains(key) || Strings.containsIgnoreCase(httpClient.url(), key)) { paramKey = key; break; } } if (paramKey == null) { continue; } } final RemoteExecutionRequest request = paramKey == null ? request0 : N.copy(request0); request.setParameter(serverParameterMapper == null ? null : serverParameterMapper.get(paramKey)); final Callable cmd = new Callable<>() { @Override public RemoteExecutionResponse call() throws Exception { final RemoteExecutionResponse result = httpClient.post(RemoteExecutionResponse.class, request, newHttpSettings); result.setRequestHost(request.getRequestHost()); result.setRequestId(request.getRequestId()); result.setRequestTime(request.getRequestTime()); result.setResponseTime(System.currentTimeMillis()); result.setElapsedTime(result.getResponseTime() - result.getRequestTime()); return result; } }; if (logger.isInfoEnabled()) { logger.info("Starting task on remote server: " + httpClient.url()); } futureResponses.add(asyncExecutor.execute(cmd)); } return futureResponses; } /** * Creates the remote request. * * @param remoteTask * @return */ @SuppressWarnings("deprecation") private RemoteExecutionRequest createRemoteRequest(final Class> remoteTask) { final RemoteExecutionRequest request = new RemoteExecutionRequest(); request.setRequestHost(hostName); request.setRequestId(Strings.uuid()); request.setRequestTime(System.currentTimeMillis()); request.setClassName(getClassName(remoteTask)); request.setClassBytesMap(getClassDependency(remoteTask)); return request; } // @SuppressWarnings("deprecation") // private RemoteExecutionRequest setRequestParameter(final RemoteExecutionRequest request, Object parameter) { // if (parameter == null) { // request.setParameterType(null); // request.setParameterString(null); // } else { // final Type paramType = getType(parameter); // // request.setParameterType(paramType.getName()); // request.setParameterString(paramType.stringOf(parameter)); // } // // return request; // } /** * Gets the class dependency. * * @param classToCheck * @return */ private Map getClassDependency(final Class classToCheck) { LinkedHashMap classBytesMap = classBytesMapPool.get(classToCheck); if (classBytesMap == null) { final List> dependencies = new ArrayList<>(DependencyFinder.getDependenciesRecursively(classToCheck, excludeClassByName)); N.reverse(dependencies); classBytesMap = new LinkedHashMap<>(dependencies.size() + 1); for (Class cls : dependencies) { classBytesMap.put(getClassName(cls), readClass(cls)); } final String clsName = getClassName(classToCheck); if (excludeClassByName.test(clsName) == false) { classBytesMap.put(getClassName(classToCheck), readClass(classToCheck)); } classBytesMapPool.put(classToCheck, classBytesMap); } return classBytesMap; } /** * * @param cls * @return */ private static byte[] readClass(Class cls) { InputStream is = null; try { String clsName = getClassName(cls); int lastIndex = clsName.lastIndexOf('.'); is = cls.getResourceAsStream((lastIndex < 0 ? clsName : clsName.substring(lastIndex + 1)) + ".class"); return IOUtil.readAllBytes(is); } finally { IOUtil.close(is); } } /** * Gets the class name. * * @param cls * @return */ private static String getClassName(final Class cls) { return cls.getName(); } }