package nl.tudelft.bw4t.client; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.ServerNotActiveException; import java.rmi.server.UnicastRemoteObject; import java.util.Collection; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import eis.EnvironmentInterfaceStandard; import eis.EnvironmentListener; import eis.exceptions.AgentException; import eis.exceptions.EntityException; import eis.exceptions.ManagementException; import eis.exceptions.NoEnvironmentException; 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.client.environment.RemoteEnvironment; import nl.tudelft.bw4t.client.startup.ConfigFile; import nl.tudelft.bw4t.client.startup.InitParam; import nl.tudelft.bw4t.map.NewMap; import nl.tudelft.bw4t.network.BW4TClientActions; import nl.tudelft.bw4t.network.BW4TServerActions; import nl.tudelft.bw4t.network.BW4TServerHiddenActions; import nl.tudelft.bw4t.scenariogui.BW4TClientConfig; /** * A client remote object that can be registered to a BW4TServer. This object * lives at the client side. This object is a listener for server events, and * forwards them to the owner: the {@link RemoteEnvironment}. */ public class BW4TClient extends UnicastRemoteObject implements BW4TClientActions { private static final long serialVersionUID = -7174958200299731682L; /** * The parent that we serve. */ private final EnvironmentListener parent; private String bindAddress; private BW4TServerActions server; private static final Logger LOGGER = Logger.getLogger(BW4TClient.class); /** * the map that the server uses. */ private NewMap map; /** * Registry. Null while not yet connected. */ private Registry register = null; /** * Create a listener for the server. * * @param parent * is the parent {@link RemoteEnvironment} * @throws RemoteException * if an exception occurs during the execution of a remote * object call * @throws MalformedURLException * @throws NotBoundException */ public BW4TClient(EnvironmentListener parent) throws RemoteException, MalformedURLException, NotBoundException { this.parent = parent; } /** * run the client. This is provided as separate function from the * constructor, because the parent (BW4TEnvironment) needs to do some setup * with the given constructor, before it is really ready to handle callbacks * from the server. * * @param parameters * the set of initialization parameters as Map<String,Parameter>. * Should contain values for the following keys: * <ul> * <li>clientip * <li>clientport * <li>serverip * <li>serverport * <li>agentcount * <li>humancount * <li>goal * </ul> * Note, these are not all init parameters available to * BW4TRemoteEnvironment, these are just the ones needed to * launch this client class. * @throws MalformedURLException * @throws RemoteException * @throws NotBoundException */ public void connectServer() throws RemoteException, MalformedURLException, NotBoundException { int portNumber = Integer.parseInt(InitParam.CLIENTPORT.getValue()); // Launch the client and bind it while (register == null) { try { bindAddress = "rmi://" + InitParam.CLIENTIP.getValue() + ":" + portNumber + "/BW4TClient"; register = LocateRegistry.createRegistry(portNumber); } catch (Exception e) { LOGGER.warn("Registry was already created, trying the next port number.", e); portNumber++; } } Naming.rebind(bindAddress, this); LOGGER.info("The BW4T Client is bound to: " + bindAddress); // Register the client to the server String address = "//" + InitParam.SERVERIP.getValue() + ":" + InitParam.SERVERPORT.getValue() + "/BW4TServer"; try { server = (BW4TServerActions) Naming.lookup(address); } catch (Exception e) { LOGGER.error("The BW4T Client failed to connect to the server: " + address); throw new NoEnvironmentException("Failed to connect " + address, e); } } /** * Try to shutdown the server with the given parameters. * * @param shutdownParams * the parameters given by console */ public void shutdownServer() { String killKey = InitParam.KILL.getValue(); // Register the client to the server String address = "//" + InitParam.SERVERIP.getValue() + ":" + InitParam.SERVERPORT.getValue() + "/BW4TServer"; try { server = (BW4TServerActions) Naming.lookup(address); } catch (NotBoundException | MalformedURLException | RemoteException e) { LOGGER.info("The server is already down: " + address); return; } LOGGER.info("Attempting to shutdown the server with key: " + killKey); try { ((BW4TServerHiddenActions) server).stopServer(killKey); } catch (RemoteException e) { // Will always throw an exception because the server stop the // connection before we can get an answer from it return; } LOGGER.error("Unable to shutdown the server."); } /** * Register our client with the server. Provided separately, of run * * @param initParameters * @throws RemoteException */ public void register() throws RemoteException { ConfigFile file = InitParam.getConfigFile(); if (file != null) { BW4TClientConfig conf = file.getConfig(); LOGGER.info(String.format("Requesting %d robots and %d e-partners.", conf.getAmountBot(), conf.getAmountEPartner())); server.registerClient(this, conf); } else { int agentCountInt = Integer.parseInt(InitParam.AGENTCOUNT.getValue()); int humanCountInt = Integer.parseInt(InitParam.HUMANCOUNT.getValue()); Double speed = null; String speedstr = InitParam.SPEED.getValue(); if (!speedstr.isEmpty()) { speed = Double.parseDouble(speedstr); } LOGGER.info("Requesting " + agentCountInt + " automated agent(s) , " + humanCountInt + " human agent(s) and speed=" + speed); server.registerClient(this, agentCountInt, humanCountInt, speed); } } /** * kill the client interface. Does not kill the server, it just disconnects * the client. Make sure all entities and agents have been unbound before * doing this. * * @throws RemoteException * when we can't unregister from the server * @throws NotBoundException * when we can't stop RMI * @throws MalformedURLException * when there is internal error with bindAddress (should never * happen) * @throws ServerNotActiveException * if server can't contact us. */ public void kill() throws RemoteException, MalformedURLException, NotBoundException, ServerNotActiveException { server.unregisterClient(this); Naming.unbind(bindAddress); UnicastRemoteObject.unexportObject(register, true); LOGGER.warn("Client has been disconnected from server"); // bit of a hack. The server won't change to KILLED state just for one // killed client. // But this connector is dead and we have to notify our parent. handleStateChange(EnvironmentState.KILLED); } /** * Perform an entity action on the server * * @param entity * , the entity that should perform the action * @param action * , the action to be performed * @return the resulting percept of the action * @throws RemoteException * if an exception occurs during the execution of a remote * object call */ public Percept performEntityAction(String entity, Action action) throws RemoteException { LOGGER.debug("Entity " + entity + " performing action: " + action.toProlog()); return server.performEntityAction(entity, action); } /** * Associate an agent to an entity on the server * * @param agentId * , an already registered agent that should be associated to the * entity * @param entityId * , the entity that the agent should be associated to * @throws RelationException * if the attempt to manipulate the agents-entities-relation has * failed. * @throws RemoteException * if an exception occurs during the execution of a remote * object call */ public void associateEntity(String agentId, String entityId) throws RelationException, RemoteException { LOGGER.debug("Agent " + agentId + " associated with entity: " + entityId); server.associateEntity(agentId, entityId); } /** * Register an agent to the server, should be done before trying to * associate the agent with an entity * * @param agentId * , the agent to be registered * @throws RemoteException * if an exception occurs during the execution of a remote * object call * @throws AgentException * if the attempt to register or unregister an agent has failed. */ public void registerAgent(String agentId) throws RemoteException, AgentException { server.registerAgent(agentId, this); LOGGER.debug("Register agent: " + agentId); } /** * Get all percepts for a certain entity from the server * * @param entity * , the entity for which percepts should be gotten * @return a list of all received percepts * @throws RemoteException * if an exception occurs during the execution of a remote * object call */ public List<Percept> getAllPerceptsFromEntity(String entity) throws RemoteException { return server.getAllPerceptsFromEntity(entity); } /** * Get all agents registered on the server * * @return a list of agent names * @throws RemoteException * , if an exception occurs during the execution of a remote * object call */ public List<String> getAgents() throws RemoteException { return server.getAgents(); } /** * @return all agents running on this client */ public Set<String> getLocalAgents() throws RemoteException { return server.getClientAgents(this); } /** * Get all associated entities for a certain agent from the server * * @param agent * , the agent * @return a list of entity names * @throws RemoteException * , if an exception occurs during the execution of a remote * object call * @throws AgentException * , if an attempt to register or unregister an agent has * failed. */ public Set<String> getAssociatedEntities(String agent) throws RemoteException, AgentException { return server.getAssociatedEntities(agent); } /** * Unregister an agent on the server * * @param agentId * , the agent to unregister * @throws AgentException * , if an attempt to register or unregister an agent has * failed. * @throws RemoteException * , if an exception occurs during the execution of a remote * object call */ public void unregisterAgent(String agentId) throws AgentException, RemoteException { server.unregisterAgent(agentId); LOGGER.debug("Unregistered agent: " + agentId); } /** * Get all the entities on the server * * @return the list of entities * @throws RemoteException * , if an exception occurs during the execution of a remote * object call */ public Collection<String> getEntities() throws RemoteException { return server.getEntities(); } /** * Free an entity on the server * * @param entity * , the entity to free * @throws RemoteException * , if an exception occurs during the execution of a remote * object call * @throws RelationException * , if an attempt to manipulate the agents-entities-relation * has failed. * @throws EntityException * , if something unexpected happens when attempting to add or * remove an entity. */ public void freeEntity(String entity) throws RemoteException, RelationException, EntityException { server.freeEntity(entity); } /** * Free an agent on the server * * @param agent * , the agent to free * @throws RemoteException * , if an exception occurs during the execution of a remote * object call * @throws RelationException * , if an attempt to manipulate the agents-entities-relation * has failed. */ public void freeAgent(String agent) throws RemoteException, RelationException { server.freeAgent(agent); } /** * Free an agent-entity pair on the server * * @param agent * , the agent for which an entity should be freed * @param entity * , the entity to be freed * @throws RemoteException * , if an exception occurs during the execution of a remote * object call * @throws RelationException * , if an attempt to manipulate the agents-entities-relation * has failed. */ public void freePair(String agent, String entity) throws RemoteException, RelationException { server.freePair(agent, entity); } /** * Get all associated agents for a certain entity from the server * * @param entity * , the entity for which to get all associated agents * @return the list of agents * @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 Collection<String> getAssociatedAgents(String entity) throws RemoteException, EntityException { return server.getAssociatedAgents(entity); } /** * Get all free entities on the server * * @return all free entities * @throws RemoteException * , if an exception occurs during the execution of a remote * object call */ public Collection<String> getFreeEntities() throws RemoteException { return server.getFreeEntities(); } /** * Get the type of an entity * * @param entity * , the entity for which to get its type * @return the type of the 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 String getType(String entity) throws RemoteException, EntityException { return server.getType(entity); } /** * Get the state of the environment * * @return the state of the environment * @throws RemoteException * , if an exception occurs during the execution of a remote * object call */ public EnvironmentState getState() throws RemoteException { return server.getState(); } /** * Query a property of the server environment * * @param property * , the property to query * @return the property's value * @throws QueryException * the server was unable to query * @throws RemoteException * if an exception occurs during the execution of a remote * object call */ public String queryProperty(String property) throws QueryException, RemoteException { return server.queryProperty(property); } /** * Query a property of an entity on the server * * @param entity * , the entity for which to query the property * @param property * , the property to query * @return the value of the property * @throws RemoteException * , if an exception occurs during the execution of a remote * object call */ public String queryEntityProperty(String entity, String property) throws RemoteException { return server.queryEntityProperty(entity, property); } /** * * @param arg0 * The action to be checked * @return returns true if an Action is supported by the environment * @throws RemoteException */ public boolean isSupportedByEnvironment(Action arg0) throws RemoteException { return server.isSupportedByEnvironment(arg0); } /** * tell server to start. This will cause callback to us of state change when * succesful. * * @throws ManagementException * Throws a ManagementException when the server fails to start */ public void start() throws ManagementException { try { server.requestStart(); } catch (RemoteException e) { throw new ManagementException("start failed", e); } } /** * tell server to stop. This will cause callback to us of state change when * succesful. * * @throws ManagementException * Throws a ManagementException when the server fails to pause */ public void pause() throws ManagementException { try { server.requestPause(); } catch (RemoteException e) { throw new ManagementException("pause failed", e); } } /** * initialize the server. * * @param parameters * init parameters for eis. see * {@link EnvironmentInterfaceStandard#init(java.util.Map)} * @throws ManagementException * Throws a ManagementException when the server fails to * initialize */ public void initServer(java.util.Map<String, Parameter> parameters) throws ManagementException { try { server.requestInit(parameters); } catch (RemoteException e) { throw new ManagementException("server init failed", e); } } /** * reset the server, used for BatchRunner. NOTE: we don't do reset as it's * not implemented, but we just do an INIT. This will clear and re-create * the entities. * * @param parameters * init parameters for eis. see * {@link EnvironmentInterfaceStandard#init(java.util.Map)} * @throws ManagementException * Throws a ManagementException when the server fails to reset */ public void resetServer(java.util.Map<String, Parameter> parameters) throws ManagementException { try { server.requestInit(parameters); LOGGER.info("BW4T Server was reset."); } catch (RemoteException e) { throw new ManagementException("server reset failed", e); } } /**********************************************************************/ /******************** Implements BW4TClientActions ********************/ /**********************************************************************/ /** * {@inheritDoc} */ @Override public void handleNewEntity(String entity) throws RemoteException, EntityException { parent.handleNewEntity(entity); } /** * {@inheritDoc} */ @Override public void handleFreeEntity(String entity, Collection<String> agents) throws RemoteException { parent.handleFreeEntity(entity, agents); } /** * {@inheritDoc} */ @Override public void handleDeletedEntity(String entity, Collection<String> agents) throws RemoteException { parent.handleDeletedEntity(entity, agents); } /** * {@inheritDoc} */ @Override public void handleStateChange(EnvironmentState newState) throws RemoteException { parent.handleStateChange(newState); } @Override public void useMap(NewMap newMap) { map = newMap; } public NewMap getMap() { return map; } }