home *** CD-ROM | disk | FTP | other *** search
Wrap
Java Source | 1998-03-18 | 30.3 KB | 921 lines
/** * @(#)QuerySynchronizer.java * * Copyright (c) 1997 Symantec Corporation. All Rights Reserved. * */ /** * As the name implies, the major function of the QuerySynchronizer class * is synchronizing QueryNavigator objects. This consists of three main parts: * * <P>- managing separate trees of QueryNavigator objects that are synchronized as a group * - synchronizing group save operations * - synchronizing group navigation operations * * <P>Managing Trees * * <P>The QuerySynchronizer class holds the data that forms trees, performs direct manipulation of this data * such as adding and removing nodes, and provides methods for joining and leaving trees. * The QueryNavigator class relies on the QuerySynchronizer class for all tree function, * such as putting nodes in the correct trees and traversing trees in the correct node order. * * <P>Synchronizing Save Operations * * <P>QueryNavigator objects pass control of the flow of execution to their QuerySynchronizer objects * at the beginning of any publicly exposed save operation. From that point forward, the * QuerySynchronizer objects guarantee that internal save operations performed by individual * QueryNavigator objects occur in the correct order. Feedback from individual save operations * is funneled back through QuerySynchronizer objects. * * <P>Synchronizing Navigation and Related Operations * * <P>QueryNavigator objects pass control of the flow of execution to their QuerySynchronizer objects at * the beginning of any publicly exposed navigation operation. From that point forward, the * QuerySynchronizer objects guarantee that internal navigation operations and related operations, * resolving dirty data and executing new queries, occur in the correct sequence. Feedback from operations * performed by individual QueryNavigator objects is funneled back through QuerySynchronizer objects. * */ package symantec.itools.db.beans.binding; import javax.awt.swing.tree.*; import java.util.*; import java.sql.SQLException; public class QuerySynchronizer { // CONSTRUCTOR SECTION /** * Default constructor which takes no arguments and is private. * Constructor is private because this class manages its own object instances. * No other class has or needs access to create or destroy objects. * Adds itself to the internal set of object instances. * * @see addSynchronizer */ private QuerySynchronizer() { addSynchronizer(this); } // STATIC SECTION // DATA // VARIABLES /** * Collection of all synchronizer object instances. * There is one object per tree of related synchronizable targets. */ private static QuerySynchronizer[] m_AllSynchronizers = new QuerySynchronizer[255]; /** * Collection of all links; used to increase search performance. */ private static Hashtable m_AllLinks = new Hashtable(); /** * Used to determine course of action when there are changes. * It asks the question should changes be saved. * The possible answers are YES, NO, or CANCEL. */ private static SaveChangesWindow m_SaveChangesWindow = new SaveChangesWindow(); // CONSTANTS // Tree processing actions /** * Save deleted records in children */ private final static int SAVE_DELETED_CHILDREN = 1; /** * Save current target */ private final static int SAVE_CURRENT = 2; /** * Save deleted in current target */ private final static int SAVE_DELETED = 3; /** * Save new or modified children */ private final static int SAVE_NEW_OR_MODIFIED_CHILDREN = 4; /** * Commit saves */ private final static int COMMIT_STATE = 5; /** * Restart the queries for all children */ private final static int QUERY_CHILDREN = 6; /** * Cleare out all children */ private final static int CLEAR_CHILDREN = 7; /** * Save new or modified in current target */ private final static int SAVE_NEW_OR_MODIFIED = 8; /** * Save new or modified in current target */ private final static int CLOSE = 9; // PACKAGE ACCESS METHODS /** * Self test; which, traverses trees and prints out each node. */ static void testTrees() { System.out.println("Testing trees!"); for (int index = 0; index < m_AllSynchronizers.length; index++) { if (m_AllSynchronizers[index] != null) { System.out.println(m_AllSynchronizers[index].toString()); } } } /** * QueryNavigator objects register with the QuerySynchronizer class by invoking this method. Since the QueryNavigator objects do not have direct access to QuerySynchronizer objects, this method is static. The access privilege of this method is at least package. * - This method places QueryNavigators in the appropriate tree * - This method will add distinct JdbcConnection objects * - This method will invoke a method on the QueryNavigator object setting its tree node. * - QueryNavigator objects pass control of the flow of execution by invoking methods on nodes. * - Nodes know what QuerySynchronizer objects own their tree. * * <P>Tree Placement Algorithm * - Search globally for master and detail * If detail is found * replace * If master found, merge * Else * If master not found, create new tree * If master alias is blank, fill new tree with detail * Else * create detail * If master not found * fill new tree with master alias * add detail to new tree * Else * merge */ synchronized static void addSynchronizable(Synchronizable synchronizable) throws IllegalRelationshipException { String masterAlias; String detailAlias; SynchronizerLink rootLink; SynchronizerLink masterLink; SynchronizerLink detailLink; SynchronizerNode rootNode; SynchronizerNode masterNode; SynchronizerNode detailNode; SynchronizerTree tree; QuerySynchronizer synchronizer; detailAlias = synchronizable.getAliasName(); masterAlias = synchronizable.getMasterAliasName(); if (masterAlias == null) { masterAlias = ""; // Convention that client may not know about } if (detailAlias == masterAlias) { throw new IllegalRelationshipException("Attempt to add a self-referencing master-detail relationship."); } detailLink = findLink(detailAlias); // Global search... if (masterAlias != "") { masterLink = findLink(masterAlias); } else { masterLink = null; } if (detailLink != null) { if (!detailLink.isOpen() && detailLink.getMasterAlias() != masterAlias) { throw new IllegalRelationshipException("Attempt to alter an existing master-detail relationship."); } if (masterLink != null && detailLink.getParentLink() != masterLink) { merge(masterLink, detailLink); } } else { if (masterLink == null) { synchronizer = new QuerySynchronizer(); // Create new objects rootNode = new SynchronizerNode(); tree = new SynchronizerTree(rootNode); rootLink = new QueryNavigatorLink(); synchronizer.m_Tree = tree; // Set synchronizer references synchronizer.m_RootLink = rootLink; rootNode.setUserObject(rootLink); // Set root node references rootLink.setNode(rootNode); // Set root link references rootLink.setSynchronizer(synchronizer); } else { synchronizer = null; } if (masterAlias == "") { detailLink = synchronizer.m_RootLink; } else { detailNode = new SynchronizerNode(); // Create new objects detailLink = new QueryNavigatorLink(); detailNode.setUserObject(detailLink); // Set detail node references detailLink.setNode(detailNode); // set detail link references if (masterLink == null) { masterLink = synchronizer.m_RootLink; masterLink.setAlias(masterAlias); // mini merge... masterNode = (SynchronizerNode)masterLink.getNode(); detailNode = (SynchronizerNode)detailLink.getNode(); masterNode.add(detailNode); detailLink.setSynchronizer(synchronizer); // end of mine merge } else { merge(masterLink, detailLink); } } } detailLink.setSynchronizable(synchronizable); synchronizable.setLink(detailLink); } // PRIVATE METHODS /** * Search all links for the one with this alias. * Not finding a match is not an error. * This method exists so that the way in which links are stored does * not affect the rest of the methods in this class. */ static private SynchronizerLink findLink(Object key) { SynchronizerLink link; if (m_AllLinks.containsKey(key)) { link = (SynchronizerLink)m_AllLinks.get(key); } else { link = null; } return link; } /** * Encapsulate storage data structure of m_AllSynchronizers * Maybe use a vector; maybe use an array */ static private synchronized void addSynchronizer(QuerySynchronizer sync) { for (int index = 0; index < m_AllSynchronizers.length; index++) { if (m_AllSynchronizers[index] == null) { m_AllSynchronizers[index] = sync; return; } } QuerySynchronizer[] tempArray = new QuerySynchronizer[m_AllSynchronizers.length + 255]; System.arraycopy(m_AllSynchronizers, 0, tempArray, 255, m_AllSynchronizers.length); m_AllSynchronizers = tempArray; } /** * Encapsulate storage data structure of m_AllSynchronizers * Maybe use a vector; maybe use an array */ static private synchronized void removeSynchronizer(QuerySynchronizer sync) { for (int index = 0; index < m_AllSynchronizers.length; index++) { if (m_AllSynchronizers[index] == sync) { m_AllSynchronizers[index].removeAllLinks(); m_AllSynchronizers[index] = null; return; } } } /** * Links add themselves when their alias is set because * the alias is the key. */ static synchronized void addLink(SynchronizerLink link) { Object key = link.getAlias(); if (!m_AllLinks.contains(key)) { m_AllLinks.put(key, link); } } /** * Links add themselves when their alias is set because * the alias is the key. */ static synchronized void removeLink(SynchronizerLink link) { SynchronizerNode node = (SynchronizerNode)link.getNode(); SynchronizerNode parentNode = (SynchronizerNode)node.getParent(); QuerySynchronizer synchronizer = link.getSynchronizer(); m_AllLinks.remove(link.getAlias()); if (parentNode != null) { try { synchronizer.orderAndProcessNodes(node, CLOSE); } catch (SQLException ex) { // foo } parentNode.remove(node); } } /** * Add detail as child of master * Point all of the children to new QuerySynchronizer * Recover the detail QSync */ static private synchronized void merge(SynchronizerLink masterLink, SynchronizerLink detailLink) { SynchronizerNode node; SynchronizerLink link; SynchronizerNode masterNode; SynchronizerNode detailNode; QuerySynchronizer detailSync; detailSync = detailLink.getSynchronizer(); if (detailSync != null) { removeSynchronizer(detailSync); } masterNode = (SynchronizerNode)masterLink.getNode(); detailNode = (SynchronizerNode)detailLink.getNode(); masterNode.add(detailNode); detailLink.setSynchronizer(masterLink.getSynchronizer()); Enumeration enum = detailNode.children(); while(enum.hasMoreElements()) { node = (SynchronizerNode)enum.nextElement(); link = (SynchronizerLink)node.getUserObject(); link.setSynchronizer(masterLink.getSynchronizer()); } } // NON-STATIC SECTION // DATA // Each QuerySynchronizer object contains one and only one // QueryNavigatorTree object. This tree class implements the JFC // swing tree interface. The parent child relationships between tree // nodes correspond to the master detail relationships between // QueryNavigators objects. Each node contains a reference to a // QueryNavigatorLink object. The purpose of this link is to hold // a spot for a QueryNavigator when the alias is known from a detail // QueryNavigator object before the master QueryNavigator object is // registered. /** // The tree */ private SynchronizerTree m_Tree = null; /** // The root link */ private SynchronizerLink m_RootLink = null; /** // The connections */ private Hashtable m_AllConnections = null; // METHODS /** * Invoked by the synchronizable object to perform saveAll */ boolean saveAll(SynchronizerLink startLink) throws RelationshipPendingException, ParentInvalidRecordException { boolean success = saveTree((SynchronizerNode)startLink.getNode(), true); if (success) { navigate(startLink, Synchronizable.RESTART, null); } /* if (success) { try { orderAndProcessNodes(startNode, QUERY_CHILDREN); } catch (SQLException ex) { // swallow it } } */ return success; } /** * Invoked by the synchronizable object to perform saveAllLevels */ boolean saveAllLevels(SynchronizerLink startLink) throws RelationshipPendingException, ParentInvalidRecordException { return saveAll((SynchronizerLink)((SynchronizerNode)m_Tree.getRoot()).getUserObject()); } /** * Invoked by the synchronizable object to perform save */ boolean save(SynchronizerLink startLink) throws RelationshipPendingException, ParentInvalidRecordException { boolean success = saveTree((SynchronizerNode)startLink.getNode(), false); if (success) { try { orderAndProcessNodes((SynchronizerNode)startLink.getNode(), QUERY_CHILDREN); } catch (SQLException ex) { // swallow it } } return success; } /** * * Execute all neccessary operations in correct order within transactions. * Heterogeneous master detail is provided without two-phase commit logic. * * Order of events: * - Begin transactions * - Process deleted children * - Process current target * - Process new or modified children * - Commit state * - End transactions * */ private boolean saveTree(SynchronizerNode startNode, boolean saveAll) throws RelationshipPendingException, ParentInvalidRecordException { boolean success; if (isOpen()) { throw new RelationshipPendingException("Attempt to saveAll with a relationship pending."); } if (isParentOnNewRecord((SynchronizerLink)startNode.getUserObject())) { throw new ParentInvalidRecordException("Parent record has not been committed. Data changes are not permitted at this time."); } beginAllTransactions(); try { orderAndProcessNodes(startNode, SAVE_DELETED_CHILDREN); if (saveAll) { orderAndProcessNodes(startNode, SAVE_DELETED); orderAndProcessNodes(startNode, SAVE_NEW_OR_MODIFIED); } else { orderAndProcessNodes(startNode, SAVE_CURRENT); } orderAndProcessNodes(startNode, SAVE_NEW_OR_MODIFIED_CHILDREN); success = true; orderAndProcessNodes(startNode, COMMIT_STATE); } catch (SQLException ex) { System.out.println("Transaction failed due to SQL failure, will be rolled back."); success = false; } catch (Exception ex) { success = true; } endAllTransactions(success); return success; } /** * QueryNavigator objects invoke this method to allow QuerySynchronizer * objects to orchestrate, control and synchronize all related tasks. * The QuerySynchronizer object first determines if any QueryNavigator * has dirty data. There are three ways to resolve such an occurrence. * * - the data can be saved * - the data can be thrown away * - the move can be cancelled * * Once the way is determined, the QuerySynchronizer object executes it. * When the occurrence is resolved, the QuerySynchronizer object * continues with the navigation. * * The QuerySynchronizer object invokes the navigation method of the * calling QueryNavigator object. If the navigation is successful, * the QuerySynchronizer object invokes the executeQuery methods * of other QueryNavigator objects in the tree. * * - Row is passed * * @see save */ synchronized void navigate(SynchronizerLink startLink, int type, Integer position) throws RelationshipPendingException, ParentInvalidRecordException { int resolution; boolean success; Enumeration orderedEnum; SynchronizerLink link; SynchronizerNode node; SynchronizerNode startNode; Synchronizable target; SynchronizerLink parentLink; SynchronizerNode parentNode; Synchronizable parentTarget; if (isOpen()) { throw new RelationshipPendingException("Attempt to navigate with a relationship pending."); } if (isParentOnNewRecord(startLink)) { throw new ParentInvalidRecordException("Parent record has not been committed. Data changes are not permitted at this time."); } startNode = (SynchronizerNode)startLink.getNode(); // Resolve dirty data // by means of save, cancel, or continue if (isDirty(startLink) && m_SaveChangesWindow != null) { resolution = m_SaveChangesWindow.run(); switch (resolution) { case SaveChangesDialog.YES: success = save(startLink); if (success != true) { return; } break; case SaveChangesDialog.NO: default: break; case SaveChangesDialog.CANCEL: return; } } else { resolution = SaveChangesDialog.NO; } try { // take care of starting target link = (SynchronizerLink)startNode.getUserObject(); target = link.getSynchronizable(); if (type == Synchronizable.RESTART) { // determine starting position parentNode = (SynchronizerNode)startNode.getParent(); if (parentNode != null) { parentLink = (SynchronizerLink)parentNode.getUserObject(); parentTarget = parentLink.getSynchronizable(); } else { parentTarget = null; } target.executeQuery(parentTarget); } else { target.navigate(type, position); } // synchronize children orderAndProcessNodes(startNode, QUERY_CHILDREN); } catch (SQLException ex) { } } /** * Debug output */ public String toString() { SynchronizerNode node = (SynchronizerNode)m_Tree.getRoot(); Enumeration enum = node.enumerateNodes(SynchronizerNode.TOP_DOWN_INCLUSIVE); String output = super.toString(); output += "\nQuerySynchronizer nodes: "; while (enum.hasMoreElements()) { output += "\n\t" + enum.nextElement().toString(); } return output; } /** * Is the root link an open link */ private boolean isOpen() { SynchronizerNode node = (SynchronizerNode)m_Tree.getRoot(); SynchronizerLink link = (SynchronizerLink)node.getUserObject(); return link.isOpen(); } /** * Does this link or any of its children have dirty data? */ private boolean isDirty(SynchronizerLink startLink) { Enumeration orderedEnum; SynchronizerNode node; Synchronizable target; SynchronizerLink link; node = (SynchronizerNode)startLink.getNode(); orderedEnum = node.enumerateNodes(SynchronizerNode.TOP_DOWN_EXCLUSIVE); while (orderedEnum.hasMoreElements()) { node = (SynchronizerNode)orderedEnum.nextElement(); link = (SynchronizerLink)node.getUserObject(); target = link.getSynchronizable(); if (target.isDirty()) { return true; } } return false; } private void beginAllTransactions() { Enumeration allConnections = enumerateConnections(); Connection connection; while (allConnections.hasMoreElements()) { connection = (Connection)allConnections.nextElement(); connection.beginTransaction(); } } /** * End transaction. */ private void endAllTransactions(boolean commit) { Enumeration allConnections = enumerateConnections(); Connection connection; while (allConnections.hasMoreElements()) { connection = (Connection)allConnections.nextElement(); connection.endTransaction(commit); } } /** * Encapsulate storage data structure of m_AllConnections * User a hashtable */ private synchronized void addConnection(Connection key) { Integer useCount; if (m_AllConnections.containsKey(key)) { useCount = (Integer)m_AllConnections.get(key); m_AllConnections.remove(key); useCount = new Integer(useCount.intValue() + 1); } else { useCount = new Integer(1); } m_AllConnections.put(key, useCount); } /** * Encapsulate storage data structure of m_AllConnections * Use a hashtable */ private synchronized void removeConnection(Connection key) { Integer useCount; useCount = (Integer)m_AllConnections.get(key); m_AllConnections.remove(key); useCount = new Integer(useCount.intValue() - 1); // If connection is still referenced, put it back if (useCount.intValue() > 0) { m_AllConnections.put(key, useCount); } } /** * Encapsulate storage data structure of m_AllConnections * Use a hashtable */ private Enumeration enumerateConnections() { Connection conn; Enumeration orderedEnum; Synchronizable target; SynchronizerLink link; SynchronizerNode node; if (m_AllConnections == null) { m_AllConnections = new Hashtable(); node = (SynchronizerNode)m_Tree.getRoot(); orderedEnum = node.enumerateNodes(SynchronizerNode.TOP_DOWN_INCLUSIVE); while (orderedEnum.hasMoreElements()) { node = (SynchronizerNode)orderedEnum.nextElement(); link = (SynchronizerLink)node.getUserObject(); target = link.getSynchronizable(); conn = target.getConnection(); addConnection(conn); } } return m_AllConnections.keys(); } /** * Remove all links. */ void removeAllLinks() { Enumeration orderedEnum; SynchronizerNode node; SynchronizerLink link; node = (SynchronizerNode)m_Tree.getRoot(); orderedEnum = node.enumerateNodes(SynchronizerNode.TOP_DOWN_INCLUSIVE); while (orderedEnum.hasMoreElements()) { node = (SynchronizerNode)orderedEnum.nextElement(); link = (SynchronizerLink)node.getUserObject(); removeLink(link); } } /** * Check that parent is not in an invalid state. * If node has a parent, get it and make sure its state isn't new */ boolean isParentOnNewRecord(SynchronizerLink startLink) { SynchronizerNode node; SynchronizerNode parentNode; SynchronizerLink parentLink; Synchronizable parentTarget; boolean parentIsOnNewRecord; PersistentObject object; node = (SynchronizerNode)startLink.getNode(); parentNode = (SynchronizerNode)node.getParent(); if (parentNode != null) { parentLink = (SynchronizerLink)parentNode.getUserObject(); parentTarget = parentLink.getSynchronizable(); object = (PersistentObject)parentTarget.getCurrentObject(); if (object == null || object.getMarkedAsNew()) { parentIsOnNewRecord = true; } else { parentIsOnNewRecord = false; } } else { parentIsOnNewRecord = false; } return parentIsOnNewRecord; } /** * Order nodes based on the action to be performed and process them in order. */ private void orderAndProcessNodes(SynchronizerNode startNode, int action) throws SQLException { int order; int process; SynchronizerLink link; SynchronizerNode node; Synchronizable target; Enumeration path; SynchronizerLink parentLink; SynchronizerNode parentNode; Synchronizable parentTarget; PersistentObject object; switch (action) { case SAVE_DELETED_CHILDREN: order = SynchronizerNode.BOTTOM_UP; process = Synchronizable.SAVE_DELETED_DIRTY_DATA; break; case SAVE_CURRENT: order = SynchronizerNode.CURRENT_NODE_ONLY; process = Synchronizable.SAVE_CURRENT_RECORD; break; case SAVE_DELETED: order = SynchronizerNode.CURRENT_NODE_ONLY; process = Synchronizable.SAVE_DELETED_DIRTY_DATA; break; case SAVE_NEW_OR_MODIFIED: order = SynchronizerNode.CURRENT_NODE_ONLY; process = Synchronizable.SAVE_ALL_DIRTY_DATA; break; case SAVE_NEW_OR_MODIFIED_CHILDREN: order = SynchronizerNode.TOP_DOWN_EXCLUSIVE; process = Synchronizable.SAVE_ALL_DIRTY_DATA; break; case COMMIT_STATE: order = SynchronizerNode.TOP_DOWN_INCLUSIVE; process = COMMIT_STATE; break; case QUERY_CHILDREN: default: order = SynchronizerNode.TOP_DOWN_EXCLUSIVE; link = (SynchronizerLink)startNode.getUserObject(); target = link.getSynchronizable(); object = (PersistentObject)target.getCurrentObject(); if (object == null || object.getMarkedAsNew()) { action = CLEAR_CHILDREN; process = Synchronizable.CLEAR_ALL_DATA; } else { process = QUERY_CHILDREN; } break; case CLOSE: order = SynchronizerNode.TOP_DOWN_EXCLUSIVE; process = CLOSE; // process is ignored here but it will be a compile error not to set it. break; } path = startNode.enumerateNodes(order); while (path.hasMoreElements()) { node = (SynchronizerNode)path.nextElement(); link = (SynchronizerLink)node.getUserObject(); target = link.getSynchronizable(); if (action == COMMIT_STATE) { target.commitState(); } else if (action == CLOSE) { link.close(); } else if (action == QUERY_CHILDREN) { parentNode = (SynchronizerNode)node.getParent(); if (parentNode != null) { parentLink = (SynchronizerLink)parentNode.getUserObject(); parentTarget = parentLink.getSynchronizable(); target.executeQuery(parentTarget); } } else { target.saveAll(process); } } } }