/*FreeMind - A Program for creating and viewing Mindmaps *Copyright (C) 2000-2009 Christian Foltin 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. * * Created on 03.02.2009 */ /*$Id: UpdateThread.java,v 1.1.2.1 2009/02/04 19:31:21 christianfoltin Exp $*/ package plugins.collaboration.database; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Vector; import javax.swing.SwingUtilities; import plugins.collaboration.database.DatabaseBasics.ResultHandler; import freemind.controller.actions.generated.instance.XmlAction; import freemind.extensions.PermanentNodeHook; import freemind.main.Tools; import freemind.modes.MapAdapter; import freemind.modes.NodeAdapter; import freemind.modes.mindmapmode.MindMapController; import freemind.modes.mindmapmode.MindMapMapModel; import freemind.modes.mindmapmode.MindMapNodeModel; import freemind.modes.mindmapmode.actions.xml.ActionFilter.FinalActionFilter; import freemind.modes.mindmapmode.actions.xml.ActionPair; public class UpdateThread extends Thread implements ResultHandler, FinalActionFilter { private static final String QUERY_GET_USERS = "SELECT * FROM " + DatabaseBasics.TABLE_USERS; private static final String QUERY = "SELECT * FROM " + DatabaseBasics.TABLE_XML_ACTIONS + " WHERE " + DatabaseBasics.ROW_PK + " >= ?"; private boolean mShouldTerminate = false; private boolean mIsTerminated = false; protected Connection mConnection = null; protected BigInteger mPrimaryKeyMutex = BigInteger.valueOf(0); protected long mPrimaryKey = 1l; protected MindMapController mController; protected boolean mFilterEnabled = true; private static java.util.logging.Logger logger = null; private PreparedStatement mPrepareStatement; private PreparedStatement mPrepareStatementUsers = null; protected String mPort; public String getPort() { return mPort; } public void setPort(String pPort) { mPort = pPort; } public String getHost() { return mHost; } public void setHost(String pHost) { mHost = pHost; } protected String mHost; public UpdateThread(Connection pConnection, MindMapController pController) throws SQLException { super(); if (logger == null) { logger = freemind.main.Resources.getInstance().getLogger( this.getClass().getName()); } mConnection = pConnection; mController = pController; } public void run() { try { mPrepareStatement = mConnection.prepareStatement(QUERY); } catch (SQLException e1) { freemind.main.Resources.getInstance().logException(e1); return; } int counter = 1; while (!mShouldTerminate) { try { logger.fine("Looking for updates..."); synchronized (mPrimaryKeyMutex) { mPrepareStatement.setLong(1, mPrimaryKey); logger.fine("Looking for updates... Query"); query(mPrepareStatement, this); } logger.fine("Looking for updates... Done."); Thread.sleep(1000); counter--; if (counter <= 0) { counter = 10; mController.getController().setTitle(); } } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } mIsTerminated = true; } public void commitSuicide() { mShouldTerminate = true; int timeout = 100; logger.info("Shutting down update thread."); while (!mIsTerminated && timeout-- > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { freemind.main.Resources.getInstance().logException(e); } } if (timeout == 0) { logger.warning("Can't stop update thread!"); } else { logger.info("Shutting down update thread complete."); } } public void shutdown(boolean pWithShutdown) { try { if (pWithShutdown) { Statement st = mConnection.createStatement(); st.execute("SHUTDOWN"); } mConnection.close(); mConnection = null; } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } public void processResults(ResultSet rs) { try { while (rs.next()) { long nextPk = rs.getLong(DatabaseBasics.ROW_PK); mPrimaryKey = nextPk + 1; String doAction = rs.getString(DatabaseBasics.ROW_ACTION); String undoAction = rs.getString(DatabaseBasics.ROW_UNDOACTION); String map = rs.getString(DatabaseBasics.ROW_MAP); logger.info("Got the following from database: " + nextPk + ", " + doAction + ", " + undoAction + ", " + map); if (doAction != null && undoAction != null) { XmlAction xmlDoAction = mController.unMarshall(doAction); XmlAction xmlUndoAction = mController .unMarshall(undoAction); ActionPair pair = new ActionPair(xmlDoAction, xmlUndoAction); executeTransaction(pair); } else if (map != null) { createNewMap(map); } else { logger.info("Shutting down was signalled."); rs.close(); // session has ended. DatabaseBasics.togglePermanentHook(mController); // and end. return; } } rs.close(); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } private void createNewMap(String map) throws IOException { { // deregister from old controller: deregisterFilter(); logger.info("Restoring the map..."); MindMapController newModeController = (MindMapController) mController .getMode().createModeController(); MapAdapter newModel = new MindMapMapModel(mController.getFrame(), newModeController); HashMap IDToTarget = new HashMap(); StringReader reader = new StringReader(map); MindMapNodeModel rootNode = (MindMapNodeModel) newModeController .createNodeTreeFromXml(reader, IDToTarget); reader.close(); newModel.setRoot(rootNode); rootNode.setMap(newModel); mController.newMap(newModel); newModeController.invokeHooksRecursively((NodeAdapter) rootNode, newModel); mController = newModeController; // add new hook DatabaseBasics.togglePermanentHook(mController); // tell him about this thread. Collection activatedHooks = mController.getRootNode() .getActivatedHooks(); for (Iterator it = activatedHooks.iterator(); it.hasNext();) { PermanentNodeHook hook = (PermanentNodeHook) it.next(); if (hook instanceof DatabaseConnectionHook) { DatabaseConnectionHook connHook = (DatabaseConnectionHook) hook; connHook.setUpdateThread(this); break; } } /* register as listener, as I am a slave. */ registerFilter(); } } private void executeTransaction(final ActionPair pair) throws InterruptedException, InvocationTargetException { SwingUtilities.invokeLater(new Runnable() { public void run() { mFilterEnabled = false; try { mController.doTransaction("update", pair); } finally { mFilterEnabled = true; } } }); } protected void insertIntoActionTable(String expression) throws SQLException { synchronized (mPrimaryKeyMutex) { update(expression); mPrimaryKey++; } } public synchronized void query(PreparedStatement preparedStatement, ResultHandler pHandler) throws SQLException { Statement st = null; ResultSet rs = null; boolean execute = preparedStatement.execute(); if (execute) { rs = preparedStatement.getResultSet(); pHandler.processResults(rs); } } public synchronized boolean update(String expression) throws SQLException { logger.info("Executing " + expression); Statement st = null; st = mConnection.createStatement(); // statements int i = st.executeUpdate(expression); // run the query if (i == -1) { logger.severe("db error : " + expression); } st.close(); return i != -1; } public ActionPair filterAction(ActionPair pPair) { if (pPair == null || !mFilterEnabled) return pPair; String doAction = mController.marshall(pPair.getDoAction()); String undoAction = mController.marshall(pPair.getUndoAction()); String expression = "INSERT INTO " + DatabaseBasics.TABLE_XML_ACTIONS + "(" + DatabaseBasics.ROW_PK + "," + DatabaseBasics.ROW_ACTION + "," + DatabaseBasics.ROW_UNDOACTION + ") VALUES(" + mPrimaryKey + ", '" + escapeQuotations(doAction) + "', '" + escapeQuotations(undoAction) + "')"; try { insertIntoActionTable(expression); } catch (SQLException e) { freemind.main.Resources.getInstance().logException(e); } return pPair; } protected String escapeQuotations(String pInput) { return pInput.replaceAll("'", "''"); } protected void deregisterFilter() { logger.info("Deregistering filter"); mController.getActionFactory().deregisterFilter(this); } protected void registerFilter() { logger.info("Registering filter"); mController.getActionFactory().registerFilter(this); } public void setupTables(String password) throws Exception { // create tables logger.info("Create tables..."); createTables(password); insertUser(); // register as listener: registerFilter(); // send first action: logger.info("Store map in database..."); StringWriter writer = new StringWriter(); mController.getMap().getXml(writer); String expression = "INSERT INTO " + DatabaseBasics.TABLE_XML_ACTIONS + "(" + DatabaseBasics.ROW_PK + "," + DatabaseBasics.ROW_MAP + ") VALUES(" + mPrimaryKey + ", '" + escapeQuotations(writer.toString()) + "')"; insertIntoActionTable(expression); } void insertUser() throws SQLException { update("INSERT INTO " + DatabaseBasics.TABLE_USERS + "(" + DatabaseBasics.ROW_USER + ") VALUES('" + escapeQuotations(getUserName()) + "')"); } public void removeUser() throws SQLException { update("DELETE FROM " + DatabaseBasics.TABLE_USERS + " WHERE " + DatabaseBasics.ROW_USER + " = '" + escapeQuotations(getUserName()) + "'"); } private String getUserName() { // Get host name String hostname = Tools.getHostName(); return System.getProperty("user.name") + "@" + hostname; } protected void createTables(String pPassword) throws SQLException { update("ALTER USER sa SET PASSWORD \"" + pPassword + "\""); dropTable(DatabaseBasics.TABLE_XML_ACTIONS); dropTable(DatabaseBasics.TABLE_USERS); update("CREATE TABLE " + DatabaseBasics.TABLE_XML_ACTIONS + " (" + DatabaseBasics.ROW_PK + " IDENTITY, " + DatabaseBasics.ROW_ACTION + " VARCHAR, " + DatabaseBasics.ROW_UNDOACTION + " VARCHAR, " + DatabaseBasics.ROW_MAP + " VARCHAR)"); update("CREATE TABLE " + DatabaseBasics.TABLE_USERS + " (" + DatabaseBasics.ROW_USER + " VARCHAR)"); } private boolean dropTable(String tableName) throws SQLException { return update("DROP TABLE " + tableName + " IF EXISTS"); } public void signalEndOfSession() { // signal end of session: String expression = "INSERT INTO " + DatabaseBasics.TABLE_XML_ACTIONS + "(" + DatabaseBasics.ROW_PK + ") VALUES(" + mPrimaryKey + ")"; try { insertIntoActionTable(expression); // and wait until the others should have shut down. Thread.sleep(2000); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } public Vector getUsers() throws SQLException { if (mPrepareStatementUsers == null) { mPrepareStatementUsers = mConnection .prepareStatement(QUERY_GET_USERS); } Vector result = new Vector(); boolean execute = mPrepareStatementUsers.execute(); if (!execute) { return result; } ResultSet rs = mPrepareStatementUsers.getResultSet(); try { while (rs.next()) { result.add(rs.getString(DatabaseBasics.ROW_USER)); } rs.close(); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } return result; } }