/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This 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 software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.protocol.mgmt;
import java.io.DataInput;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.as.protocol.ProtocolLogger;
import org.jboss.as.protocol.ProtocolMessages;
import org.jboss.as.protocol.StreamUtils;
import org.jboss.remoting3.Channel;
import org.jboss.remoting3.MessageOutputStream;
import org.jboss.threads.AsyncFuture;
import org.xnio.Cancellable;
/**
* Utility class for request/response handling
*
* @author Emanuel Muckenhuber
*/
public abstract class AbstractMessageHandler extends ActiveOperationSupport implements ManagementMessageHandler {
private final ExecutorService executorService;
private final AtomicInteger requestID = new AtomicInteger();
private final Map<Integer, ActiveRequest<?, ?>> requests = new ConcurrentHashMap<Integer, ActiveRequest<?, ?>>(16, 0.75f, Runtime.getRuntime().availableProcessors());
protected AbstractMessageHandler(final ExecutorService executorService) {
if(executorService == null) {
throw ProtocolMessages.MESSAGES.nullExecutor();
}
this.executorService = executorService;
}
/**
* Get the executor
*
* @return the executor
*/
protected ExecutorService getExecutor() {
return executorService;
}
/**
* Get the request handler.
*
* @param header the request header
* @return the request handler
*/
protected ManagementRequestHandler<?, ?> getRequestHandler(final ManagementRequestHeader header) {
return getFallbackHandler(header);
}
/**
* Validate whether the request can be handled.
*
* @param header the protocol header
* @return the management request header
* @throws IOException
*/
protected ManagementRequestHeader validateRequest(final ManagementProtocolHeader header) throws IOException {
return (ManagementRequestHeader) header;
}
/**
* Handle a message.
*
* @param channel the channel
* @param input the message
* @param header the management protocol header
* @throws IOException
*/
public void handleMessage(final Channel channel, final DataInput input, final ManagementProtocolHeader header) throws IOException {
final byte type = header.getType();
if(type == ManagementProtocol.TYPE_RESPONSE) {
// Handle response to local requests
final ManagementResponseHeader response = (ManagementResponseHeader) header;
final ActiveRequest<?, ?> request = requests.remove(response.getResponseId());
if(request == null) {
ProtocolLogger.CONNECTION_LOGGER.noSuchRequest(response.getResponseId(), channel);
safeWriteErrorResponse(channel, header, ProtocolMessages.MESSAGES.responseHandlerNotFound(response.getResponseId()));
} else if(response.getError() != null) {
// Actually we could move this in the response handler
request.context.getResultHandler().failed(new IOException(response.getError()));
} else {
handleRequest(channel, input, header, request);
}
} else {
// Handle requests (or other messages)
try {
final ManagementRequestHeader requestHeader = validateRequest(header);
final ManagementRequestHandler<?, ?> handler = getRequestHandler(requestHeader);
if(handler == null) {
safeWriteErrorResponse(channel, header, ProtocolMessages.MESSAGES.responseHandlerNotFound(requestHeader.getBatchId()));
} else {
handleMessage(channel, input, requestHeader, handler);
}
} catch (Exception e) {
safeWriteErrorResponse(channel, header, e);
}
}
}
/**
* Execute a request.
*
* @param request the request
* @param channel the channel
* @param support the request support
* @return the future result
*/
protected <T, A> AsyncFuture<T> executeRequest(final ManagementRequest<T, A> request, final Channel channel, final ActiveOperation<T, A> support) {
assert support != null;
final Integer requestId = this.requestID.incrementAndGet();
final ActiveRequest<T, A> ar = new ActiveRequest<T, A>(support, request, channel);
requests.put(requestId, ar);
final ManagementRequestHeader header = new ManagementRequestHeader(ManagementProtocol.VERSION, requestId, support.getOperationId(), request.getOperationType());
final ActiveOperation.ResultHandler<T> resultHandler = support.getResultHandler();
try {
request.sendRequest(resultHandler, new ManagementRequestContext<A>() {
@Override
public Integer getOperationId() {
return support.getOperationId();
}
@Override
public A getAttachment() {
return support.getAttachment();
}
@Override
public Channel getChannel() {
return channel;
}
@Override
public ManagementProtocolHeader getRequestHeader() {
return header;
}
@Override
public void executeAsync(final AsyncTask<A> task) {
final ManagementRequestContext<A> context = this;
final AsyncTaskRunner runner = new AsyncTaskRunner() {
@Override
protected void doExecute() {
try {
task.execute(context);
} catch (Exception e) {
resultHandler.failed(e);
requests.remove(requestId);
}
}
};
support.addCancellable(runner);
getExecutor().execute(runner);
}
@Override
public FlushableDataOutput writeMessage(final ManagementProtocolHeader header) throws IOException {
final MessageOutputStream os = channel.writeMessage();
return writeHeader(header, os);
}
});
} catch (Exception e) {
resultHandler.failed(e);
requests.remove(requestId);
}
return support.getResult();
}
/**
* Handle a message.
*
* @param channel the channel
* @param message the message
* @param header the protocol header
* @param activeRequest the active request
*/
protected <T, A> void handleRequest(final Channel channel, final DataInput message, final ManagementProtocolHeader header, ActiveRequest<T, A> activeRequest) {
handleMessage(channel, message, header, activeRequest.context, activeRequest.handler);
}
/**
* Handle a message.
*
* @param channel the channel
* @param message the message
* @param header the protocol header
* @param handler the request handler
* @throws IOException
*/
protected <T, A> void handleMessage(final Channel channel, final DataInput message, final ManagementRequestHeader header, ManagementRequestHandler<T, A> handler) throws IOException {
final ActiveOperation<T, A> support = getActiveOperation(header);
if(support == null) {
throw ProtocolMessages.MESSAGES.responseHandlerNotFound(header.getBatchId());
}
handleMessage(channel, message, header, support, handler);
}
/**
* Handle a message.
*
* @param channel the channel
* @param message the message
* @param header the protocol header
* @param support the request support
* @param handler the request handler
*/
protected <T, A> void handleMessage(final Channel channel, final DataInput message, final ManagementProtocolHeader header,
final ActiveOperation<T, A> support, final ManagementRequestHandler<T, A> handler) {
assert support != null;
final ActiveOperation.ResultHandler<T> resultHandler = support.getResultHandler();
try {
handler.handleRequest(message, resultHandler, new ManagementRequestContext<A>() {
@Override
public Integer getOperationId() {
return support.getOperationId();
}
@Override
public A getAttachment() {
return support.getAttachment();
}
@Override
public Channel getChannel() {
return channel;
}
@Override
public ManagementProtocolHeader getRequestHeader() {
return header;
}
@Override
public void executeAsync(final AsyncTask<A> task) {
final ManagementRequestContext<A> context = this;
final AsyncTaskRunner runner = new AsyncTaskRunner() {
@Override
protected void doExecute() {
try {
/*UserInfo userInfo = channel.getConnection().getUserInfo();
if (userInfo instanceof SubjectUserInfo) {
Subject.doAs(((SubjectUserInfo) userInfo).getSubject(),
new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
task.execute(context);
return null;
}
});
} else {*/
task.execute(context);
//}
} catch (RejectedExecutionException e) {
if(resultHandler.failed(e)) {
safeWriteErrorResponse(channel, header, e);
}
} catch (Exception e) {
ProtocolLogger.ROOT_LOGGER.debugf(e, " failed to process async request for %s on channel %s", task, channel);
if(resultHandler.failed(e)) {
safeWriteErrorResponse(channel, header, e);
}
}
}
};
support.addCancellable(runner);
getExecutor().execute(runner);
}
@Override
public FlushableDataOutput writeMessage(final ManagementProtocolHeader header) throws IOException {
final MessageOutputStream os = channel.writeMessage();
return writeHeader(header, os);
}
});
} catch (Exception e) {
resultHandler.failed(e);
safeWriteErrorResponse(channel, header, e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void shutdown() {
super.shutdown();
}
/**
* {@inheritDoc}
*/
@Override
public void shutdownNow() {
shutdown();
cancelAllActiveOperations();
}
/**
* {@inheritDoc}
*/
@Override
public boolean awaitCompletion(long timeout, TimeUnit unit) throws InterruptedException {
return super.awaitCompletion(timeout, unit);
}
/**
* Receive a notification that the channel was closed.
*
* This is used for the {@link ManagementClientChannelStrategy.Establishing} since it might use multiple channels.
*
* @param closed the closed resource
* @param e the exception which occurred during close, if any
*/
public void handleChannelClosed(final Channel closed, final IOException e) {
for(final Map.Entry<Integer, ActiveRequest<?, ?>> requestEntry : requests.entrySet()) {
final ActiveRequest<?, ?> request = requestEntry.getValue();
if(request.channel == closed) {
final IOException failure = e == null ? new IOException("Channel closed") : e;
request.context.getResultHandler().failed(failure);
requests.remove(requestEntry.getKey());
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected <T, A> ActiveOperation<T, A> removeActiveOperation(Integer id) {
final ActiveOperation<T, A> removed = super.removeActiveOperation(id);
if(removed != null) {
for(final Map.Entry<Integer, ActiveRequest<?, ?>> requestEntry : requests.entrySet()) {
final ActiveRequest<?, ?> request = requestEntry.getValue();
if(request.context == removed) {
requests.remove(requestEntry.getKey());
}
}
}
return removed;
}
/**
* Safe write error response.
*
* @param channel the channel
* @param header the request header
* @param error the exception
*/
protected static void safeWriteErrorResponse(final Channel channel, final ManagementProtocolHeader header, final Exception error) {
if(header.getType() == ManagementProtocol.TYPE_REQUEST) {
try {
writeErrorResponse(channel, (ManagementRequestHeader) header, error);
} catch(IOException ioe) {
ProtocolLogger.ROOT_LOGGER.tracef(ioe, "failed to write error response for %s on channel: %s", header, channel);
}
}
}
/**
* Write an error response.
*
* @param channel the channel
* @param header the request
* @param error the error
* @throws IOException
*/
protected static void writeErrorResponse(final Channel channel, final ManagementRequestHeader header, final Exception error) throws IOException {
final ManagementResponseHeader response = ManagementResponseHeader.create(header, error);
final MessageOutputStream output = channel.writeMessage();
try {
writeHeader(response, output);
output.close();
} finally {
StreamUtils.safeClose(output);
}
}
/**
* Write the management protocol header.
*
* @param header the mgmt protocol header
* @param os the output stream
* @throws IOException
*/
protected static FlushableDataOutput writeHeader(final ManagementProtocolHeader header, final OutputStream os) throws IOException {
final FlushableDataOutput output = FlushableDataOutputImpl.create(os);
header.write(output);
return output;
}
/**
* Get a fallback handler.
*
* @param header the protocol header
* @return the fallback handler
*/
protected <T, A> ManagementRequestHandler<T, A> getFallbackHandler(final ManagementRequestHeader header) {
return new ManagementRequestHandler<T, A>() {
@Override
public void handleRequest(final DataInput input, ActiveOperation.ResultHandler<T> resultHandler, ManagementRequestContext<A> context) throws IOException {
final Exception error = new IOException("no handler registered");
if(resultHandler.failed(error)) {
safeWriteErrorResponse(context.getChannel(), context.getRequestHeader(), error);
}
}
};
}
private static class ActiveRequest<T, A> {
private final Channel channel;
private final ActiveOperation<T, A> context;
private final ManagementRequestHandler<T, A> handler;
ActiveRequest(ActiveOperation<T, A> context, ManagementRequestHandler<T, A> handler, Channel channel) {
this.context = context;
this.handler = handler;
this.channel = channel;
}
}
private abstract static class AsyncTaskRunner implements Runnable, Cancellable {
private final AtomicBoolean cancelled = new AtomicBoolean(false);
private volatile Thread thread;
@Override
public Cancellable cancel() {
if(cancelled.compareAndSet(false, true)) {
final Thread thread = this.thread;
if(thread != null) {
thread.interrupt();
}
}
return this;
}
/**
* Execute...
*/
protected abstract void doExecute();
@Override
public void run() {
if(cancelled.get()) {
return;
}
this.thread = Thread.currentThread();
try {
doExecute();
} finally {
this.thread = null;
}
}
}
}