/* Loader.java A convenient initialization thread, does start up stuff for the client. Created: 1 October 1997 Module By: Michael Mulvaney ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2013 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 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, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.client; import java.rmi.RemoteException; import java.util.Hashtable; import java.util.Vector; import arlut.csd.Util.VecQuickSort; import arlut.csd.ganymede.common.BaseDump; import arlut.csd.ganymede.common.FieldTemplate; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.rmi.Base; import arlut.csd.ganymede.rmi.Session; /*------------------------------------------------------------------------------ class Loader ------------------------------------------------------------------------------*/ /** * <p>Client-side cache for loading object and field type definitions * from the server in the background during the client's start-up, and * providing that information to the client during its operations.</p> * * @author Mike Mulvaney */ public class Loader extends Thread { private static final boolean debug = false; private Hashtable<Short, Base> baseMap; private Hashtable<Base, String> baseNames; private Hashtable<Base, Short> baseToShort; private Hashtable<String, Short> nameShorts; private Vector<Base> baseList; private volatile boolean isShutdown = false, baseNamesLoaded = false, baseListLoaded = false, baseMapLoaded = false, templateLoading = false; private Session session; /** * Hash mapping Short object type id's to Vectors of * {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate}'s, * used by the client to quickly look up information about fields * in order to populate * {@link arlut.csd.ganymede.client.containerPanel containerPanel}'s. * * This hash is used by * {@link arlut.csd.ganymede.client.gclient#getTemplateVector(java.lang.Short) getTemplateVector}. */ private Hashtable<Short, Vector<FieldTemplate>> templateHash; /** * Hash mapping Short object type id's to a Hash that * maps field name to {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate} * object */ private Hashtable<Short, Hashtable<String, FieldTemplate>> templateNameHash; /* -- */ public Loader(Session session, boolean debug) { if (debug) { System.err.println("Initializing Loader"); } /* this.debug = debug; */ this.session = session; if (session == null) { throw new NullPointerException("Null session parameter in Loader constructor"); } } public void run() { if (debug) { System.err.println("Starting thread in loader"); } try { if (isShutdown) { if (debug) { System.err.println("**Stopping before baseList is loaded"); } // Ok, it's not really loaded, but this basically means that it is finished. baseListLoaded = true; } loadBaseList(); if (isShutdown) { if (debug) { System.err.println("**Stopping before baseNames are loaded"); } baseNamesLoaded = true; } loadBaseNames(); if (isShutdown) { if (debug) { System.err.println("**Stopping before baseMap is loaded"); } baseMapLoaded = true; } loadBaseMap(); } catch (RemoteException rx) { throw new RuntimeException("Could not load base hash/map in Loader: " + rx); } if (debug) { System.err.println("Done with thread in loader."); } } /** * Clear out all the information in the loader, and spawn * a new loader thread to download new information from * the server. */ public void clear() { cleanUp(); if (debug) { System.err.println("Starting to load the loader again"); } isShutdown = false; // start up a new thread Thread t = new Thread(this); t.setPriority(Thread.NORM_PRIORITY); t.start(); } /** * Clear out all information in the loader */ public void cleanUp() { if (debug) { System.err.println("** Loader cleanUp()"); } isShutdown = true; synchronized (this) { // setting isShutdown to true should cause the loader run // method to quickly drop out and set the loader flags to // true, so we wait until all of the boolean loaded flags are // set while (!(baseNamesLoaded && baseListLoaded && baseMapLoaded && !templateLoading)) { if (debug) { System.err.println("Loader waiting for previous method to stop."); } try { this.wait(); } catch (InterruptedException x) { throw new RuntimeException("Interrupted while waiting for previous loader to finish. " + x); } } } baseNamesLoaded = false; baseListLoaded = false; baseMapLoaded = false; if (baseList != null) { baseList.setSize(0); baseList = null; } // many of these hashes have values that are themselves hashes, // but we won't worry about things enough to iterate over the // interior hashes and clear them out. GC will do that for us // anyway, and it would take us some effort to do it affirmatively if (baseNames != null) { baseNames.clear(); baseNames = null; } if (baseMap != null) { baseMap.clear(); baseMap = null; } if (templateHash != null) { templateHash.clear(); templateHash = null; } if (templateNameHash != null) { templateNameHash.clear(); templateNameHash = null; } } /** * <p>Returns the type name for a given object.</p> * * <p>If the loader thread hasn't yet downloaded that information, * this method will block until the information is available.</p> */ public String getObjectType(Invid objId) { return this.getObjectType(objId.getType()); } /** * <p>Returns the type name for a given object type number.</p> * * <p>If the loader thread hasn't yet downloaded that information, * this method will block until the information is available.</p> */ public String getObjectType(short typeId) { try { Hashtable baseMap = getBaseMap(); // block BaseDump base = (BaseDump) baseMap.get(Short.valueOf(typeId)); return base.getName(); } catch (NullPointerException ex) { return "<unknown>"; } } /** * <p>Returns a Vector of {@link arlut.csd.ganymede.common.BaseDump * BaseDump} objects, providing a local cache of {@link * arlut.csd.ganymede.rmi.Base Base} references that the client * consults during operations.</p> * * <p>If this thread hasn't yet downloaded that information, this method will * block until the information is available.</p> */ public Vector<Base> getBaseList() { if (!baseListLoaded && !isShutdown) { synchronized (this) { while (!baseListLoaded && !isShutdown) { if (debug) { System.err.println("Dang, have to wait to get the base list"); } try { this.wait(); } catch (InterruptedException x) { throw new RuntimeException("Interrupted while waiting for base list to load: " + x); } } } } if (debug) { if (baseList == null) { System.err.println("baseList is null"); } else { System.err.println("returning baseList"); } } return baseList; } /** * <p>Returns a hash mapping {@link * arlut.csd.ganymede.common.BaseDump BaseDump} references to their * title.</p> * * <p>If this thread hasn't yet downloaded that information, this * method will block until the information is available.</p> */ public Hashtable<Base, String> getBaseNames() { if (!baseNamesLoaded && !isShutdown) { synchronized (this) { // we have to check baseNamesLoaded inside the // synchronization loop or else we can get deadlocked while (!baseNamesLoaded && !isShutdown) { if (debug) { System.err.println("Dang, have to wait to get the base names list"); } try { this.wait(); } catch (InterruptedException x) { throw new RuntimeException("Interrupted while waiting for base names to load: " + x); } } } } if (debug) { if (baseNames == null) { System.err.println("baseNames is null"); } else { System.err.println("returning baseNames"); } } return baseNames; } /** * <p>Returns a hash mapping Short {@link * arlut.csd.ganymede.rmi.Base Base} id's to {@link * arlut.csd.ganymede.common.BaseDump BaseDump} objects.</p> * * <p>If this thread hasn't yet downloaded that information, this * method will block until the information is available.</p> */ public Hashtable<Short, Base> getBaseMap() { if (!baseMapLoaded && !isShutdown) { synchronized (this) { // we have to check baseMapLoaded inside the // synchronization loop or else we can get deadlocked while (!baseMapLoaded && !isShutdown) { if (debug) { System.err.println("Loader: waiting for base map"); } try { this.wait(); } catch (InterruptedException x) { throw new RuntimeException("Interrupted while waiting for base hash to load: " + x); } } } } if (debug) { if (baseMap == null) { System.err.println("baseMap is null"); } else { System.err.println("returning baseMap"); } } return baseMap; } /** * <p>Returns a hashtable mapping {@link * arlut.csd.ganymede.common.BaseDump BaseDump} references to their * object type id in Short form. This is a holdover from a time * when the client didn't create local copies of the server's Base * references.</p> * * <p>If this thread hasn't yet downloaded that information, this * method will block until the information is available.</p> */ public Hashtable<Base, Short> getBaseToShort() { // baseToShort is loaded in the loadBaseMap function, so we can just // check to see if the baseMapLoaded is true. if (!baseMapLoaded && !isShutdown) { synchronized (this) { // we have to check baseMapLoaded inside the // synchronization loop or else we can get deadlocked while (!baseMapLoaded && !isShutdown) { if (debug) { System.err.println("Loader: waiting for base hash"); } try { this.wait(); } catch (InterruptedException x) { throw new RuntimeException("Interrupted while waiting for base hash to load: " + x); } } } } if (debug) { if (baseToShort == null) { System.err.println("baseToShort is null"); } else { System.err.println("returning baseToShort"); } } return baseToShort; } /** * <p>Returns a hashtable mapping base names to their object type id * in Short form. This is used by the XML client to quickly map * object type names to the numeric type id.</p> * * <p>If this thread hasn't yet downloaded that information, this * method will block until the information is available.</p> */ public Hashtable<String, Short> getNameToShort() { // baseToShort is loaded in the loadBaseMap function, so we can just // check to see if the baseMapLoaded is true. if (!baseMapLoaded && !isShutdown) { synchronized (this) { // we have to check baseMapLoaded inside the // synchronization loop or else we can get deadlocked while (!baseMapLoaded && !isShutdown) { if (debug) { System.err.println("Loader: waiting for base hash"); } try { this.wait(); } catch (InterruptedException x) { throw new RuntimeException("Interrupted while waiting for base hash to load: " + x); } } } } if (false) { if (nameShorts == null) { System.err.println("nameShorts is null"); } else { System.err.println("returning nameShorts"); } } return nameShorts; } /** * Returns a {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate} * for a field specified by object type id and field name. */ public FieldTemplate getFieldTemplate(short objectid, String fieldname) { return getFieldTemplate(Short.valueOf(objectid), fieldname); } /** * Returns a {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate} * for a field specified by object type id and field name. */ public FieldTemplate getFieldTemplate(Short objectid, String fieldname) { if (templateNameHash == null) { templateNameHash = new Hashtable<Short, Hashtable<String, FieldTemplate>>(); } Hashtable<String, FieldTemplate> nameHash = templateNameHash.get(objectid); if (nameHash == null) { getTemplateVector(objectid); nameHash = templateNameHash.get(objectid); if (nameHash == null) { return null; } } return nameHash.get(fieldname); } /** * Returns a vector of * {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate}'s. * * @param id Object type id to retrieve field information for. */ public Vector<FieldTemplate> getTemplateVector(short id) { return getTemplateVector(Short.valueOf(id)); } /** * Returns a vector of * {@link arlut.csd.ganymede.common.FieldTemplate FieldTemplate}'s * listing fields and field informaton for the object type identified by * id. * * @param id The id number of the object type to be returned the base id. */ public synchronized Vector<FieldTemplate> getTemplateVector(Short id) { Vector<FieldTemplate> result = null; /* -- */ if (isShutdown) { if (debug) { System.err.println("Loader.getTemplateVector() -- isShutdown is true"); } return null; } try { templateLoading = true; if (templateHash == null) { templateHash = new Hashtable<Short, Vector<FieldTemplate>>(); } if (templateHash.containsKey(id)) { result = templateHash.get(id); } else { try { result = session.getFieldTemplateVector(id.shortValue()); templateHash.put(id, result); constructTemplateNameHash(id, result); } catch (RemoteException rx) { throw new RuntimeException("Could not get field templates: " + rx); } } return result; } finally { templateLoading = false; } } /* -- Private methods -- */ private void constructTemplateNameHash(Short objectId, Vector<FieldTemplate> fieldTemplates) { Hashtable nameHash = new Hashtable<String, FieldTemplate>(); for (FieldTemplate x: fieldTemplates) { nameHash.put(x.getName(), x); } if (templateNameHash == null) { templateNameHash = new Hashtable<Short, Hashtable<String, FieldTemplate>>(); } templateNameHash.put(objectId, nameHash); } /** * loadBaseList loads a sorted Vector of types from the server. */ private synchronized void loadBaseList() throws RemoteException { baseList = session.getBaseList().getBaseList(); if (debug) { System.err.println("Finished loading base list"); if (baseList == null) { System.err.println("****** BaseList is null after loading!!!! *****"); } else { System.err.println("*** BaseList is not null."); } } (new VecQuickSort(baseList, null)).sort(); baseListLoaded = true; notifyAll(); } /** * loadBaseNames constructs a hashtable mapping Base references * to base names. This is intended to serve as a local cache * to avoid having to do round-trip calls to the server just * to get a Base reference's name. */ private synchronized void loadBaseNames() throws RemoteException { baseNames = new Hashtable<Base, String>(); Vector<Base> list = getBaseList(); for (Base b: list) { baseNames.put(b, b.getName()); } if (debug) { System.err.println("Finished loading base list"); } baseNamesLoaded = true; notifyAll(); } /** * loadBaseMap() generates baseMap, a mapping of Short's to * the corresponding remote base reference. */ private synchronized void loadBaseMap() throws RemoteException { Vector<Base> myBaseList; int size; /* -- */ myBaseList = getBaseList(); size = myBaseList.size(); baseMap = new Hashtable<Short, Base>(size); baseToShort = new Hashtable<Base, Short>(size); nameShorts = new Hashtable<String, Short>(size); for (Base base: myBaseList) { Short id = Short.valueOf(base.getTypeID()); baseMap.put(id, base); baseToShort.put(base, id); nameShorts.put(base.getName(), id); } baseMapLoaded = true; notifyAll(); } }