/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.fabric.netty.handlers;
import com.liferay.portal.fabric.agent.FabricAgent;
import com.liferay.portal.fabric.netty.agent.NettyFabricAgentStub;
import com.liferay.portal.fabric.netty.fileserver.FileHelperUtil;
import com.liferay.portal.fabric.netty.rpc.ChannelThreadLocal;
import com.liferay.portal.fabric.netty.rpc.RPCUtil;
import com.liferay.portal.fabric.netty.rpc.SyncProcessRPCCallable;
import com.liferay.portal.fabric.netty.util.NettyUtil;
import com.liferay.portal.fabric.netty.worker.NettyFabricWorkerConfig;
import com.liferay.portal.fabric.netty.worker.NettyFabricWorkerStub;
import com.liferay.portal.fabric.repository.Repository;
import com.liferay.portal.fabric.worker.FabricWorker;
import com.liferay.portal.kernel.concurrent.BaseFutureListener;
import com.liferay.portal.kernel.concurrent.FutureListener;
import com.liferay.portal.kernel.concurrent.NoticeableFuture;
import com.liferay.portal.kernel.concurrent.NoticeableFutureConverter;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.process.ClassPathUtil;
import com.liferay.portal.kernel.process.ProcessCallable;
import com.liferay.portal.kernel.process.ProcessConfig;
import com.liferay.portal.kernel.process.ProcessConfig.Builder;
import com.liferay.portal.kernel.process.ProcessException;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GenericFutureListener;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @author Shuyang Zhou
*/
public class NettyFabricWorkerExecutionChannelHandler
extends SimpleChannelInboundHandler<NettyFabricWorkerConfig<Serializable>> {
public NettyFabricWorkerExecutionChannelHandler(
Repository<Channel> repository, FabricAgent fabricAgent,
long executionTimeout) {
if (repository == null) {
throw new NullPointerException("Repository is null");
}
if (fabricAgent == null) {
throw new NullPointerException("Fabric agent is null");
}
_repository = repository;
_fabricAgent = fabricAgent;
_executionTimeout = executionTimeout;
}
@Override
public void exceptionCaught(
ChannelHandlerContext channelHandlerContext, Throwable throwable) {
final Channel channel = channelHandlerContext.channel();
_log.error("Closing " + channel + " due to:", throwable);
ChannelFuture channelFuture = channel.close();
channelFuture.addListener(
new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) {
if (_log.isInfoEnabled()) {
_log.info(channel + " is closed");
}
}
});
}
@Override
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
NettyFabricWorkerConfig<Serializable> nettyFabricWorkerConfig) {
NoticeableFuture<LoadedPaths> noticeableFuture = loadPaths(
channelHandlerContext.channel(), nettyFabricWorkerConfig);
noticeableFuture.addFutureListener(
new PostLoadPathsFutureListener(
channelHandlerContext, nettyFabricWorkerConfig));
}
protected NoticeableFuture<LoadedPaths> loadPaths(
Channel channel,
NettyFabricWorkerConfig<Serializable> nettyFabricWorkerConfig) {
Map<Path, Path> mergedPaths = new HashMap<>();
ProcessConfig processConfig =
nettyFabricWorkerConfig.getProcessConfig();
final Map<Path, Path> bootstrapPaths = new LinkedHashMap<>();
for (String pathString :
processConfig.getBootstrapClassPathElements()) {
bootstrapPaths.put(Paths.get(pathString), null);
}
mergedPaths.putAll(bootstrapPaths);
final Map<Path, Path> runtimePaths = new LinkedHashMap<>();
for (String pathString : processConfig.getRuntimeClassPathElements()) {
runtimePaths.put(Paths.get(pathString), null);
}
mergedPaths.putAll(runtimePaths);
final Map<Path, Path> inputPaths =
nettyFabricWorkerConfig.getInputPathMap();
mergedPaths.putAll(inputPaths);
return new NoticeableFutureConverter<LoadedPaths, Map<Path, Path>>(
_repository.getFiles(channel, mergedPaths, false)) {
@Override
protected LoadedPaths convert(Map<Path, Path> mergedPaths)
throws IOException {
Map<Path, Path> loadedInputPaths = new HashMap<>();
List<Path> missedInputPaths = new ArrayList<>();
for (Path path : inputPaths.keySet()) {
Path loadedInputPath = mergedPaths.get(path);
if (loadedInputPath == null) {
missedInputPaths.add(path);
}
else {
loadedInputPaths.put(path, loadedInputPath);
}
}
if (!missedInputPaths.isEmpty()) {
throw new IOException(
"Unable to get input paths: " + missedInputPaths);
}
List<Path> loadedBootstrapPaths = new ArrayList<>();
List<Path> missedBootstrapPaths = new ArrayList<>();
for (Path path : bootstrapPaths.keySet()) {
Path loadedBootstrapPath = mergedPaths.get(path);
if (loadedBootstrapPath == null) {
missedBootstrapPaths.add(path);
}
else {
loadedBootstrapPaths.add(loadedBootstrapPath);
}
}
if (!missedBootstrapPaths.isEmpty() && _log.isWarnEnabled()) {
_log.warn(
"Incomplete bootstrap classpath loaded, missed: " +
missedBootstrapPaths);
}
List<Path> loadedRuntimePaths = new ArrayList<>();
List<Path> missedRuntimePaths = new ArrayList<>();
for (Path path : runtimePaths.keySet()) {
Path loadedRuntimePath = mergedPaths.get(path);
if (loadedRuntimePath == null) {
missedRuntimePaths.add(path);
}
else {
loadedRuntimePaths.add(loadedRuntimePath);
}
}
if (!missedRuntimePaths.isEmpty() && _log.isWarnEnabled()) {
_log.warn(
"Incomplete runtime classpath loaded, missed: " +
missedRuntimePaths);
}
return new LoadedPaths(
loadedInputPaths,
StringUtil.merge(loadedBootstrapPaths, File.pathSeparator),
StringUtil.merge(loadedRuntimePaths, File.pathSeparator));
}
};
}
protected void sendResult(
Channel channel, long fabricWorkerId, Serializable result,
Throwable t) {
final FabricWorkerResultProcessCallable
fabricWorkerResultProcessCallable =
new FabricWorkerResultProcessCallable(
fabricWorkerId, result, t);
NoticeableFuture<Serializable> noticeableFuture = RPCUtil.execute(
channel,
new SyncProcessRPCCallable<Serializable>(
fabricWorkerResultProcessCallable));
NettyUtil.scheduleCancellation(
channel, noticeableFuture, _executionTimeout);
noticeableFuture.addFutureListener(
new BaseFutureListener<Serializable>() {
@Override
public void completeWithException(
Future<Serializable> future, Throwable throwable) {
_log.error(
"Unable to send back fabric worker result " +
fabricWorkerResultProcessCallable,
throwable);
}
});
}
protected static class FabricAgentFinishStartupProcessCallable
implements ProcessCallable<Serializable> {
@Override
public Serializable call() throws ProcessException {
Channel channel = ChannelThreadLocal.getChannel();
NettyFabricAgentStub nettyStubFabricAgent =
NettyChannelAttributes.getNettyFabricAgentStub(channel);
if (nettyStubFabricAgent == null) {
throw new ProcessException(
"Unable to locate fabric agent on channel " + channel);
}
nettyStubFabricAgent.finishStartup(_id);
return null;
}
protected FabricAgentFinishStartupProcessCallable(long id) {
_id = id;
}
private static final long serialVersionUID = 1L;
private final long _id;
}
protected static class FabricWorkerResultProcessCallable
implements ProcessCallable<Serializable> {
@Override
public Serializable call() throws ProcessException {
Channel channel = ChannelThreadLocal.getChannel();
NettyFabricAgentStub nettyStubFabricAgent =
NettyChannelAttributes.getNettyFabricAgentStub(channel);
if (nettyStubFabricAgent == null) {
throw new ProcessException(
"Unable to locate fabric agent on channel " + channel);
}
NettyFabricWorkerStub<Serializable> nettyStubFabricWorker =
(NettyFabricWorkerStub<Serializable>)
nettyStubFabricAgent.takeNettyStubFabricWorker(_id);
if (nettyStubFabricWorker == null) {
throw new ProcessException(
"Unable to locate fabric worker on channel " + channel +
", with fabric worker id " + _id);
}
if (_throwable != null) {
nettyStubFabricWorker.setException(_throwable);
}
else {
nettyStubFabricWorker.setResult(_result);
}
return null;
}
@Override
public String toString() {
StringBundler sb = new StringBundler(7);
sb.append("{id=");
sb.append(_id);
sb.append(", result=");
sb.append(_result);
sb.append(", throwable=");
sb.append(_throwable);
sb.append("}");
return sb.toString();
}
protected FabricWorkerResultProcessCallable(
long id, Serializable result, Throwable throwable) {
_id = id;
_result = result;
_throwable = throwable;
}
private static final long serialVersionUID = 1L;
private final long _id;
private final Serializable _result;
private final Throwable _throwable;
}
protected static class LoadedPaths {
public LoadedPaths(
Map<Path, Path> inputPaths, String bootstrapClassPath,
String runtimeClassPath) {
_inputPaths = inputPaths;
_bootstrapClassPath = bootstrapClassPath;
_runtimeClassPath = runtimeClassPath;
}
public Map<Path, Path> getInputPaths() {
return _inputPaths;
}
public ProcessConfig toProcessConfig(ProcessConfig processConfig)
throws ProcessException {
Builder builder = new Builder();
builder.setArguments(processConfig.getArguments());
builder.setBootstrapClassPath(_bootstrapClassPath);
builder.setJavaExecutable(processConfig.getJavaExecutable());
builder.setRuntimeClassPath(_runtimeClassPath);
try {
builder.setReactClassLoader(
new URLClassLoader(
ArrayUtil.append(
ClassPathUtil.getClassPathURLs(_bootstrapClassPath),
ClassPathUtil.getClassPathURLs(
_runtimeClassPath))));
}
catch (MalformedURLException murle) {
throw new ProcessException(murle);
}
return builder.build();
}
private final String _bootstrapClassPath;
private final Map<Path, Path> _inputPaths;
private final String _runtimeClassPath;
}
protected class PostFabricWorkerExecutionFutureListener
implements GenericFutureListener
<io.netty.util.concurrent.Future<FabricWorker<Serializable>>> {
public PostFabricWorkerExecutionFutureListener(
Channel channel, LoadedPaths loadedPaths,
NettyFabricWorkerConfig<Serializable> nettyFabricWorkerConfig) {
_channel = channel;
_loadedPaths = loadedPaths;
_nettyFabricWorkerConfig = nettyFabricWorkerConfig;
}
@Override
public void operationComplete(
io.netty.util.concurrent.Future<FabricWorker<Serializable>>
future)
throws Exception {
Throwable throwable = future.cause();
if (throwable != null) {
sendResult(
_channel, _nettyFabricWorkerConfig.getId(), null,
throwable);
return;
}
FabricWorker<Serializable> fabricWorker = future.get();
NettyChannelAttributes.putFabricWorker(
_channel, _nettyFabricWorkerConfig.getId(), fabricWorker);
NoticeableFuture<Serializable> noticeableFuture = RPCUtil.execute(
_channel,
new SyncProcessRPCCallable<Serializable>(
new FabricAgentFinishStartupProcessCallable(
_nettyFabricWorkerConfig.getId())));
NettyUtil.scheduleCancellation(
_channel, noticeableFuture, _executionTimeout);
noticeableFuture.addFutureListener(
new BaseFutureListener<Serializable>() {
@Override
public void completeWithException(
Future<Serializable> future, Throwable throwable) {
_log.error(
"Unable to finish fabric worker startup",
throwable);
}
});
NoticeableFuture<Serializable> processNoticeableFuture =
fabricWorker.getProcessNoticeableFuture();
processNoticeableFuture.addFutureListener(
new PostFabricWorkerFinishFutureListener(
_channel, _nettyFabricWorkerConfig, _loadedPaths));
}
private final Channel _channel;
private final LoadedPaths _loadedPaths;
private final NettyFabricWorkerConfig<Serializable>
_nettyFabricWorkerConfig;
}
protected class PostFabricWorkerFinishFutureListener
implements FutureListener<Serializable> {
public PostFabricWorkerFinishFutureListener(
Channel channel,
NettyFabricWorkerConfig<Serializable> nettyFabricWorkerConfig,
LoadedPaths loadedPaths) {
_channel = channel;
_nettyFabricWorkerConfig = nettyFabricWorkerConfig;
_loadedPaths = loadedPaths;
}
@Override
public void complete(Future<Serializable> future) {
Map<Path, Path> inputPaths = _loadedPaths.getInputPaths();
for (Path path : inputPaths.values()) {
FileHelperUtil.delete(true, path);
}
try {
sendResult(
_channel, _nettyFabricWorkerConfig.getId(), future.get(),
null);
}
catch (Throwable t) {
if (t instanceof ExecutionException) {
t = t.getCause();
}
sendResult(_channel, _nettyFabricWorkerConfig.getId(), null, t);
}
}
private final Channel _channel;
private final LoadedPaths _loadedPaths;
private final NettyFabricWorkerConfig<Serializable>
_nettyFabricWorkerConfig;
}
protected class PostLoadPathsFutureListener
extends BaseFutureListener<LoadedPaths> {
public PostLoadPathsFutureListener(
ChannelHandlerContext channelHandlerContext,
NettyFabricWorkerConfig<Serializable> nettyFabricWorkerConfig) {
_channelHandlerContext = channelHandlerContext;
_nettyFabricWorkerConfig = nettyFabricWorkerConfig;
}
@Override
public void completeWithException(
Future<LoadedPaths> future, Throwable throwable) {
sendResult(
_channelHandlerContext.channel(),
_nettyFabricWorkerConfig.getId(), null, throwable);
}
@Override
public void completeWithResult(
Future<LoadedPaths> loadPathsFuture,
final LoadedPaths loadedPaths) {
EventExecutor eventExecutor = _channelHandlerContext.executor();
io.netty.util.concurrent.Future<FabricWorker<Serializable>> future =
eventExecutor.submit(
new Callable<FabricWorker<Serializable>>() {
@Override
public FabricWorker<Serializable> call()
throws ProcessException {
ProcessConfig processConfig =
_nettyFabricWorkerConfig.getProcessConfig();
return _fabricAgent.execute(
loadedPaths.toProcessConfig(processConfig),
_nettyFabricWorkerConfig.getProcessCallable());
}
});
future.addListener(
new PostFabricWorkerExecutionFutureListener(
_channelHandlerContext.channel(), loadedPaths,
_nettyFabricWorkerConfig));
}
private final ChannelHandlerContext _channelHandlerContext;
private final NettyFabricWorkerConfig<Serializable>
_nettyFabricWorkerConfig;
}
private static final Log _log = LogFactoryUtil.getLog(
NettyFabricWorkerExecutionChannelHandler.class);
private final long _executionTimeout;
private final FabricAgent _fabricAgent;
private final Repository<Channel> _repository;
}