blob: 0152d9dd71698a8b2b7d6bd42de7c351bf64c656 [file] [log] [blame]
/*
* 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.royale.test.ant;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import org.apache.tools.ant.BuildException;
/**
* Class responsible for managing the connections to the test runner and any boiler plate in the network interactivity.
*/
public class RoyaleUnitSocketServer implements IRoyaleUnitServer
{
private static final char NULL_BYTE = '\u0000';
private static final String POLICY_FILE_REQUEST = "<policy-file-request/>";
//Uncomment to use DTD for validation rather than schema
//private static final String DOMAIN_POLICY = "<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"{0}\" /></cross-domain-policy>";
private static final String DOMAIN_POLICY =
"<?xml version=\"1.0\"?>"
+ "<cross-domain-policy xmlns=\"http://localhost\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.adobe.com/xml/schemas PolicyFileSocket.xsd\">"
+ "<allow-access-from domain=\"*\" to-ports=\"{0}\" />"
+ "</cross-domain-policy>";
private static final String START_OF_TEST_RUN_ACK = "<startOfTestRunAck/>";
private static final String END_OF_TEST_RUN_ACK = "<endOfTestRunAck/>";
private int port;
private int timeout;
private int inboundBufferSize; //if this is not set high enough, incoming data may clobber unread data in the buffer
private boolean waitForPolicyFile;
private ServerSocket serverSocket = null;
private Socket clientSocket = null;
private InputStreamReader inboundReader = null;
private OutputStreamWriter outboundWriter = null;
public RoyaleUnitSocketServer(int port, int timeout, int inboundBufferSize, boolean waitForPolicyFile)
{
this.port = port;
this.timeout = timeout;
this.inboundBufferSize = inboundBufferSize;
this.waitForPolicyFile = waitForPolicyFile;
}
/**
* Starts the socket server, managing policy file requests, and starting the test process.
*/
public void start() throws IOException
{
LoggingUtil.log("Starting server ...");
try
{
openServerSocket();
openClientSocket();
prepareClientSocket();
}
catch (SocketTimeoutException e)
{
throw new BuildException("Socket timeout waiting for royaleunit report", e);
}
}
public boolean isPending()
{
return false;
}
public Exception getException()
{
return null;
}
/**
* Resets the client connection.
*/
private void resetInboundStream() throws IOException
{
LoggingUtil.log("Resetting client connection ...");
closeClientSocket();
openClientSocket();
}
/**
* Creates a connection on the specified socket. Waits {socketTimeout}
* seconds for a client connection before throwing an error
*/
private void openServerSocket() throws IOException
{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(timeout);
LoggingUtil.log("Opening server socket on port [" + port + "].");
}
/**
* Creates the client connection. This method will pause until the connection
* is made or the timout limit is reached.
*
* Once a connection is established opens the in and out buffer.
*/
private void openClientSocket() throws IOException
{
LoggingUtil.log("Waiting for client connection ...");
// This method blocks until a connection is made.
clientSocket = serverSocket.accept();
LoggingUtil.log("Client connected.");
LoggingUtil.log("Setting inbound buffer size to [" + inboundBufferSize + "] bytes.");
inboundReader = new InputStreamReader(new BufferedInputStream(clientSocket.getInputStream(), inboundBufferSize), "UTF-8");
outboundWriter = new OutputStreamWriter(new BufferedOutputStream(clientSocket.getOutputStream()), "UTF-8");
LoggingUtil.log("Receiving data ...");
}
/**
* Decides whether to send a policy request or a start ack
*/
private void prepareClientSocket() throws IOException
{
// if it's a policy request, make sure the first thing we send is a policy response
if (waitForPolicyFile)
{
String request = readNextTokenFromSocket();
if (request.equals(POLICY_FILE_REQUEST))
{
LoggingUtil.log("Policy file requested.");
sendPolicyFile();
resetInboundStream();
}
}
//tell client to start the testing process
sendTestRunStartAcknowledgement();
}
/**
* Generate domain policy message and send
*/
private void sendPolicyFile() throws IOException
{
sendOutboundMessage(MessageFormat.format(DOMAIN_POLICY, new Object[] { Integer.toString(port) }));
LoggingUtil.log("Policy file sent.");
}
/**
* Generate and send message to inform test runner to begin sending test data
*/
private void sendTestRunStartAcknowledgement() throws IOException
{
LoggingUtil.log("Sending acknowledgement to player to start sending test data ...\n");
sendOutboundMessage(START_OF_TEST_RUN_ACK);
}
/**
* Reads tokens from the socket input stream based on NULL_BYTE as a delimiter
*/
public String readNextTokenFromSocket() throws IOException
{
StringBuffer buffer = new StringBuffer();
int piece = -1;
while ((piece = inboundReader.read()) != NULL_BYTE)
{
//Did we reach the end of the buffer? Tell the user there is nothing more.
if (piece == -1)
{
return null;
}
final char chr = (char) piece;
buffer.append(chr);
}
//Did we recieve a message that the test run is over? Tell the user we have nothing more.
String token = buffer.toString();
return token;
}
private void sendOutboundMessage(String message) throws IOException
{
if(outboundWriter != null)
{
outboundWriter.write(message);
outboundWriter.write(NULL_BYTE);
outboundWriter.flush();
}
}
/**
* Stops the socket server, notifying the test runner, and closing the appropriate connections.
*/
public void stop() throws IOException, InterruptedException
{
LoggingUtil.log("\nStopping server ...");
sendTestRunEndAcknowledgement();
closeClientSocket();
closeServerSocket();
}
/**
* Sends the end of test run to the listener to close the connection
*/
private void sendTestRunEndAcknowledgement() throws IOException
{
LoggingUtil.log("End of test data reached, sending acknowledgement to player ...");
sendOutboundMessage(END_OF_TEST_RUN_ACK);
}
/**
* Closes the client connection and all buffers, ignoring any errors
*/
private void closeClientSocket() throws IOException
{
LoggingUtil.log("Closing client connection ...");
// Close the output stream.
if (outboundWriter != null)
{
outboundWriter.close();
}
// Close the input stream.
if (inboundReader != null)
{
inboundReader.close();
}
// Close the client socket.
if (clientSocket != null)
{
clientSocket.close();
}
}
/**
* Closes the server socket. Ignores any errors if unable to close
*/
private void closeServerSocket() throws IOException
{
LoggingUtil.log("Closing server on port [" + port + "] ...");
if (serverSocket != null)
{
serverSocket.close();
}
}
}