Java tutorial
package io.hosuaby.restful.simulators; import io.hosuaby.restful.domain.Teapot; import io.hosuaby.restful.domain.TeapotMessage; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.websocket.ClientEndpointConfig; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; import javax.websocket.DeploymentException; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import javax.websocket.WebSocketContainer; import com.fasterxml.jackson.databind.ObjectMapper; /** * Creates simulator of teapot that communicates with server via websocket. */ // TODO: get port from environment instead use of PortHolder public class TeapotSimulator { /** EOF character */ private static final char EOT = 0x004; /** Cancel character */ private static final char CAN = 0x024; /** URL for teapot registration */ private static final String REGISTER_URL = "ws://0.0.0.0:%d/teapots/register/%s"; /** Pattern to match tokens from message payload */ private static final Pattern TOKENS_PATTERN = Pattern.compile("\\S+"); // TODO: Implement custom protocol instead of manual conversion with Jackson private static final ObjectMapper mapper = new ObjectMapper(); /** Teapot file system */ private TeapotFs fs; /** * Creates and starts teapot simulator. * * @param teapot teapot domain object * @param port port number of the server * * @throws URISyntaxException * @throws DeploymentException * @throws IOException */ public TeapotSimulator(Teapot teapot, int port) throws URISyntaxException, DeploymentException, IOException { /* Get websocket container */ final WebSocketContainer container = ContainerProvider.getWebSocketContainer(); /* Configuration of teapot client endpoint */ final ClientEndpointConfig teapotConfig = ClientEndpointConfig.Builder.create().build(); /* Disable websocket timeout */ container.setDefaultMaxSessionIdleTimeout(0); URI uri = new URI(String.format(REGISTER_URL, port, teapot.getId())); /* Create websocket client for the teapot */ container.connectToServer(new TeapotSimulatorEndpoint(this), teapotConfig, uri); /* Create the file system */ fs = new TeapotFs(); /* Create help.txt file */ fs.cat("help.txt", createHelpFileContent()); /* Create license file */ fs.cat("license", createLicenseFileContent()); /* Create config.json file */ fs.cat("config.json", createConfigFileContent(teapot)); } /** * Executes <code>ls</code> command on the teapot simulator. */ public String[] ls() { return fs.ls(); } /** * Executes <code>cat</code> command on the teapot simulator. * * @param filename filename * @return content of the file */ public String[] cat(String filename) { return fs.cat(filename); } /** * Executes <code>cat</code> command on the teapot simulator. * * @param filename filename * @param content content to write */ public void cat(String filename, String[] content) { fs.cat(filename, content); } /** * Executes <code>touch</code> command on the teapot simulator. * * @param filename file to create */ public void touch(String filename) { fs.touch(filename); } /** * Executes <code>touch</code> command on the teapot simulator. * * @param oldFilename old filename * @param newFilename new filename */ public void mv(String oldFilename, String newFilename) { fs.mv(oldFilename, newFilename); } /** * Executes <code>touch</code> command on the teapot simulator. * * @param filename filename */ public void rm(String filename) { fs.rm(filename); } /** * @return content for help.txt file */ private static String[] createHelpFileContent() { return new String[] { "Teapot simulator. Available commands:", " ls lists existing files", " cat prints content to stdout or to file", " touch creates new empty file", " mv renames the file", " rm remove the file" }; } /** * @return content for license file */ private static String[] createLicenseFileContent() { return new String[] { "The MIT License (MIT)", "", "Copyright (c) 2015 Alexei KLENIN", "", "Permission is hereby granted, free of charge, to any person obtaining a copy", "of this software and associated documentation files (the \"Software\"), to deal", "in the Software without restriction, including without limitation the rights", "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", "copies of the Software, and to permit persons to whom the Software is", "furnished to do so, subject to the following conditions:", "", "The above copyright notice and this permission notice shall be included in all", "copies or substantial portions of the Software.", "", "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE." }; } /** * Creates content of config.json file from teapot domain object. * * @param teapot teapot domain object * @return content of config.json file */ private static String[] createConfigFileContent(Teapot teapot) { return new String[] { "{", " \"id\": \"" + teapot.getId() + "\",", " \"name\": \"" + teapot.getName() + "\",", " \"brand:\"" + teapot.getBrand() + "\",", " \"volume:\"" + teapot.getVolume() + "\"", "}" }; } /** * Websocket endpoint of teapot simulator. */ private static class TeapotSimulatorEndpoint extends Endpoint { /** Used simulator */ private TeapotSimulator simulator; /** * Constructor from simulator object. * * @param simulator simulator object */ public TeapotSimulatorEndpoint(TeapotSimulator simulator) { this.simulator = simulator; } /** * Attaches message handler on opening of the session. */ @Override public void onOpen(Session session, EndpointConfig config) { session.addMessageHandler(new TeapotSimulatorMessageHandler(simulator, session)); } /** * Prints the reason of closure of the session of the standard output. */ @Override public void onClose(Session session, CloseReason closeReason) { System.out.println("Session of teapot was closed for reason: " + closeReason.getCloseCode() + " " + closeReason.getReasonPhrase()); } } /** * Handler for incoming messages. */ private static class TeapotSimulatorMessageHandler implements MessageHandler.Whole<String> { /** Teapot simulator object */ private TeapotSimulator simulator; /** Websocket session */ private Session session; /** * Constructor from simulator and websocket session. * * @param simulator simulator object * @param session websocket object */ public TeapotSimulatorMessageHandler(TeapotSimulator simulator, Session session) { this.simulator = simulator; this.session = session; } /** * Handler for incoming messages. */ @Override public void onMessage(String message) { TeapotMessage msg; try { msg = mapper.readValue(message, TeapotMessage.class); String clientId = msg.getClientId(); String payload = msg.getPayload(); /* Cut payload on tokens */ Matcher matcher = TOKENS_PATTERN.matcher(payload); List<String> args = new ArrayList<>(); while (matcher.find()) { args.add(matcher.group()); } String cmd = args.remove(0); // first arg is a command switch (cmd) { case "ls": sendAnswer(session, clientId, simulator.ls()); break; case "cat": switch (args.size()) { case 1: sendAnswer(session, clientId, simulator.cat(args.get(0))); break; case 2: simulator.cat(args.get(0), args.get(1).split("\\n")); break; } break; case "touch": simulator.touch(args.get(0)); break; case "mv": simulator.mv(args.get(0), args.get(1)); break; case "rm": simulator.rm(args.get(0)); break; default: sendError(session, clientId, "Command " + cmd + " is not supported!"); break; } } catch (IOException e) { e.printStackTrace(); } } /** * Sends the answer to the client via websocket session and adds EOT * character at the end to indicate the end of transmission. * * @param session websocket session * @param clientId client id * @param answer text data to send */ private void sendAnswer(Session session, String clientId, String[] answer) { try { /* Send lines as separate messages */ int length = answer.length; for (int i = 0; i < length - 1; i++) { String line = answer[i]; TeapotMessage message = new TeapotMessage(clientId, line); session.getBasicRemote().sendText(mapper.writeValueAsString(message)); } /* Send last message */ TeapotMessage message = new TeapotMessage(clientId, answer[length - 1] + EOT); session.getBasicRemote().sendText(mapper.writeValueAsString(message)); } catch (IOException e) { e.printStackTrace(); } } /** * Sends the error message to the client via websocket session and adds * CAN character at the end to indicate the end of transmission with * error. * * @param session websocket session * @param clientId client id * @param msg error message */ private void sendError(Session session, String clientId, String msg) { TeapotMessage message = new TeapotMessage(clientId, msg + CAN); try { session.getBasicRemote().sendText(mapper.writeValueAsString(message)); } catch (IOException e) { e.printStackTrace(); } } } }