package nl.tudelft.bw4t.server; import java.net.MalformedURLException; import java.rmi.AccessException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.ServerNotActiveException; import java.rmi.server.UnicastRemoteObject; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import eis.exceptions.ActException; import eis.exceptions.AgentException; import eis.exceptions.EntityException; import eis.exceptions.ManagementException; import eis.exceptions.QueryException; import eis.exceptions.RelationException; import eis.iilang.Action; import eis.iilang.EnvironmentState; import eis.iilang.Parameter; import eis.iilang.Percept; import nl.tudelft.bw4t.map.EntityType; import nl.tudelft.bw4t.network.BW4TClientActions; import nl.tudelft.bw4t.network.BW4TServerHiddenActions; import nl.tudelft.bw4t.scenariogui.BW4TClientConfig; import nl.tudelft.bw4t.scenariogui.BotConfig; import nl.tudelft.bw4t.scenariogui.EPartnerConfig; import nl.tudelft.bw4t.server.eis.EntityInterface; import nl.tudelft.bw4t.server.environment.BW4TEnvironment; import nl.tudelft.bw4t.server.environment.Launcher; /** * Server that allows connected client to perform actions and receive percepts * from the central {@link BW4TEnvironment} and notifies clients of new entities * at the central {@link BW4TEnvironment}, all using RMI */ public class BW4TServer extends UnicastRemoteObject implements BW4TServerHiddenActions { /** * The log4j logger, logs to the console. */ private static final Logger LOGGER = Logger.getLogger(BW4TServer.class); private static final long serialVersionUID = -3459272460308988888L; /** * Stores references to all connected clients plus information about them. * Is not transfered to the clients as it is only used by the server. */ private transient Map<BW4TClientActions, ClientInfo> clients; private String servername; /** * Create a new instance of the server * * @param serverIp * the ip address that the server should listen to * @param serverPort * the port that the server should listen to * * @throws RemoteException * if an exception occurs during the execution of a remote * object call * @throws MalformedURLException */ public BW4TServer(String serverIp, String serverPort) throws RemoteException, MalformedURLException { super(); reset(); try { LocateRegistry.createRegistry(Integer.parseInt(serverPort)); } catch (RemoteException e) { LOGGER.warn("Registry is already running. Getting running registry instead."); LocateRegistry.getRegistry(Integer.parseInt(serverPort)); } servername = "rmi://" + serverIp + ":" + serverPort + "/BW4TServer"; Naming.rebind(servername, this); LOGGER.debug("Server bound to: " + servername); } public BW4TServer(String serverIp, int serverPort) throws RemoteException, MalformedURLException { this(serverIp, Integer.toString(serverPort)); } /** * {@inheritDoc} */ @Override public synchronized void registerClient(BW4TClientActions client, int agentCount, int humanCount, Double speed) throws RemoteException { try { registerClient(client, new ClientInfo(getClientHost(), agentCount, humanCount, speed)); } catch (ServerNotActiveException e) { throw new RuntimeException("call to associateEntity comes from non-active server", e); } } @Override public synchronized void registerClient(BW4TClientActions client, BW4TClientConfig clientConfig) throws RemoteException { try { registerClient(client, new ClientInfo(clientConfig, getClientHost())); } catch (ServerNotActiveException e) { throw new RuntimeException("call to associateEntity comes from non-active server", e); } } private synchronized void registerClient(BW4TClientActions client, ClientInfo cInfo) throws RemoteException { BW4TEnvironment env = Launcher.getInstance().getEnvironment(); LOGGER.info("Registering client: " + client); clients.put(client, cInfo); // send the client our map client.useMap(BW4TEnvironment.getInstance().getMap()); client.handleStateChange(getState()); if (cInfo.getSpeed() != null) { env.setDelay((int) (1000. / cInfo.getSpeed())); } /* * proceed the entity launching in separate thread. not in main thread: * client registration is complete and we must now return to free up the * RMI port. */ launchEntities(client, cInfo, env); } /** * Launch entities and e-partners as a background job. This call returns * immediately. * * @param client * @param cInfo * @param env */ private void launchEntities(final BW4TClientActions client, final ClientInfo cInfo, final BW4TEnvironment env) { new Thread(new Runnable() { @Override public void run() { // for every request and attach them env.spawnBots(cInfo.getRequestedBots(), client); // for every request and attach them env.spawnEPartners(cInfo.getRequestedEPartners(), client); } }).start(); } @Override public synchronized void unregisterClient(BW4TClientActions client) throws ServerNotActiveException { if (clients.containsKey(client)) { try { BW4TEnvironment.getInstance().freeClient(client); } catch (EntityException | RelationException e) { e.printStackTrace(); } clients.remove(client); } } /** * Notify given client of a new free entity, if the new entity is of * interest for that client. Note, we use this both when an entity is new * and when an entity became free after use. * * @param client * @param ci * @throws EntityException */ public void notifyFreeEpartner(BW4TClientActions client, EPartnerConfig ci) throws EntityException { try { String entity = ci.getEpartnerName(); if ("unknown".equals(Launcher.getInstance().getEnvironment().getType(entity))) { BW4TEnvironment.getInstance().setType(entity, EntityType.EPARTNER.nameLower()); } client.handleNewEntity(entity); return; } catch (RemoteException e) { reportClientProblem(client, e); } } public void notifyFreeRobot(BW4TClientActions client, BotConfig ci) throws EntityException { try { String entity = ci.getBotName(); if ("unknown".equals(Launcher.getInstance().getEnvironment().getType(entity))) { String type = "bot"; if (EntityType.HUMAN == ci.getBotController()) { type = "human"; } BW4TEnvironment.getInstance().setType(entity, type); } client.handleNewEntity(entity); return; } catch (RemoteException e) { reportClientProblem(client, e); } } /** * {@inheritDoc} */ @Override public synchronized void unregisterAgent(String agent) throws AgentException { BW4TEnvironment.getInstance().unregisterAgent(agent); } /** * {@inheritDoc} */ @Override public Percept performEntityAction(String entity, Action action) throws RemoteException { try { return BW4TEnvironment.getInstance().performClientAction(entity, action); } catch (ActException e) { throw new RemoteException("action failed", e); } } /** * {@inheritDoc} */ @Override public void associateEntity(final String agentId, final String entityId) throws RelationException { BW4TEnvironment.getInstance().associateEntity(agentId, entityId); ((EntityInterface) BW4TEnvironment.getInstance().getEntity(entityId)).connect(); } /** * {@inheritDoc} */ @Override public synchronized void registerAgent(String agentId, BW4TClientActions client) throws RemoteException, AgentException { if (!clients.containsKey(client)) { throw new AgentException("client " + client + " has not registered"); } BW4TEnvironment.getInstance().registerAgent(agentId, client); } @Override public List<Percept> getAllPerceptsFromEntity(String entity) throws RemoteException { return BW4TEnvironment.getInstance().getAllPerceptsFrom(entity); } @Override public List<String> getAgents() throws RemoteException { return BW4TEnvironment.getInstance().getAgents(); } @Override public Set<String> getClientAgents(BW4TClientActions client) { return BW4TEnvironment.getInstance().getClientAgents(client); } @Override public Set<String> getAssociatedEntities(String agent) throws RemoteException, AgentException { return BW4TEnvironment.getInstance().getAssociatedEntities(agent); } @Override public Collection<String> getEntities() throws RemoteException { return BW4TEnvironment.getInstance().getEntities(); } @Override public void freeEntity(String entity) throws RemoteException, RelationException, EntityException { BW4TEnvironment.getInstance().freeEntity(entity); } @Override public void freeAgent(String agent) throws RemoteException, RelationException { try { BW4TEnvironment.getInstance().freeAgent(agent); } catch (EntityException e) { throw new RelationException("can't free agent", e); } } @Override public void freePair(String agent, String entity) throws RemoteException, RelationException { BW4TEnvironment.getInstance().freePair(agent, entity); } /** * {@inheritDoc} */ @Override public Collection<String> getAssociatedAgents(String entity) throws RemoteException, EntityException { return BW4TEnvironment.getInstance().getAssociatedAgents(entity); } /** * {@inheritDoc} */ @Override public Collection<String> getFreeEntities() throws RemoteException { return BW4TEnvironment.getInstance().getFreeEntities(); } /** * {@inheritDoc} */ @Override public boolean isSupportedByEnvironment(Action arg0) throws RemoteException { return BW4TEnvironment.getInstance().isSupportedByEnvironment(arg0); } /** * {@inheritDoc} */ @Override public String getType(String entity) throws RemoteException, EntityException { return BW4TEnvironment.getInstance().getType(entity); } /** * {@inheritDoc} */ @Override public EnvironmentState getState() throws RemoteException { return BW4TEnvironment.getInstance().getState(); } /** * {@inheritDoc} */ @Override public String queryProperty(String property) throws RemoteException, QueryException { return BW4TEnvironment.getInstance().queryProperty(property); } /** * {@inheritDoc} */ @Override public String queryEntityProperty(String entity, String property) throws RemoteException { return BW4TEnvironment.getInstance().queryEntityProperty(entity, property); } public void reset() { clients = new HashMap<>(); } public void notifyDeletedEntity(String entity, Collection<String> agents) { for (BW4TClientActions client : clients.keySet()) { try { client.handleDeletedEntity(entity, agents); } catch (RemoteException e) { reportClientProblem(client, e); } } } /** * This is called when we should call back to one of our clients but it * gives us an exception. * * @param client * @param e */ private void reportClientProblem(BW4TClientActions client, Exception e) { LOGGER.warn("Problems detected with client " + client, e); } /** * notify all clients of state change. If connection fails, this will modify * client hashmaps. Therefore callers should not iterate directly over the * clientWaitingForAgent and clientWaitingForHuman arrays. * * @param newState * @throws RemoteException */ public void notifyStateChange(EnvironmentState newState) { // duplicate the set before iteration, since we may call // unregisterClient. Set<BW4TClientActions> clientset = new HashSet<>(this.clients.keySet()); for (BW4TClientActions client : clientset) { try { client.handleStateChange(newState); } catch (RemoteException e) { reportClientProblem(client, e); try { unregisterClient(client); } catch (ServerNotActiveException e1) { e1.printStackTrace(); } } } } @Override public void requestStart() throws RemoteException, ManagementException { BW4TEnvironment.getInstance().start(); } @Override public void requestPause() throws RemoteException, ManagementException { BW4TEnvironment.getInstance().pause(); } /** * {@inheritDoc} */ @Override public void requestInit(Map<String, Parameter> parameters) throws RemoteException, ManagementException { BW4TEnvironment.getInstance().init(parameters); } @Override public void requestReset(Map<String, Parameter> parameters) throws RemoteException, ManagementException { throw new ManagementException("not implemented."); } /** * Notifies connected clients of new entities on a first come first serve * basis. All clients should notify server of how many entities they expect. * The server moves on when expectations of a client have been fulfilled. * * @param entity * , the new entity * @throws RemoteException * if an exception occurs during the execution of a remote * object call * @throws EntityException * if something unexpected happens when attempting to add or * remove an entity. */ public void notifyNewEntity(String entity) { } /** * Stop the RMI service. Used in total reset of env, eg when loading new * map. * * @throws NotBoundException * @throws RemoteException * @throws AccessException */ public void takeDown() { try { // Unregister ourself // Naming.unbind(servername); // Unexport; this will also remove us from the RMI runtime UnicastRemoteObject.unexportObject(this, true); } catch (RemoteException e) { LOGGER.error("server disconnect RMI failed", e); } } /** * {@inheritDoc} */ @Override public void stopServer(String key) throws RemoteException { Launcher.getInstance().getEnvironment().shutdownServer(key); } }