1
\$\begingroup\$

I'm developing an architecture with multiple users communicating with each other via server (Glassfish).

Inspiration for this was: http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/HomeWebsocket/WebsocketHome.html

The problem I face is large class. What I would like to achieve is to divide ClientSessionHandler class into classes responsible for:

  1. logging into system

  2. data exchanging between client-server-master client

Here is the code:

ClientSessionHandler.java

@ApplicationScoped
public class ClientSessionHandler {

private final Map<Session, Integer> sessions = new HashMap<>(); // sesja +
                                                                // ID
private final Map<Integer, Integer> clients = new HashMap<>(); // ID + state
private Session masterClientSession = null;
private AppOneData appOneData = new AppOneData();
private AppTwoData appTwoData = new AppTwoData();
private AppThreeData appThreeData = new AppThreeData();
private AppFourData appFourData = new AppFourData();

public ClientSessionHandler() {
    for (int i = 1; i <= 6; i++) {
        clients.put(i, 0);
    }
}

/* LOGGING */
/*********/
// add client to data structures
public void sendIdToClient(Integer clientID, Session session) {
    JsonProvider provider = JsonProvider.provider();
    JsonObject idMessage = provider.createObjectBuilder().add("action", "idDeclared").build();
    sessions.put(session, clientID);
    clients.put(clientID, Strings.LOGGED_IN);
    sendToSession(session, idMessage);
    // send to master-client aswell
    if (masterClientSession != null) {
        sendClientsStateToMasterClient(masterClientSession);
    }
}

// remove client from data structures
public void removeClient(Session session) {
    int clientID = sessions.remove(session);
    clients.put(clientID, Strings.LOGGED_OUT);

    if (masterClientSession != null) {
        sendClientsStateToMasterClient(masterClientSession);
    }
}

public void saveMasterClientSession(Session session) {
    masterClientSession = session;
}

// after master-client log-in
public void sendClientsStateToMasterClient(Session session) {
    JsonProvider provider = JsonProvider.provider();
    JsonObjectBuilder builder = provider.createObjectBuilder().add("action", "changedState");
    for (int i = 1; i <= clients.size(); i++) {
        builder.add("client" + i, (int) clients.get(i));
    }
    JsonObject idMessage = builder.build();
    sendToSession(session, idMessage);
}

// after change on client side
public void sendClientStateToMasterClient(int clientID, int clientState) {
    clients.put(clientID, clientState);
    if (masterClientSession != null) {
        sendClientsStateToMasterClient(masterClientSession);
    }
}

// response to client state change sent by master-client
public void sendMessageToClient(int clientID, int clientState, String task) {
    clients.put(clientID, clientState);
    Session clientSession = CollectionManager.getSessionFromID(sessions, clientID);

    if (clientSession != null) {
        JsonProvider provider = JsonProvider.provider();
        JsonObject idMessage = provider.createObjectBuilder().add("action", task).build();
        sendToSession(clientSession, idMessage);
    }
}

private void sendToSession(Session session, JsonObject message) {
    try {
        session.getBasicRemote().sendText(message.toString());
    } catch (IOException ex) {
        sessions.remove(session);
        Logger.getLogger(ClientSessionHandler.class.getName()).log(Level.SEVERE, null, ex);
    }
}

// data from app1
public void sendDataToMasterClient(int clientID, String controlName, boolean controlValue) {
    createAndSendUpdatingJson(controlName, controlValue, masterClientSession);
}

// data from master-client to app1
public void sendDataToClient(int clientID, String controlName, boolean controlValue) {
    Session clientSession = CollectionManager.getSessionFromID(sessions, clientID);
    createAndSendUpdatingJson(controlName, controlValue, clientSession);
}

// for the use of app1 both client and master-client
private void createAndSendUpdatingJson(String controlName, boolean controlValue, Session session) {
    JsonProvider provider = JsonProvider.provider();
    JsonObject idMessage = provider.createObjectBuilder().add("action", "toggle-element")
            .add("controlName", controlName).add("controlValue", controlValue).build();
    sendToSession(session, idMessage);
}

public void saveDataOnServer(int clientID, String controlName, boolean controlValue) {
    if (clientID == Strings.APPLICATION_ONE) {
        switch (controlName) {
        case Strings.APP_ONE_VALVE_1:
            appOneData.setValve1(controlValue);
            break;
        case Strings.APP_ONE_VALVE_2:
            appOneData.setValve2(controlValue);
            break;
        case Strings.APP_ONE_MOTOR_3:
            appOneData.setMotorA(controlValue);
            appOneData.setMotorB(!controlValue);
            break;
        case Strings.APP_ONE_MOTOR_4:
            appOneData.setMotorA(controlValue);
            appOneData.setMotorB(!controlValue);
            break;
        case Strings.APP_ONE_CHECKBOX_5:
            appOneData.setExhaust(controlValue);
            break;
        case Strings.APP_ONE_CHECKBOX_6:
            appOneData.setFilter(controlValue);
            break;
        }
        sendDataToMasterClient(clientID, controlName, controlValue);
        sendDataToClient(clientID, controlName, controlValue);
    }
}

// data from app2&3
public void sendDataToMasterClient(int clientID, String controlName, double controlValue, String output) {
    createAndSendUpdatingJson(controlName, controlValue, masterClientSession, output);
}

// data from master-client to app2&app3
public void sendDataToClient(int clientID, String controlName, double controlValue, String output) {
    Session clientSession = CollectionManager.getSessionFromID(sessions, clientID);
    createAndSendUpdatingJson(controlName, controlValue, clientSession, output);
}

// for the use of app2&3 both client and master-client
private void createAndSendUpdatingJson(String controlName, double controlValue, Session session, String output) {
    JsonProvider provider = JsonProvider.provider();
    JsonObject idMessage = provider.createObjectBuilder().add("action", "set-element-value")
            .add("controlName", controlName).add("controlValue", controlValue).add("outputName", output).build();
    sendToSession(session, idMessage);
}

public void saveDataOnServer(int clientID, String controlName, double controlValue, String output) {
    if (clientID == Strings.APPLICATION_TWO) {
        switch (controlName) {
        case Strings.APP_TWO_VELOCITY:
            appTwoData.setVelocityInput(controlValue);
            break;
        case Strings.APP_TWO_PRESSURE:
            appTwoData.setPressureInput(controlValue);
            break;
        }
    }
    if (clientID == Strings.APPLICATION_THREE) {
        switch (controlName) {
        case Strings.APP_THREE_VOLTAGE_1:
            appThreeData.setVoltage1(controlValue);
            break;
        case Strings.APP_THREE_VOLTAGE_2:
            appThreeData.setVoltage2(controlValue);
            break;
        }
    }
    sendDataToMasterClient(clientID, controlName, controlValue, output);
    sendDataToClient(clientID, controlName, controlValue, output);

}

public void generateNumbers(int clientID) {
    appFourData.setFirstTurn(RandomNumberGenerator.generateNumber(60));
    appFourData.setSecondTurn(RandomNumberGenerator.generateNumber(60));
    appFourData.setThirdTurn(RandomNumberGenerator.generateNumber(60));
    //TODO: 
}

}

And I present websocket class if someone finds it necessary:

@ApplicationScoped
@ServerEndpoint("/actions")
public class ClientsWebSocketServer {

@Inject
private ClientSessionHandler sessionHandler;

@OnOpen
public void open(Session session) {
}

@OnClose
public void close(Session session) {
    sessionHandler.removeClient(session);
}

@OnError
public void onError(Throwable error) {
}

@OnMessage
public void handleMessage(String message, Session session) {
    System.out.println(message+session);
    try(JsonReader reader = Json.createReader(new StringReader(message))) {
        JsonObject jsonMessage = reader.readObject();

        if("get-id".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            sessionHandler.sendIdToClient(clientID,session);
        }

        if("get-master-id".equals(jsonMessage.getString("action"))) {
            sessionHandler.saveMasterClientSession(session);
            sessionHandler.sendClientsStateToMasterClient(session);
        }

        if("client-ready".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            sessionHandler.sendClientStateToMasterClient(clientID, Strings.START_BUTTON_NOTIFICATION);
        }

        if("client-stop".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            sessionHandler.sendClientStateToMasterClient(clientID, Strings.LOGGED_IN);
        }

        if("allow-user".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            sessionHandler.sendMessageToClient(clientID, Strings.MASTER_CLIENT_APPROVAL, "permitOperating");
        }

        if("stop-user".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            sessionHandler.sendMessageToClient(clientID, Strings.LOGGED_IN, "prohibitOperating");
        }

        if("toggle-element".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            String name = jsonMessage.getString("name");
            boolean value = jsonMessage.getBoolean("value");
            sessionHandler.saveDataOnServer(clientID, name, value);
        }

        if("set-element-value".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            String name = jsonMessage.getString("name");
            double value = Double.parseDouble(jsonMessage.getString("value"));
            String output = jsonMessage.getString("output");
            sessionHandler.saveDataOnServer(clientID, name, value, output);
        }

        if("generate-number".equals(jsonMessage.getString("action"))) {
            int clientID = jsonMessage.getInt("id");
            sessionHandler.generateNumbers(clientID);
        }
    }
}
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

A few ideas:

First of all, I think splitting your class into several classes is a good idea. As I admittedly do not fully grasp the business logic, this is more or less up to you, but basically have the session handling in an application scoped bean and inject that into the various other derived classes.

Then, for easier maintenance of the various resulting handlers, I'd let the handler manage the decision whether to operate on a given message or not. You might inject an Instance of a common interface into the WebSocketServer to achieve this. Something like:

interface ClientHandler {
    boolean handlesCommand(String command);
    void handleCommand(String command, JsonObject parameters);
}

...

class GetIdHandler implements ClientHandler {
    boolean handlesCommand(String command) {
        return "get-id".equals(command);
    }

    void handleCommand(String command, JsonObject parameters) {
        ...
    }
}

...

// Usage
@Inject Instance<ClientHandler> handlers;

ClientHandler h = StreamSupport.stream(handlers.spliterator(), false)
    .filter(handler -> handler.handlesCommand(message))
    .findFirst()
    .orElseThrow(() -> new RuntimeException("Unknown command"));
h.handleCommand(...);
\$\endgroup\$
2
  • \$\begingroup\$ could you suggest where should I put sessions and clients maps? GetIdHandler should add element for each client and other Handler should have access to these maps. \$\endgroup\$
    – miki
    May 11, 2017 at 18:57
  • 1
    \$\begingroup\$ I'd probably create a "SessionRegistry" bean, which holds these maps and provides convenient manipulation methods (add, remove, get, query, whatever) and inject that bean everywhere I need it. (Make sure to use ApplicationScoped / Singleton for that.) \$\endgroup\$
    – mtj
    May 11, 2017 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.