/*
* JBoss, Home of Professional Open Source.
* Copyright 2014, 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.controller.remote;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.concurrent.Executor;
import org.jboss.as.controller.client.OperationResponse;
import org.jboss.as.controller.client.impl.ModelControllerProtocol;
import org.jboss.as.protocol.mgmt.ActiveOperation;
import org.jboss.as.protocol.mgmt.FlushableDataOutput;
import org.jboss.as.protocol.mgmt.ManagementProtocol;
import org.jboss.as.protocol.mgmt.ManagementProtocolHeader;
import org.jboss.as.protocol.mgmt.ManagementRequestContext;
import org.jboss.as.protocol.mgmt.ManagementRequestHandler;
import org.jboss.as.protocol.mgmt.ManagementRequestHeader;
import org.jboss.remoting3.Channel;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit tests of {@link org.jboss.as.controller.remote.ResponseAttachmentInputStreamSupport}.
*
* @author Brian Stansberry (c) 2014 Red Hat Inc.
*/
public class ResponseAttachmentInputStreamSupportTestCase {
private static final byte[] data = new byte[8193];
@Test
public void testReadHandler() throws IOException {
ResponseAttachmentInputStreamSupport testee = new ResponseAttachmentInputStreamSupport();
InputStream stream = new ByteArrayInputStream(data);
OperationResponse.StreamEntry streamEntry = new MockStreamEntry(stream);
testee.registerStreams(1, Arrays.asList(streamEntry));
ManagementRequestHandler<Void, Void> handler = testee.getReadHandler();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MockFlushableDataOutput mfdo = new MockFlushableDataOutput(baos);
ActiveOperation.ResultHandler<Void> mrh = new MockResultHandler();
handler.handleRequest(getDataInput(1, 0), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(1, ((MockStreamEntry) streamEntry).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(1, ((MockResultHandler) mrh).result);
DataInput di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ModelControllerProtocol.PARAM_INPUTSTREAM_LENGTH, di.readByte());
int length = di.readInt();
Assert.assertEquals(8192, length);
Assert.assertEquals(ModelControllerProtocol.PARAM_INPUTSTREAM_CONTENTS, di.readByte());
di.readFully(new byte[length]);
Assert.assertEquals(ModelControllerProtocol.PARAM_INPUTSTREAM_LENGTH, di.readByte());
length = di.readInt();
Assert.assertEquals(1, length);
Assert.assertEquals(ModelControllerProtocol.PARAM_INPUTSTREAM_CONTENTS, di.readByte());
di.readFully(new byte[length]);
Assert.assertEquals(ModelControllerProtocol.PARAM_END, di.readByte());
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
// Test a missing entry
baos = new ByteArrayOutputStream();
mfdo = new MockFlushableDataOutput(baos);
handler.handleRequest(getDataInput(1, 0), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(1, ((MockStreamEntry) streamEntry).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(2, ((MockResultHandler) mrh).result);
di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ModelControllerProtocol.PARAM_END, di.readByte());
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
}
@Test
public void testCloseHandler() throws IOException {
ResponseAttachmentInputStreamSupport testee = new ResponseAttachmentInputStreamSupport();
InputStream stream = new ByteArrayInputStream(data);
OperationResponse.StreamEntry streamEntry = new MockStreamEntry(stream);
testee.registerStreams(1, Arrays.asList(streamEntry));
ManagementRequestHandler<Void, Void> handler = testee.getCloseHandler();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MockFlushableDataOutput mfdo = new MockFlushableDataOutput(baos);
ActiveOperation.ResultHandler<Void> mrh = new MockResultHandler();
handler.handleRequest(getDataInput(1, 0), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(1, ((MockStreamEntry) streamEntry).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(1, ((MockResultHandler) mrh).result);
DataInput di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
// Test a missing entry
baos = new ByteArrayOutputStream();
mfdo = new MockFlushableDataOutput(baos);
handler.handleRequest(getDataInput(1, 0), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(1, ((MockStreamEntry) streamEntry).closeCount); // still only closed once because it wasn't registered
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(2, ((MockResultHandler) mrh).result);
di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
}
@Test
public void testShutdown() throws IOException {
ResponseAttachmentInputStreamSupport testee = new ResponseAttachmentInputStreamSupport();
InputStream stream1 = new ByteArrayInputStream(data);
OperationResponse.StreamEntry streamEntry1 = new MockStreamEntry(stream1);
InputStream stream2 = new ByteArrayInputStream(data);
OperationResponse.StreamEntry streamEntry2 = new MockStreamEntry(stream2);
testee.registerStreams(1, Arrays.asList(streamEntry1, streamEntry2));
testee.shutdown();
Assert.assertEquals(1, ((MockStreamEntry) streamEntry1).closeCount);
Assert.assertEquals(1, ((MockStreamEntry) streamEntry2).closeCount);
// Validate that streams registered after shutdown are immediately closed and are not usable by a caller
testee.registerStreams(2, Arrays.asList(streamEntry1, streamEntry2));
Assert.assertEquals(2, ((MockStreamEntry) streamEntry1).closeCount); // stream was closed by registerStreams
Assert.assertEquals(2, ((MockStreamEntry) streamEntry2).closeCount); // stream was closed by registerStreams
// Validate readHandler treats as empty stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MockFlushableDataOutput mfdo = new MockFlushableDataOutput(baos);
ActiveOperation.ResultHandler<Void> mrh = new MockResultHandler();
testee.getReadHandler().handleRequest(getDataInput(2, 0), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(2, ((MockStreamEntry) streamEntry1).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(1, ((MockResultHandler) mrh).result);
DataInput di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ModelControllerProtocol.PARAM_END, di.readByte());
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
baos = new ByteArrayOutputStream();
mfdo = new MockFlushableDataOutput(baos);
testee.getCloseHandler().handleRequest(getDataInput(2, 1), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(2, ((MockStreamEntry) streamEntry2).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(2, ((MockResultHandler) mrh).result);
di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
}
@Test
public void testGC() throws IOException, InterruptedException {
ResponseAttachmentInputStreamSupport testee = new ResponseAttachmentInputStreamSupport(1);
InputStream stream1 = new ByteArrayInputStream(data);
OperationResponse.StreamEntry streamEntry1 = new MockStreamEntry(stream1);
InputStream stream2 = new ByteArrayInputStream(data);
OperationResponse.StreamEntry streamEntry2 = new MockStreamEntry(stream2);
testee.registerStreams(1, Arrays.asList(streamEntry1, streamEntry2));
Thread.sleep(2);
testee.gc();
Assert.assertEquals(1, ((MockStreamEntry) streamEntry1).closeCount);
Assert.assertEquals(1, ((MockStreamEntry) streamEntry2).closeCount);
// Validate readHandler treats as empty stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MockFlushableDataOutput mfdo = new MockFlushableDataOutput(baos);
ActiveOperation.ResultHandler<Void> mrh = new MockResultHandler();
testee.getReadHandler().handleRequest(getDataInput(1, 0), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(1, ((MockStreamEntry) streamEntry1).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(1, ((MockResultHandler) mrh).result);
DataInput di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ModelControllerProtocol.PARAM_END, di.readByte());
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
baos = new ByteArrayOutputStream();
mfdo = new MockFlushableDataOutput(baos);
testee.getCloseHandler().handleRequest(getDataInput(1, 1), mrh, new MockManagementRequestContext(mfdo));
Assert.assertEquals(1, ((MockStreamEntry) streamEntry2).closeCount);
Assert.assertTrue("Close count: " + mfdo.closeCount, mfdo.closeCount > 0);
Assert.assertEquals(2, ((MockResultHandler) mrh).result);
di = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
Assert.assertEquals(ManagementProtocol.RESPONSE_END, di.readByte());
}
private static DataInput getDataInput(int operationId, int streamIndex) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeByte(ModelControllerProtocol.PARAM_OPERATION);
dos.writeInt(operationId);
dos.writeByte(ModelControllerProtocol.PARAM_INPUTSTREAM_INDEX);
dos.writeInt(streamIndex);
dos.flush();
return new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
}
private static final class MockFlushableDataOutput extends DataOutputStream implements FlushableDataOutput {
private int closeCount;
/**
* Creates a new data output stream to write data to the specified
* underlying output stream. The counter <code>written</code> is
* set to zero.
*
* @param out the underlying output stream, to be saved for later
* use.
* @see java.io.FilterOutputStream#out
*/
public MockFlushableDataOutput(OutputStream out) {
super(out);
}
@Override
public void close() throws IOException {
super.close();
closeCount++;
}
}
private static final class MockManagementRequestContext implements ManagementRequestContext<Void> {
private final FlushableDataOutput dataOutput;
private MockManagementRequestContext(FlushableDataOutput dataOutput) {
this.dataOutput = dataOutput;
}
@Override
public Integer getOperationId() {
return 1;
}
@Override
public Void getAttachment() {
throw new UnsupportedOperationException();
}
@Override
public Channel getChannel() {
throw new UnsupportedOperationException();
}
@Override
public ManagementProtocolHeader getRequestHeader() {
return new ManagementRequestHeader(1, 1, 1, (byte) 1);
}
@Override
public boolean executeAsync(AsyncTask<Void> task) {
try {
task.execute(this);
} catch (RuntimeException r) {
throw r;
}
catch (Exception e) {
throw new RuntimeException(e);
}
return true;
}
@Override
public boolean executeAsync(AsyncTask<Void> task, boolean cancellable) {
throw new UnsupportedOperationException();
}
@Override
public boolean executeAsync(AsyncTask<Void> task, Executor executor) {
throw new UnsupportedOperationException();
}
@Override
public boolean executeAsync(AsyncTask<Void> task, boolean cancellable, Executor executor) {
throw new UnsupportedOperationException();
}
@Override
public FlushableDataOutput writeMessage(ManagementProtocolHeader header) throws IOException {
return dataOutput;
}
}
private static class MockResultHandler implements ActiveOperation.ResultHandler<Void> {
private volatile int result;
@Override
public boolean done(Void result) {
int was = this.result;
this.result += 1;
return was == 0;
}
@Override
public boolean failed(Throwable t) {
int was = this.result;
this.result += 10;
return was == 0;
}
@Override
public void cancel() {
this.result += 100;
}
}
private static final class MockStreamEntry implements OperationResponse.StreamEntry {
private final InputStream stream;
private int closeCount;
private MockStreamEntry(InputStream stream) {
this.stream = stream;
}
@Override
public String getUUID() {
return toString();
}
@Override
public String getMimeType() {
return "application/x-www-form-urlencoded";
}
@Override
public InputStream getStream() {
return stream;
}
@Override
public void close() throws IOException {
stream.close();
closeCount++;
}
}
}