/******************************************************************************* * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.gebit.integrity.remoting.server; import java.io.IOException; import java.io.Serializable; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import de.gebit.integrity.remoting.IntegrityRemotingConstants; import de.gebit.integrity.remoting.entities.setlist.SetListEntry; import de.gebit.integrity.remoting.transport.Endpoint; import de.gebit.integrity.remoting.transport.MessageProcessor; import de.gebit.integrity.remoting.transport.ServerEndpoint; import de.gebit.integrity.remoting.transport.enums.BreakpointActions; import de.gebit.integrity.remoting.transport.enums.ExecutionStates; import de.gebit.integrity.remoting.transport.enums.TestRunnerCallbackMethods; import de.gebit.integrity.remoting.transport.messages.AbortExecutionMessage; import de.gebit.integrity.remoting.transport.messages.AbstractMessage; import de.gebit.integrity.remoting.transport.messages.BreakpointUpdateMessage; import de.gebit.integrity.remoting.transport.messages.ExecutionControlMessage; import de.gebit.integrity.remoting.transport.messages.ExecutionStateMessage; import de.gebit.integrity.remoting.transport.messages.IntegrityRemotingVersionMessage; import de.gebit.integrity.remoting.transport.messages.SetListBaselineMessage; import de.gebit.integrity.remoting.transport.messages.SetListUpdateMessage; import de.gebit.integrity.remoting.transport.messages.ShutdownRequestMessage; import de.gebit.integrity.remoting.transport.messages.TestRunnerCallbackMessage; import de.gebit.integrity.remoting.transport.messages.VariableUpdateMessage; /** * The server implementation. * * @author Rene Schneider - initial API and implementation * */ public class IntegrityRemotingServer { /** * The actual server communication endpoint. */ private ServerEndpoint serverEndpoint; /** * The server listener. */ private IntegrityRemotingServerListener listener; /** * The current execution state. */ private ExecutionStates executionState; /** * The bound port. */ private int port; /** * Whether this remoting server has been initialized as a fork. */ private boolean isFork; /** * Creates a new server, listening on a specified port and a specified host IP. * * @param aHostIP * the host IP to listen on * @param aPort * the port to listen on * @param aListener * the listener * @param aClassLoader * the classloader to use when deserializing objects * @param anIsForkFlag * whether this remoting server is serving inside an Integrity fork process * @throws UnknownHostException * @throws IOException */ public IntegrityRemotingServer(String aHostIP, int aPort, IntegrityRemotingServerListener aListener, ClassLoader aClassLoader, boolean anIsForkFlag) throws UnknownHostException, IOException { if (aListener == null) { throw new IllegalArgumentException("A listener must be provided."); } listener = aListener; port = aPort; isFork = anIsForkFlag; serverEndpoint = new ServerEndpoint(aHostIP, aPort, createProcessors(), aClassLoader, anIsForkFlag); } /** * Closes all connections to clients. * * @param anEmptyOutputQueueFlag * whether the output message queue shall be emptied before closing the connection */ public void closeAll(boolean anEmptyOutputQueueFlag) { try { Thread.sleep(2000); } catch (InterruptedException exc) { // TODO Auto-generated catch block exc.printStackTrace(); } if (serverEndpoint.isActive()) { serverEndpoint.closeAll(anEmptyOutputQueueFlag); } } /** * Updates the execution state, broadcasting a message to all clients to notify them about that change. * * @param aNewState * the new state */ public void updateExecutionState(ExecutionStates aNewState) { if (aNewState != executionState) { executionState = aNewState; serverEndpoint.broadcastMessage(new ExecutionStateMessage(aNewState)); } } /** * Transmits updates to some {@link SetListEntry} instances to all clients. * * @param anEntryInExecution * the entry in execution (null if nothing changed) * @param someUpdatedEntries * the updated entries */ public void updateSetList(Integer anEntryInExecution, SetListEntry... someUpdatedEntries) { if (serverEndpoint.isActive()) { serverEndpoint.broadcastMessage(new SetListUpdateMessage(anEntryInExecution, someUpdatedEntries)); } } /** * Notifies all clients about the creation of a breakpoint. * * @param anEntryReference * the entry at which the breakpoint was created */ public void confirmBreakpointCreation(int anEntryReference) { if (serverEndpoint.isActive()) { serverEndpoint.broadcastMessage(new BreakpointUpdateMessage(BreakpointActions.CREATE, anEntryReference)); } } /** * Notifies all clients about the removal of a breakpoint. * * @param anEntryReference * the entry at which the breakpoint was removed */ public void confirmBreakpointRemoval(int anEntryReference) { if (serverEndpoint.isActive()) { serverEndpoint.broadcastMessage(new BreakpointUpdateMessage(BreakpointActions.REMOVE, anEntryReference)); } } /** * Sends data from a test runner callback in a fork to the master, which will then forward it to the matching * callback. * * @param aCallbackClassName * the name of the callback class * @param aMethod * the method being called * @param someData * the data */ public void sendTestRunnerCallbackData(String aCallbackClassName, TestRunnerCallbackMethods aMethod, Serializable[] someData) { if (serverEndpoint.isActive()) { serverEndpoint.broadcastMessage(new TestRunnerCallbackMessage(aCallbackClassName, aMethod, someData)); } } /** * Transmits an update for a variables' value to the master. * * @param aVariableName * the name of the variable * @param aValue * the updated value */ public void sendVariableUpdate(String aVariableName, Serializable aValue) { if (serverEndpoint.isActive()) { serverEndpoint.broadcastMessage(new VariableUpdateMessage(aVariableName, aValue)); } } /** * Transmits an abortion message. * * @param anExceptionMessage * @param anExceptionStackTrace */ public void sendAbortMessage(String anExceptionMessage, String anExceptionStackTrace) { if (serverEndpoint.isActive()) { serverEndpoint.broadcastMessage(new AbortExecutionMessage(anExceptionMessage, anExceptionStackTrace)); } } /** * Creates the processors for processing incoming messages. * * @return a map of message classes to processors */ protected Map<Class<? extends AbstractMessage>, MessageProcessor<?>> createProcessors() { Map<Class<? extends AbstractMessage>, MessageProcessor<?>> tempMap = new HashMap<Class<? extends AbstractMessage>, MessageProcessor<?>>(); tempMap.put(IntegrityRemotingVersionMessage.class, new MessageProcessor<IntegrityRemotingVersionMessage>() { @Override public void processMessage(IntegrityRemotingVersionMessage aVersion, Endpoint anEndpoint) { if (IntegrityRemotingConstants.MAJOR_PROTOCOL_VERSION == aVersion.getProtocolMajorVersion()) { listener.onConnectionSuccessful(aVersion, anEndpoint); } anEndpoint.sendMessage(new IntegrityRemotingVersionMessage( IntegrityRemotingConstants.MAJOR_PROTOCOL_VERSION, IntegrityRemotingConstants.MINOR_PROTOCOL_VERSION, IntegrityRemotingConstants.MAJOR_VERSION, IntegrityRemotingConstants.MINOR_VERSION, IntegrityRemotingConstants.PATCH_VERSION, IntegrityRemotingConstants.BUILD_VERSION)); } }); tempMap.put(SetListBaselineMessage.class, new MessageProcessor<SetListBaselineMessage>() { @Override public void processMessage(SetListBaselineMessage aBaselineRequest, Endpoint anEndpoint) { listener.onSetListRequest(anEndpoint); } }); tempMap.put(ExecutionStateMessage.class, new MessageProcessor<ExecutionStateMessage>() { @Override public void processMessage(ExecutionStateMessage aBaselineRequest, Endpoint anEndpoint) { anEndpoint.sendMessage(new ExecutionStateMessage(executionState)); } }); tempMap.put(ExecutionControlMessage.class, new MessageProcessor<ExecutionControlMessage>() { @Override public void processMessage(ExecutionControlMessage aRequest, Endpoint anEndpoint) { switch (aRequest.getCommand()) { case RUN: listener.onRunCommand(anEndpoint); break; case PAUSE: listener.onPauseCommand(anEndpoint); break; case STEP_INTO: listener.onStepIntoCommand(anEndpoint); break; default: break; } } }); tempMap.put(BreakpointUpdateMessage.class, new MessageProcessor<BreakpointUpdateMessage>() { @Override public void processMessage(BreakpointUpdateMessage aMessage, Endpoint anEndpoint) { switch (aMessage.getAction()) { case CREATE: listener.onCreateBreakpoint(aMessage.getEntryReference(), anEndpoint); break; case REMOVE: listener.onRemoveBreakpoint(aMessage.getEntryReference(), anEndpoint); break; default: break; } } }); tempMap.put(VariableUpdateMessage.class, new MessageProcessor<VariableUpdateMessage>() { @Override public void processMessage(VariableUpdateMessage aMessage, Endpoint anEndpoint) { listener.onVariableUpdateRetrieval(aMessage.getName(), aMessage.getValue()); } }); tempMap.put(ShutdownRequestMessage.class, new MessageProcessor<ShutdownRequestMessage>() { @Override public void processMessage(ShutdownRequestMessage aMessage, Endpoint anEndpoint) { listener.onShutdownRequest(); } }); return tempMap; } public int getPort() { return port; } public boolean isFork() { return isFork; } }