package no.ntnu.qos.testclient; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.SocketException; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; import no.ntnu.qos.client.ExceptionHandler; import no.ntnu.qos.client.QoSClient; import no.ntnu.qos.client.ReceiveObject; import no.ntnu.qos.client.impl.QoSClientImpl; /** * TestClient is a test web service client using the client library to request web services. * @author Ola Martin * */ public class TestClient implements ExceptionHandler{ /** * Enum to define log type. */ private enum LogType{ INFO, WARN, SEVERE; } /** * finals to use when getting configuration entries. */ private final String DO_LOG = "doLog"; private final String LOG_TO_FILE = "logToFile"; private final String FALSE = "false"; private final String TRUE = "true"; private final String USERNAME = "username"; private final String PASSWORD = "password"; private final String ROLE = "role"; private final String SERVICE = "service"; private final String INTERVAL = "interval"; private final String DELAY = "delay"; private final String DATA = "request"; private final String NOFREQUESTS = "nofreqs"; private final String REQID = "REQID"; private final long DEFAULT_DELAY = 0; /** * Integer to count how many responses are received. */ private static final AtomicInteger responses = new AtomicInteger(0); /** * The ID of the request, incremented once for every request. */ private static final AtomicInteger requestID = new AtomicInteger(0); /** * The file to read configuration from, may be overridden with the only command line argument. */ private String configFile = "client.config"; /** * HashMap containing all the entries in configFile. */ private Map<String, String> config = new HashMap<String, String>(); /** * The client library */ private QoSClient connection; /** * The service URI */ private URI destination; /** * Timer used to schedule tasks */ private static Timer timer; /** * The number of requests to send. */ private int nofRequests = 0; /** * Starts the TestClient using the default configFile. */ public TestClient() { start(); } /** * Starts the TestClient using the parameter as configFile * @param configFile */ public TestClient(String configFile) { this.configFile=configFile; start(); } /** * Starts the TestClient */ private void start(){ //read the configuration readConfig(); //Checks whether or not needed variables are present in configuration. if(!config.containsKey(USERNAME)){ logLine("Config must contain: "+USERNAME, LogType.SEVERE); return; } if(!config.containsKey(ROLE)){ logLine("Config must contain: "+ROLE, LogType.SEVERE); return; } if(!config.containsKey(PASSWORD)){ logLine("Config must contain: "+PASSWORD, LogType.SEVERE); return; } if(!config.containsKey(SERVICE)){ logLine("Config must contain: "+SERVICE, LogType.SEVERE); return; }else{ try { destination = new URI(config.get(SERVICE)); } catch (URISyntaxException e) { logLine(SERVICE+" syntax faulty: "+config.get(SERVICE), LogType.SEVERE); return; } } if(!config.containsKey(DATA)){ logLine("Config must contain: "+DATA, LogType.SEVERE); return; } logLine(USERNAME+" set to: "+config.get(USERNAME)+ ", "+ROLE+" set to: "+config.get(ROLE)+ ", "+PASSWORD+" set to: "+config.get(PASSWORD)+ ", "+SERVICE+" set to: "+config.get(SERVICE)+ ", "+DATA+" set to: "+config.get(DATA)); //Makes an implementation of the client library. connection = new QoSClientImpl(config.get(USERNAME), config.get(ROLE), config.get(PASSWORD), this); //Sets whether or not to log in the client library connection.setFineLogging(true); connection.setLogToFile(config.containsKey(LOG_TO_FILE)&&config.get(LOG_TO_FILE).equals(TRUE)); connection.setLogToConsole(false); //Gets the delay before starting sending requests from the configuration. //If not present DEFAULT_DELAY will be used. long delay = DEFAULT_DELAY; if(config.containsKey(DELAY)){ try{ delay = Long.parseLong(config.get(DELAY)); logLine(DELAY+" set to: "+delay); }catch(NumberFormatException nfe){ logLine(DELAY+" not int, using default delay: "+DEFAULT_DELAY, LogType.WARN); } }else{ logLine(DELAY+" not specified, using default delay: "+DEFAULT_DELAY); } //Gets the Interval between sending requests from the configuration. //If not present, only one request will be sent. long interval = -1; if(config.containsKey(INTERVAL)){ try{ interval = Long.parseLong(config.get(INTERVAL)); logLine(INTERVAL+" set to: "+interval); }catch(NumberFormatException nfe){ logLine(INTERVAL+" not int, send only one request", LogType.WARN); } }else{ logLine(INTERVAL+" not specified, send only one request"); } //Gets the number of requests to be sent from the configuration. //If not present, will send until killed, unless interval is not defined. if(config.containsKey(NOFREQUESTS)){ try{ nofRequests = Integer.parseInt(config.get(NOFREQUESTS)); logLine(NOFREQUESTS+" set to: "+nofRequests); }catch(NumberFormatException nfe){ logLine(NOFREQUESTS+" not int, send forever unless no interval", LogType.WARN); } }else{ logLine(NOFREQUESTS+" not specified, send forever unless no interval"); } //The timerTask used for starting sending of requests. TimerTask requestTask = new TimerTask() { @Override public void run() { new Thread(new RequestSender()).start(); } }; //Schedules sending of requests. timer = new Timer(); if(interval > 0){ logLine("Sending requests with "+DELAY+"="+delay+" and "+INTERVAL+"="+interval); timer.scheduleAtFixedRate(requestTask, delay, interval); }else{ logLine("Sending request with "+DELAY+"="+delay); timer.schedule(requestTask, delay); } } /** * Used to send requests to the destination using the client library * * @author Ola Martin * */ private class RequestSender implements Runnable { /** * */ @Override public void run() { //Increments and gets the request ID int reqID = requestID.incrementAndGet(); //If this is the last request to be sent, the timer is stopped, so no more requests are sent. if(reqID==nofRequests){ timer.cancel(); } logLine("Sending "+DATA+" "+reqID+" to "+config.get(SERVICE)); //takes a note of what the current time is. long sendTime = System.currentTimeMillis(); //Uses connection.sendData(data, destination) to send a request. //Here {REQID} in the request found in clientConfig is replaced with {REQID=reqID} //The client library will put the response in the returned ReceiveObject when it is received. ReceiveObject ro = connection.sendData( config.get(DATA).replace("{"+REQID+"}", "{"+REQID+"="+reqID+"}"), destination); try { logLine("Waiting for response "+reqID); //Calls the blocking method ReceiveObject.receive() to get the response. //An alternative to this would be to make a DataListener and add it to connection. //this way, every listener would receive all the responses, //and would be unsuited for this test String response = ro.receive(); //Checks the response for validity. checkResponse(response, reqID, System.currentTimeMillis()-sendTime); } catch (InterruptedException e) { logLine("Could not receive response "+reqID+": interuptedException", LogType.WARN); } //checks if program should exit exitCheck(); } } /** * Checks a response for validity. * @param response, the response * @param reqID, the ID number of the request * @param time when request was sent. */ private synchronized void checkResponse(String response, int reqID, long time){ //finds the position of '{REQID=reqId}' in the response. int start = response.indexOf("{"+REQID+"=")+("{"+REQID+"=").length(); int end = response.indexOf("}", start); //checks whether or not the reqId is found. if(end>start && start>0){ //finds the responses ID, so it can be logged if we get the response for a different request. int responseReqID = Integer.parseInt(response.substring(start, end)); //Logging of response ID and the length of the response if(reqID==responseReqID){ logLine(time+"ms: Got response for "+reqID+", size="+response.length()); }else{ logLine(time+"ms: Request "+reqID+" got response for "+responseReqID+ ", size="+response.length(), LogType.SEVERE); } }else{ //If we get a response without reqId we log this, as well as the response. //The client library will respond with an exception name if an exception is catched. logLine(time+"ms: Got faulty response for "+reqID+", "+response); } } /** * Reads the configuration file * ignores lines starting with '%' * adds lines containing ':' to the config HashMap * with what is before ':' as Key and what is after as Value. * stops when a line without '%' or ':' is encountered. */ private void readConfig(){ logLine("Reading config from file: "+configFile); try { BufferedReader br = new BufferedReader(new FileReader(new File(configFile))); String in = br.readLine(); while(in != null){ if(!in.startsWith("%")){ if(in.contains(":")){ String[] property = in.split(":", 2); config.put(property[0], property[1]); }else{ break; } } in = br.readLine(); } logLine("Config read from file: "+configFile); } catch (FileNotFoundException e) { logLine("Could not read config: file "+configFile+ " not found.",LogType.WARN); e.printStackTrace(); } catch (IOException e) { logLine("Could not read config: IOException, file "+configFile+ " might contain error?.",LogType.WARN); e.printStackTrace(); } } /** * Checks whether or not we have received all of our responses, and exits if we have. */ private void exitCheck(){ if(responses.incrementAndGet()==nofRequests){ logLine("Test Client Complete, terminating"); System.exit(0); } } /** * Logs to System.out if logging is enabled. * @param toLog, what to log * @param type, what type of log entry it is. */ private void log(String toLog, LogType type){ if(config.containsKey(DO_LOG) && config.get(DO_LOG).equals(FALSE)){ return; }else{ System.out.print(type+": "+toLog); } } /** * Logs a line using log(String,LogType) * @param toLog * @param type */ private void logLine(String toLog, LogType type){ log(toLog+"\n"); } /** * Logs a line using logLine(String,LogType.INFO) * @param toLog */ private void logLine(String toLog){ logLine(toLog, LogType.INFO); } /** * Logs using log(String, LogType.INFO) * @param toLog */ private void log(String toLog){ log(toLog, LogType.INFO); } /** * Starts the program. * @param args, clientConfig filename to use if present. */ public static void main(String[] args) { if(args.length>0){ new TestClient(args[0]); }else{ new TestClient(); } } /** * Logs an Exception * @param e, the Exception to log */ public void logException(Exception e){ logLine("Got exception: "+e.toString()+", "+e.getMessage(), LogType.WARN); } @Override public void unknownHostExceptionThrown(UnknownHostException e) { logException(e); } @Override public void ioExceptionThrown(IOException e) { logException(e); } @Override public void httpExceptionThrown(Exception e) { logException(e); } @Override public void socketExceptionThrown(SocketException e) { logException(e); } @Override public void unsupportedEncodingExceptionThrown( UnsupportedEncodingException e) { logException(e); } }