Java tutorial
/* * Copyright (C) 2016 Matteo Morena * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package mamo.vanillaVotifier; import mamo.vanillaVotifier.event.*; import mamo.vanillaVotifier.utils.RsaUtils; import mamo.vanillaVotifier.utils.SubstitutionUtils; import org.apache.commons.lang3.text.StrSubstitutor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.crypto.BadPaddingException; import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketOptions; import java.net.SocketTimeoutException; import java.security.interfaces.RSAPublicKey; import java.util.AbstractMap.SimpleEntry; import java.util.HashMap; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.PatternSyntaxException; public class VotifierServer { @NotNull protected VanillaVotifier votifier; @NotNull protected CopyOnWriteArrayList<Listener> listeners; protected boolean running; @Nullable protected ServerSocket serverSocket; public VotifierServer(@NotNull VanillaVotifier votifier) { this.votifier = votifier; listeners = new CopyOnWriteArrayList<Listener>(); listeners.add(new VotifierServerListener(votifier)); } public synchronized void start() throws IOException { if (isRunning()) { throw new IllegalStateException("Server is already running!"); } notifyListeners(new ServerStartingEvent()); serverSocket = new ServerSocket(); serverSocket.bind(votifier.getConfig().getInetSocketAddress()); running = true; notifyListeners(new ServerStartedEvent()); new Thread(new Runnable() { @Override public void run() { ExecutorService executorService = Executors.newSingleThreadExecutor(); while (isRunning()) { try { final Socket socket = serverSocket.accept(); executorService.execute(new Runnable() { @Override public void run() { try { notifyListeners(new ConnectionEstablishedEvent(socket)); socket.setSoTimeout(SocketOptions.SO_TIMEOUT); // SocketException: handled by try/catch. BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())); writer.write("VOTIFIER 2.9\n"); writer.flush(); BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); // IOException: handled by try/catch. byte[] request = new byte[((RSAPublicKey) votifier.getConfig().getKeyPair() .getPublic()).getModulus().bitLength() / Byte.SIZE]; in.read(request); // IOException: handled by try/catch. notifyListeners(new EncryptedInputReceivedEvent(socket, new String(request))); request = RsaUtils .getDecryptCipher(votifier.getConfig().getKeyPair().getPrivate()) .doFinal(request); // IllegalBlockSizeException: can't happen. String requestString = new String(request); notifyListeners(new DecryptedInputReceivedEvent(socket, requestString)); String[] requestArray = requestString.split("\n"); if ((requestArray.length == 5 || requestArray.length == 6) && requestArray[0].equals("VOTE")) { notifyListeners(new VoteEventVotifier(socket, new Vote(requestArray[1], requestArray[2], requestArray[3], requestArray[4]))); for (VoteAction voteAction : votifier.getConfig().getVoteActions()) { String[] params = new String[4]; try { for (int i = 0; i < params.length; i++) { params[i] = SubstitutionUtils.applyRegexReplacements( requestArray[i + 1], voteAction.getRegexReplacements()); } } catch (PatternSyntaxException e) { notifyListeners(new RegularExpressionPatternErrorException(e)); params = new String[] { requestArray[1], requestArray[2], requestArray[3], requestArray[4] }; } if (voteAction.getCommandSender() instanceof RconCommandSender) { RconCommandSender commandSender = (RconCommandSender) voteAction .getCommandSender(); StrSubstitutor substitutor = SubstitutionUtils.buildStrSubstitutor( new SimpleEntry<String, Object>("service-name", params[0]), new SimpleEntry<String, Object>("user-name", params[1]), new SimpleEntry<String, Object>("address", params[2]), new SimpleEntry<String, Object>("timestamp", params[3])); for (String command : voteAction.getCommands()) { String theCommand = substitutor.replace(command); notifyListeners(new SendingRconCommandEvent( commandSender.getRconConnection(), theCommand)); try { notifyListeners(new RconCommandResponseEvent( commandSender.getRconConnection(), commandSender .sendCommand(theCommand).getPayload())); } catch (Exception e) { notifyListeners(new RconExceptionEvent( commandSender.getRconConnection(), e)); } } } if (voteAction.getCommandSender() instanceof ShellCommandSender) { ShellCommandSender commandSender = (ShellCommandSender) voteAction .getCommandSender(); HashMap<String, String> environment = new HashMap<String, String>(); environment.put("voteServiceName", params[0]); environment.put("voteUserName", params[1]); environment.put("voteAddress", params[2]); environment.put("voteTimestamp", params[3]); for (String command : voteAction.getCommands()) { notifyListeners(new SendingShellCommandEvent(command)); try { commandSender.sendCommand(command, environment); notifyListeners(new ShellCommandSentEvent()); } catch (Exception e) { notifyListeners(new ShellCommandExceptionEvent(e)); } } } } } else { notifyListeners(new InvalidRequestEvent(socket, requestString)); } } catch (SocketTimeoutException e) { notifyListeners(new ReadTimedOutExceptionEvent(socket, e)); } catch (BadPaddingException e) { notifyListeners(new DecryptInputExceptionEvent(socket, e)); } catch (Exception e) { notifyListeners(new CommunicationExceptionEvent(socket, e)); } try { socket.close(); notifyListeners(new ConnectionClosedEvent(socket)); } catch (Exception e) { // IOException: catching just in case. Continue even if socket doesn't close. notifyListeners(new ConnectionCloseExceptionEvent(socket, e)); } } }); } catch (Exception e) { if (running) { // Show errors only while running, to hide error while stopping. notifyListeners(new ConnectionEstablishExceptionEvent(e)); } } } executorService.shutdown(); if (!executorService.isTerminated()) { notifyListeners(new ServerAwaitingTaskCompletionEvent()); try { executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); } catch (Exception e) { // InterruptedException: can't happen. } } notifyListeners(new ServerStoppedEvent()); } }).start(); } public synchronized void stop() throws IOException { if (!isRunning()) { throw new IllegalStateException("Server isn't running!"); } notifyListeners(new ServerStoppingEvent()); running = false; serverSocket.close(); } public synchronized boolean isRunning() { return running; } @NotNull public List<Listener> getListeners() { return listeners; } public void notifyListeners(@NotNull Event event) { for (Listener listener : listeners) { listener.onEvent(event); } } }