/*FreeMind - A Program for creating and viewing Mindmaps *Copyright (C) 2000-2012 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others. * *See COPYING for Details * *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 2 *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, write to the Free Software *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package plugins.collaboration.socket; import java.awt.EventQueue; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.SocketTimeoutException; import freemind.controller.actions.generated.instance.CollaborationActionBase; import freemind.controller.actions.generated.instance.CollaborationTransaction; import freemind.controller.actions.generated.instance.XmlAction; import freemind.main.Tools; import freemind.modes.mindmapmode.MindMapController; import freemind.modes.mindmapmode.actions.xml.ActionPair; /** * @author foltin * @date 06.09.2012 */ public abstract class CommunicationBase extends TerminateableThread { /** * */ private static final String STRING_CONTINUATION_SUFFIX = "<cont>"; protected Socket mSocket; /** * @param pName * @param pClient * @param pController * @param pOut * @param pIn */ public CommunicationBase(String pName, Socket pClient, MindMapController pController, DataOutputStream pOut, DataInputStream pIn) { super(pName); mSocket = pClient; mController = pController; out = pOut; in = pIn; } protected MindMapController mController; protected DataOutputStream out; protected DataInputStream in; protected static final int ROUNDTRIP_ROUNDS = 200; protected static final int ROUNDTRIP_TIMEOUT = 20000; protected static final int STATE_IDLE = 0; protected static final int STATE_WAIT_FOR_HELLO = 1; protected static final int STATE_WAIT_FOR_COMMAND = 2; protected static final int STATE_WAIT_FOR_WHO_ARE_YOU = 3; protected static final int STATE_WAIT_FOR_WELCOME = 4; protected static final int STATE_WAIT_FOR_LOCK = 5; protected static final int STATE_LOCK_RECEIVED = 6; private static final int MAX_STRING_LENGTH_TO_SEND = 65500; private int mCurrentState = STATE_IDLE; private String mCurrentStateMutex = "lockme"; private StringBuffer mCurrentCommand = new StringBuffer(); /** * @param pMessage * @return true, if successful. */ public synchronized boolean send(CollaborationActionBase pCommand) { try { printCommand("Send", pCommand); final String marshalledText = Tools.marshall(pCommand); logger.fine(getName() + " :Sending " + marshalledText); String text = Tools.compress(marshalledText); // split into pieces, as the writeUTF method is only able to send // 65535 bytes... int index = 0; while (index + MAX_STRING_LENGTH_TO_SEND < text.length()) { out.writeUTF(text.substring(index, index + MAX_STRING_LENGTH_TO_SEND) + STRING_CONTINUATION_SUFFIX); index += MAX_STRING_LENGTH_TO_SEND; } out.writeUTF(text.substring(index)); return true; } catch (IOException e) { freemind.main.Resources.getInstance().logException(e); } return false; } public boolean processAction() throws Exception { boolean didSomething = false; try { // Non blocking!! String text = in.readUTF(); if (text.endsWith(STRING_CONTINUATION_SUFFIX)) { mCurrentCommand.append(text.substring(0, text.length() - STRING_CONTINUATION_SUFFIX.length())); didSomething = true; } else { mCurrentCommand.append(text); final String textValue = mCurrentCommand.toString(); mCurrentCommand.setLength(0); final String decompressedText = Tools.decompress(textValue); logger.fine(getName() + " :Received " + decompressedText); final CollaborationActionBase command = (CollaborationActionBase) Tools .unMarshall(decompressedText); if (command != null) { printCommand("Receive", command); Runnable runnable = new Runnable() { public void run() { try { processCommand(command); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } }; if (command instanceof CollaborationTransaction) { // inserted in event queue here, to avoid // concurrency issues. EventQueue.invokeLater(runnable); } else { // other commands than transactions are processed directly. runnable.run(); } didSomething = true; } } } catch (SocketTimeoutException e) { } mCounter--; if (mCounter <= 0) { mCounter = 10; mController.getController().setTitle(); } return didSomething; } /** * @param pDirection * @param pCommand */ private void printCommand(String pDirection, CollaborationActionBase pCommand) { if (pCommand instanceof CollaborationTransaction) { CollaborationTransaction trans = (CollaborationTransaction) pCommand; XmlAction doAction = mController.unMarshall(trans.getDoAction()); String out = pDirection + ": " + Tools.printXmlAction(doAction) + " (Id: " + trans.getId() + ")"; logger.info(out); } else { String out = pDirection + ": " + Tools.printXmlAction(pCommand); logger.info(out); } } int mCounter = 1; public abstract void processCommand(CollaborationActionBase command) throws Exception; protected int getCurrentState() { synchronized (mCurrentStateMutex) { return mCurrentState; } } protected void setCurrentState(int pCurrentState) { synchronized (mCurrentStateMutex) { mCurrentState = pCurrentState; } } /** * @param pDoAction * @param pUndoAction * @param pLockId */ public void sendCommand(String pDoAction, String pUndoAction, String pLockId) { CollaborationTransaction trans = new CollaborationTransaction(); trans.setDoAction(pDoAction); trans.setUndoAction(pUndoAction); trans.setId(pLockId); send(trans); } public void close() throws IOException { mSocket.close(); } public ActionPair getActionPair(CollaborationTransaction trans) { return new ActionPair(mController.unMarshall(trans.getDoAction()), mController.unMarshall(trans.getUndoAction())); } public String getIpToSocket() { return mSocket.getLocalAddress().getHostAddress(); } protected void printWrongState(CollaborationActionBase pCommand) { logger.warning("Wrong state for " + pCommand.getClass() + ": " + printState(getCurrentState())); } /** * @param pCurrentState * @return */ protected String printState(int pCurrentState) { switch (pCurrentState) { case STATE_IDLE: return "STATE_IDLE"; case STATE_WAIT_FOR_HELLO: return "STATE_WAIT_FOR_HELLO"; case STATE_WAIT_FOR_COMMAND: return "STATE_WAIT_FOR_COMMAND"; case STATE_WAIT_FOR_WHO_ARE_YOU: return "STATE_WAIT_FOR_WHO_ARE_YOU"; case STATE_WAIT_FOR_WELCOME: return "STATE_WAIT_FOR_WELCOME"; case STATE_WAIT_FOR_LOCK: return "STATE_WAIT_FOR_LOCK"; case STATE_LOCK_RECEIVED: return "STATE_LOCK_RECEIVED"; } return "UNKNOWN: " + pCurrentState; } }