/*
GanymedeAdmin.java
GanymedeAdmin is the server-side implementation of the adminSession
interface; GanymedeAdmin provides the means by which privileged users
can carry out privileged operations on the Ganymede server, including
status monitoring and administrative activities.
Created: 17 January 1997
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2014
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
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.server;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.Unreferenced;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;
import arlut.csd.Util.VectorUtils;
import arlut.csd.ganymede.common.AdminEntry;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.scheduleHandle;
import arlut.csd.ganymede.rmi.AdminAsyncResponder;
import arlut.csd.ganymede.rmi.SchemaEdit;
import arlut.csd.ganymede.rmi.adminSession;
import arlut.csd.Util.TranslationService;
/*------------------------------------------------------------------------------
class
GanymedeAdmin
------------------------------------------------------------------------------*/
/**
* <p>GanymedeAdmin is the server-side implementation of the
* {@link arlut.csd.ganymede.rmi.adminSession adminSession}
* interface; GanymedeAdmin provides the means by which privileged users
* can carry out privileged operations on the Ganymede server, including
* status monitoring and administrative activities.</p>
*
* <p>GanymedeAdmin is actually a dual purpose class. One the one hand,
* GanymedeAdmin implements {@link arlut.csd.ganymede.rmi.adminSession adminSession},
* providing a hook for the admin console to talk to. On the other,
* GanymedeAdmin contains a lot of static fields and methods which the
* server code uses to communicate information to any admin consoles
* that are attached to the server at any given time.</p>
*
* @author Jonathan Abbey, jonabbey@arlut.utexas.edu, ARL:UT
*/
final class GanymedeAdmin implements adminSession, Unreferenced {
private static final boolean debug = false;
/**
* TranslationService object for handling string localization in
* the Ganymede server.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.GanymedeAdmin");
/**
* Static vector of GanymedeAdmin instances, used to
* keep track of the attached admin consoles.
*/
private static Vector<GanymedeAdmin> consoles = new Vector<GanymedeAdmin>();
/**
* Static vector of GanymedeAdmin instances for which
* remote exceptions were caught in the static
* update methods. Used by
* {@link arlut.csd.ganymede.server.GanymedeAdmin#detachBadConsoles() detachBadConsoles()}
* to remove consoles that we were not able to communicate with.
*/
private static Vector<GanymedeAdmin> badConsoles = new Vector<GanymedeAdmin>();
/**
* The overall server state.. 'normal operation', 'shutting down', etc.
*/
private static String state;
/**
* Timestamp that the Ganymede server last consolidated its journal
* file and dumped its database to disk.
*/
private static Date lastDumpDate;
/**
* Free memory statistic that the server sends to admin consoles
*/
private static long freeMem;
/**
* Total memory statistic that the server sends to admin consoles
*/
private static long totalMem;
/**
* <p>Background thread that will order a refresh of the admin
* consoles' task lists every second if we have any sync activities
* currently running. Will be null if no syncs are running.</p>
*/
private static Thread taskRefreshThread;
/**
* <p>Private monitor for managing taskRefreshThread.</p>
*/
private static Object taskRefreshThread_monitor = new Object();
/* -----====================--------------------====================-----
static methods
-----====================--------------------====================----- */
/**
* This static method handles sending disconnect messages
* to all attached consoles and cleaning up the
* GanymedeAdmin.consoles Vector.
*/
public static void closeAllConsoles(String reason)
{
if (debug)
{
System.err.println("GanymedeAdmin.closeAllConsoles: waiting for sync");
}
synchronized (GanymedeAdmin.consoles)
{
if (debug)
{
System.err.println("GanymedeAdmin.closeAllConsoles: got sync");
}
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.forceDisconnect(reason);
}
catch (RemoteException ex)
{
// don't worry about it
}
}
GanymedeAdmin.consoles.clear();
}
}
/**
* This static method is used to send debug log info to
* the consoles. It is used by
* {@link arlut.csd.ganymede.server.Ganymede#debug(java.lang.String) Ganymede.debug()}
* to append information to the console logs.
*/
public static void logAppend(String status)
{
String stampedLine;
synchronized (GanymedeServer.lSemaphore)
{
if (GanymedeServer.lSemaphore.checkEnabled() == null)
{
// "{0, Date} [{1, number, #}] {2}\n"
stampedLine = ts.l("logAppend.enabled_template", new Date(), Integer.valueOf(GanymedeServer.lSemaphore.getCount()), status);
}
else
{
// "{0, Date} [*] {1}\n"
stampedLine = ts.l("logAppend.disabled_template", new Date(), status);
}
}
/* -- */
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.asyncPort.logAppend(stampedLine);
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method sends an updated console count figure to
* all of the attached admin consoles.
*/
public static void setConsoleCount()
{
String message;
/* -- */
synchronized (GanymedeAdmin.consoles)
{
if (consoles.size() > 1)
{
// "{0, number, #} consoles attached"
message = ts.l("setConsoleCount.multiple_attached", Integer.valueOf(consoles.size()));
}
else
{
// "1 console attached"
message = ts.l("setConsoleCount.single_attached");
}
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.asyncPort.changeAdmins(message);
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method is used to send the current transactions in
* journal count to the consoles.
*/
public static void updateTransCount()
{
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doUpdateTransCount();
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method is used to send the last dump time to
* the consoles.
*/
public static void updateLastDump(Date date)
{
GanymedeAdmin.lastDumpDate = date;
updateLastDump();
}
/**
* This static method updates the last dump time to all
* consoles.
*/
public static void updateLastDump()
{
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doUpdateLastDump();
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method is used to update and transmit the server's
* memory status to the consoles.
*/
public static void updateMemState(long freeMem, long totalMem)
{
GanymedeAdmin.freeMem = freeMem;
GanymedeAdmin.totalMem = totalMem;
updateMemState();
}
/**
* This static method is used to send the server's memory status to all
* connected admin consoles.
*/
public static void updateMemState()
{
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doUpdateMemState();
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method updates the objects checked out count on all
* consoles.
*/
public static void updateCheckedOut()
{
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doUpdateCheckedOut();
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method updates the locks held count on all consoles.
*/
public static void updateLocksHeld()
{
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doUpdateLocksHeld();
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method changes the system state and
* sends it out to the consoles
*/
public static void setState(String state)
{
GanymedeAdmin.state = state;
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doSetState();
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method is used to update the list of connnected
* users that appears in any admin consoles attached to the Ganymede
* server.
*/
public static void refreshUsers()
{
Vector<AdminEntry> entries;
/* -- */
// figure out the vector we want to pass along
entries = GanymedeServer.getUserTable();
// update the consoles
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.consoles)
{
try
{
temp.doRefreshUsers(entries);
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(temp, ex);
}
}
}
detachBadConsoles();
}
/**
* This static method is used to update the list of tasks that
* appears in any admin consoles attached to the Ganymede server.
*/
public static void refreshTasks()
{
Vector<scheduleHandle> scheduleHandles;
/* -- */
if (Ganymede.scheduler == null)
{
return;
}
scheduleHandles = Ganymede.scheduler.reportTaskInfo();
boolean anyRunningSyncs = false;
for (scheduleHandle handle: scheduleHandles)
{
if (handle.isRunning())
{
anyRunningSyncs = true;
}
handle.updateServerTime();
}
synchronized (GanymedeAdmin.consoles)
{
for (GanymedeAdmin console: GanymedeAdmin.consoles)
{
try
{
console.doRefreshTasks(scheduleHandles);
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(console, ex);
}
}
}
detachBadConsoles();
synchronized (GanymedeAdmin.taskRefreshThread_monitor)
{
if (anyRunningSyncs && GanymedeAdmin.taskRefreshThread == null)
{
Thread trt = new Thread(new Thread() {
public void run() {
try
{
Thread.sleep(1000);
}
catch (InterruptedException ex)
{
}
synchronized (GanymedeAdmin.taskRefreshThread_monitor)
{
GanymedeAdmin.taskRefreshThread = null;
}
GanymedeAdmin.refreshTasks();
}
}, "task reporter");
GanymedeAdmin.taskRefreshThread = trt;
trt.start();
}
}
}
/**
* <p>This private static method handles communications link
* failures. Note that the serverAdminAsyncResponder will handle
* single instances of admin console RemoteExceptions so that only
* two RemoteExceptions in sequence will raise a
* RemoteException.</p>
*
* <p>Any code that calls this method should call detachBadConsoles()
* once it has exited any loops over the static consoles vector to
* actually expunge any failed consoles from our consoles vector.</p>
*/
private final static void handleConsoleRMIFailure(GanymedeAdmin console, RemoteException ex)
{
// don't use Ganymede.debug so that we can avoid infinite loops
// during error handling
System.err.println("Communications failure to " + console.toString());
VectorUtils.unionAdd(badConsoles, console);
}
/**
* <p>This private static method is called to remove any consoles
* that have experienced RMI failures from the static
* GanymedeAdmin.consoles vector. This method should never be
* called from within a loop over GanymedeAdmin.consoles.</p>
*/
private static void detachBadConsoles()
{
synchronized (GanymedeAdmin.badConsoles)
{
for (GanymedeAdmin temp: GanymedeAdmin.badConsoles)
{
// the logout() method will cause the console to remove
// itself from the static GanymedeAdmin.consoles vecotr,
// which is why we are synchronized on
// GanymedeAdmin.consoles here.
// "error communicating with console"
temp.logout(ts.l("detachBadConsoles.error"));
}
badConsoles.clear();
}
}
/* --- */
/**
* <p>The Invid of the admin that logged in to get this
* GanymedeAdmin object on the server.</p>
*/
private final Invid adminInvid;
/**
* The name that the admin console authenticated with. We
* keep it here rather than asking the console later so that
* the console can't decide it should call itself 'supergash'
* at some later point.
*/
private final String adminName;
/**
* The name or ip address of the system that this admin console
* is attached from.
*/
private final String clientHost;
/**
* The string token used to lock the server's lsemaphore.
*/
private final String schemaDisableToken;
/**
* If true, the admin console is attached with full privileges to
* run tasks, shut down the server, and so on. If false, the user
* just has privileges to watch the server's operation.
*/
private boolean fullprivs = false;
/**
* A server-side asyncPort that maintains an event queue for the
* admin console attached to this GanymedeAdmin object.
*/
private serverAdminAsyncResponder asyncPort;
/* -- */
/**
* <p>This is the GanymedeAdmin constructor, used to create a new
* server-side admin console attachment.</p>
*
* <p>Admin is an RMI remote object exported by the client in the
* form of a callback.</p>
*
* <p>This constructor is called from
* {@link arlut.csd.ganymede.rmi.Server#admin(java.lang.String username, java.lang.String password) admin()},
* which is responsible for authenticating the name and password before
* calling this constructor.</p>
*/
public GanymedeAdmin(boolean fullprivs, Invid adminInvid, String adminName, String clientHost) throws RemoteException
{
Ganymede.rmi.publishObject(this);
// if the memoryStatusTask hasn't previously recorded the free and
// total memory, get those statistics so we can provide them to
// the console
if (GanymedeAdmin.freeMem == 0 && GanymedeAdmin.totalMem == 0)
{
GanymedeAdmin.freeMem = Runtime.getRuntime().freeMemory();
GanymedeAdmin.totalMem = Runtime.getRuntime().totalMemory();
}
this.asyncPort = new serverAdminAsyncResponder();
this.fullprivs = fullprivs;
this.adminInvid = adminInvid;
this.adminName = adminName;
this.clientHost = clientHost;
// NB: disableToken must be "schema edit:" followed by the admin
// name to match logic in GanymedeServer, DBSchemaEdit, and
// GanymedeXMLSession
this.schemaDisableToken = "schema edit:" + adminName;
consoles.add(this); // this can block if we are currently looping on consoles
try
{
setConsoleCount();
asyncPort.setServerStart(Ganymede.startTime);
doUpdateTransCount();
doUpdateTransCount();
doUpdateLastDump();
doUpdateCheckedOut();
doUpdateLocksHeld();
doUpdateMemState();
doSetState();
doRefreshUsers(GanymedeServer.getUserTable());
doRefreshTasks(Ganymede.scheduler.reportTaskInfo());
}
catch (RemoteException ex)
{
handleConsoleRMIFailure(this, ex);
}
}
/**
* This private method is used to update this admin console
* handle's transaction count.
*/
private void doUpdateTransCount() throws RemoteException
{
asyncPort.setTransactionsInJournal(Ganymede.db.journal.getTransactionsInJournal());
}
/**
* This private method updates the last dump time on this admin
* console
*/
private void doUpdateLastDump() throws RemoteException
{
asyncPort.setLastDumpTime(GanymedeAdmin.lastDumpDate);
}
/**
* This private method updates the memory statistics display on this
* admin console
*/
private void doUpdateMemState() throws RemoteException
{
asyncPort.setMemoryState(GanymedeAdmin.freeMem, GanymedeAdmin.totalMem);
}
/**
* This private method updates the objects checked out display on
* this admin console
*/
private void doUpdateCheckedOut() throws RemoteException
{
asyncPort.setObjectsCheckedOut(Ganymede.db.objectsCheckedOut);
}
/**
* This private method updates the number of locks held display on
* this admin console
*/
private void doUpdateLocksHeld() throws RemoteException
{
asyncPort.setLocksHeld(Ganymede.db.lockSync.getLockCount(), Ganymede.db.lockSync.getLocksWaitingCount());
}
/**
* This private method updates the server state display on
* this admin console
*/
private void doSetState() throws RemoteException
{
asyncPort.changeState(GanymedeAdmin.state);
}
/**
* This private method updates the user status table on
* this admin console
*/
private void doRefreshUsers(Vector<AdminEntry> entries) throws RemoteException
{
asyncPort.changeUsers(entries);
}
/**
* This private method updates the task status table on
* this admin console
*/
private void doRefreshTasks(Vector<scheduleHandle> scheduleHandles) throws RemoteException
{
asyncPort.changeTasks(scheduleHandles);
}
/**
* <p>This public method forces a disconnect of the remote admin console
* and cleans up the asyncPort.</p>
*
* <p>This method *does not* handle removing this GanymedeAdmin
* console object from the static GanymedeAdmin.consoles Vector, so
* that it can safely be called from a loop over
* GanymedeAdmin.consoles in closeAllConsoles() when the server is
* being shut down.</p>
*/
public void forceDisconnect(String reason) throws RemoteException
{
asyncPort.forceDisconnect(reason);
}
public String toString()
{
if (fullprivs)
{
// "{0} on {1} with full access"
return ts.l("toString.fullprivs", adminName, clientHost);
}
else
{
// "{0} on {1} with monitor access"
return ts.l("toString.not_fullprivs", adminName, clientHost);
}
}
/* -----====================--------------------====================-----
remotely callable methods
-----====================--------------------====================----- */
/**
* <p>Disconnect the remote admin console associated with this
* object</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* <p>No server-side code should call this method from a thread that
* is looping over the static GanymedeAdmin.consoles Vector, or else
* the Vector will be changed from within the loop, possibly
* resulting in an exception being thrown.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public void logout()
{
this.logout(null);
}
/**
* <p>Disconnect the remote admin console associated with this
* object.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* <p>No server-side code should call this method from a thread that
* is looping over the static GanymedeAdmin.consoles Vector, or else
* the Vector will be changed from within the loop, possibly
* resulting in an exception being thrown.</p>
*/
public void logout(String reason)
{
if (asyncPort.isAlive())
{
asyncPort.shutdown();
}
if (consoles.remove(this))
{
String eventStr = null;
if (reason == null)
{
// "Admin console {0} detached from {1}"
eventStr = ts.l("logout.without_reason", adminName, clientHost);
}
else
{
// "Admin console {0} detached from {1}: {2}"
eventStr = ts.l("logout.with_reason", adminName, clientHost, reason);
}
Ganymede.debug(eventStr);
if (Ganymede.log != null)
{
Ganymede.log.logSystemEvent(new DBLogEvent("admindisconnect",
eventStr,
null,
adminName,
null,
null));
}
setConsoleCount();
}
}
/**
* <p>This method is called when the Java RMI system detects that this
* remote object is no longer referenced by any remote objects.</p>
*
* <p>This method handles abnormal logouts and time outs for us. By
* default, the 1.1 RMI time-out is 10 minutes.</p>
*
* <p>The RMI timeout can be modified by setting the system property
* sun.rmi.transport.proxy.connectTimeout.</p>
*
* @see java.rmi.server.Unreferenced
*/
public void unreferenced()
{
// "RMI timeout/dead console"
this.logout(ts.l("unreferenced.dead"));
}
/**
* <p>This method is used to allow the admin console to retrieve a remote reference to
* a {@link arlut.csd.ganymede.server.serverAdminAsyncResponder}, which will allow
* the admin console to poll the server for asynchronous messages from the server.</p>
*
* <p>This is used to allow the server to send admin notifications
* to the console, even if the console is behind a network or
* personal system firewall. The serverAdminAsyncResponder blocks
* while there is no message to send, and the console will poll for
* new messages.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public AdminAsyncResponder getAsyncPort() throws RemoteException
{
// we need to create a temp variable defined in terms of the
// interface so that RMI won't freak out and try to serialize the
// serverAdminAsyncResponder.
AdminAsyncResponder myAsyncPort = (AdminAsyncResponder) asyncPort;
return myAsyncPort;
}
/**
* <p>This method lets the admin console explicitly request a
* refresh. Upon being called, the server will call several methods
* on the admin console's {@link
* arlut.csd.ganymede.server.serverAdminAsyncResponder
* serverAdminAsyncResponder} interface to pass current status
* information to the console.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public void refreshMe() throws RemoteException
{
asyncPort.setServerStart(Ganymede.startTime);
if (consoles.size() > 1)
{
// "{0, number, #} consoles attached"
asyncPort.changeAdmins(ts.l("setConsoleCount.multiple_attached", Integer.valueOf(consoles.size())));
}
else
{
// "1 console attached"
asyncPort.changeAdmins(ts.l("setConsoleCount.single_attached"));
}
doUpdateTransCount();
doUpdateLastDump();
doUpdateCheckedOut();
doUpdateLocksHeld();
doUpdateMemState();
doSetState();
doRefreshUsers(GanymedeServer.getUserTable());
doRefreshTasks(Ganymede.scheduler.reportTaskInfo());
}
/**
* <p>This method is called by admin console code to force
* a complete rebuild of all external builds. This means that
* all databases will have their last modification timestamp
* cleared and all builder tasks will be scheduled for immediate
* execution.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal forceBuild()
{
if (!fullprivs)
{
// "Permissions Denied
// "You do not have permissions to force a full rebuild."
return Ganymede.createErrorDialog(ts.l("forceBuild.denied_title"),
ts.l("forceBuild.denied_text"));
}
// "Admin console forcing full network build..."
Ganymede.debug(ts.l("forceBuild.proceeding"));
Ganymede.forceBuilderTasks();
return null;
}
/**
* <p>Kicks all users off of the Ganymede server on behalf of this
* admin console</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal killAll()
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permissions to knock all users off of the server"
return Ganymede.createErrorDialog(ts.l("killAll.denied_title"),
ts.l("killAll.denied_text"));
}
// "Admin console disconnecting you"
GanymedeServer.server.killAllUsers(ts.l("killAll.message_to_users"));
return null;
}
/**
* <p>Kicks a user off of the Ganymede server on behalf of this admin
* console.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal kill(String user)
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to forcibly disconnect user {0}."
return Ganymede.createErrorDialog(ts.l("kill.denied_title"),
ts.l("kill.denied_text", user));
}
// "Admin console disconnecting you"
if (GanymedeServer.server.killUser(user, ts.l("kill.message_to_user")))
{
return null;
}
// "Kill Error"
// "I couldn''t find any active user named {0}."
return Ganymede.createErrorDialog(ts.l("kill.error_title"),
ts.l("kill.error_text", user));
}
/**
* <p>Shutdown the server cleanly, on behalf of this admin console.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @param waitForUsers if true, shutdown will be deferred until all
* users are logged out. No new users will be allowed to login.
*
* @param reason Message to be logged and displayed to any users
* connected.
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal shutdown(boolean waitForUsers, String reason)
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to shut down the Ganymede server."
return Ganymede.createErrorDialog(ts.l("shutdown.denied_title"),
ts.l("shutdown.denied_text"));
}
if (waitForUsers)
{
GanymedeServer.setShutdown(reason, this);
// "Server Set For Shutdown"
// "The server is prepared for shut down. Shutdown will commence as soon as all current users log out."
return Ganymede.createInfoDialog(ts.l("shutdown.advisory_title"),
ts.l("shutdown.advisory_text"));
}
else
{
return GanymedeServer.shutdown(reason, this); // we may never return if the shutdown succeeds.. the client
// will catch an exception in that case.
}
}
/**
* <p>Dumps the current state of the db to disk.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote
* interface, and may be called remotely by attached admin
* consoles.</p>
*
* @see arlut.csd.ganymede.server.DBStore#dump(java.lang.String,
* boolean, boolean)
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal dumpDB()
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to execute a database dump."
return Ganymede.createErrorDialog(ts.l("dumpDB.denied_title"),
ts.l("dumpDB.denied_text"));
}
// "Dumping Database"
setState(ts.l("dumpDB.dump_state"));
try
{
Ganymede.db.dump(Ganymede.dbFilename, true, true); // release, archive
}
catch (IOException ex)
{
// "Database Dump Error"
// "Database could not be dumped successfully: {0}"
return Ganymede.createErrorDialog(ts.l("dumpDB.error_title"),
ts.l("dumpDB.error_text", ex.toString()));
}
catch (InterruptedException ex)
{
// "Database Dump Error"
// "Database could not be dumped successfully: {0}"
return Ganymede.createErrorDialog(ts.l("dumpDB.error_title"),
ts.l("dumpDB.error_text", ex.toString()));
}
finally
{
// "Normal Operation"
setState(DBStore.normal_state);
}
Ganymede.debug(ts.l("dumpDB.dumped"));
return null;
}
/**
* <p>Runs a possibly long-running verification suite on the Ganymede server
* database's invid links.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal runInvidTest()
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to execute an Invid integrity test on the server."
return Ganymede.createErrorDialog(ts.l("runInvidTest.denied_title"),
ts.l("runInvidTest.denied_text"));
}
// "Running Invid Test"
GanymedeAdmin.setState(ts.l("runInvidTest.running_state"));
if (Ganymede.server.checkInvids())
{
// "Invid Test completed successfully, no problems boss."
Ganymede.debug(ts.l("runInvidTest.good_result"));
}
else
{
// "Invid Test encountered problems. Oi, you're in the soup now, boss."
Ganymede.debug(ts.l("runInvidTest.bad_result"));
}
// "Normal Operation"
GanymedeAdmin.setState(DBStore.normal_state);
return null;
}
/**
* <p>Runs a possibly long-running verification and repair operation on
* the Ganymede server's invid database links.</p>
*
* <p>Removes any invid pointers in the Ganymede database whose
* targets are not properly defined. This should not ever happen
* unless there is a bug some place in the server.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal runInvidSweep()
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to execute an Invid sweep on the server."
return Ganymede.createErrorDialog(ts.l("runInvidSweep.denied_title"),
ts.l("runInvidSweep.denied_text"));
}
// "Running Invid Sweep"
GanymedeAdmin.setState(ts.l("runInvidSweep.running_state"));
Ganymede.debug(ts.l("runInvidSweep.running_state"));
Ganymede.server.sweepInvids();
// "Normal Operation"
GanymedeAdmin.setState(DBStore.normal_state);
Ganymede.debug(DBStore.normal_state);
return null;
}
/**
* <p>Runs a verification on the integrity of embedded objects and
* their containers.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal runEmbeddedTest()
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to execute an embedded objects integrity test on the server."
return Ganymede.createErrorDialog(ts.l("runEmbeddedTest.denied_title"),
ts.l("runEmbeddedTest.denied_text"));
}
// "Running Embedded Test"
GanymedeAdmin.setState(ts.l("runEmbeddedTest.running_state"));
if (Ganymede.server.checkEmbeddedObjects())
{
// "Embedded Objects Test completed successfully, no problems boss."
Ganymede.debug(ts.l("runEmbeddedTest.good_result"));
}
else
{
// "Embedded Objects Test encountered problems. Oi, you're in the soup now, boss."
Ganymede.debug(ts.l("runEmbeddedTest.bad_result"));
}
// "Normal Operation"
GanymedeAdmin.setState(DBStore.normal_state);
return null;
}
/**
* <p>Removes any embedded objects which do not have containers.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal runEmbeddedSweep()
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to execute an Embedded Objects sweep on the server."
return Ganymede.createErrorDialog(ts.l("runEmbeddedSweep.denied_title"),
ts.l("runEmbeddedSweep.denied_text"));
}
return Ganymede.server.sweepEmbeddedObjects();
}
/**
* <p>Causes a pre-registered task in the Ganymede server
* to be executed as soon as possible. This method call
* will have no effect if the task is currently running.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @param name The name of the task to run
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal runTaskNow(String name)
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to execute tasks on the server."
return Ganymede.createErrorDialog(ts.l("runTaskNow.denied_title"),
ts.l("runTaskNow.denied_text"));
}
if (Ganymede.scheduler.runTaskNow(name))
{
return null;
}
// "Couldn''t run task {0}. Some sort of error on the server?"
return Ganymede.createErrorDialog(ts.l("runTaskNow.error", name));
}
/**
* <p>Causes a running task to be interrupted as soon as possible.
* Ganymede tasks need to be specifically written to be able
* to respond to interruption, so it is not guaranteed that the
* task named will always be able to safely or immediately respond
* to a stopTask() command.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @param name The name of the task to interrupt
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal stopTask(String name)
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to stop tasks on the server."
return Ganymede.createErrorDialog(ts.l("stopTask.denied_title"),
ts.l("stopTask.denied_text"));
}
if (Ganymede.scheduler.stopTask(name))
{
return null;
}
// "Couldn''t stop task {0}. Perhaps the task wasn't running?"
return Ganymede.createErrorDialog(ts.l("stopTask.error", name));
}
/**
* <p>Causes a registered task to be made ineligible for execution
* until {@link arlut.csd.ganymede.server.GanymedeAdmin#enableTask(java.lang.String) enableTask()}
* is called. This method will not stop a task that is currently
* running.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @param name The name of the task to disable
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal disableTask(String name)
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to disable tasks on the server."
return Ganymede.createErrorDialog(ts.l("disableTask.denied_title"),
ts.l("disableTask.denied_text"));
}
if (Ganymede.scheduler.disableTask(name))
{
return null;
}
// "Couldn''t disable task {0}. Some sort of error on the server?"
return Ganymede.createErrorDialog(ts.l("disableTask.error", name));
}
/**
* <p>Causes a task that was temporarily disabled by
* {@link arlut.csd.ganymede.server.GanymedeAdmin#disableTask(java.lang.String) disableTask()}
* to be available for execution again.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @param name The name of the task to enable
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public ReturnVal enableTask(String name)
{
if (!fullprivs)
{
// "Permissions Denied"
// "You do not have permission to re-enable tasks on the server."
return Ganymede.createErrorDialog(ts.l("enableTask.denied_title"),
ts.l("enableTask.denied_text"));
}
if (Ganymede.scheduler.enableTask(name))
{
return null;
}
// "Couldn''t enable task {0}. Perhaps the task isn't registered?"
return Ganymede.createErrorDialog(ts.l("enableTask.error", name));
}
/**
* <p>Locks the server to prevent client logins and edits the server
* schema.</p>
*
* <p>This method will return a {@link
* arlut.csd.ganymede.rmi.SchemaEdit SchemaEdit} remote reference to the
* admin console, which will present a graphical schema editor using
* this remote reference. The server will remain locked until the
* admin console commits or cancels the schema editing session,
* either through affirmative action or through the death of the
* admin console or the network connection. The {@link
* arlut.csd.ganymede.server.DBSchemaEdit DBSchemaEdit} class on the server
* coordinates everything.</p>
*
* <p>This method is part of the {@link
* arlut.csd.ganymede.rmi.adminSession adminSession} remote interface,
* and may be called remotely by attached admin consoles.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public SchemaEdit editSchema()
{
if (!fullprivs)
{
// "Attempt made to edit schema by a non-privileged console: {0}"
Ganymede.debug(ts.l("editSchema.no_privs", this.toString()));
return null;
}
// "entering editSchema"
Ganymede.debug(ts.l("editSchema.entering"));
try
{
// Check to see if the server is in its standard state with no
// user sessions on the lSemaphore, without blocking.
String semaphoreCondition = GanymedeServer.lSemaphore.disable(this.schemaDisableToken, true, 0);
if (semaphoreCondition != null)
{
// "Admin console {0} can''t edit schema. Ganymede login semaphore already locked with condition "{1}"."
Ganymede.debug(ts.l("editSchema.semaphore_error", this.toString(), semaphoreCondition));
return null;
}
}
catch (InterruptedException ex)
{
Ganymede.logError(ex);
throw new RuntimeException(ex.getMessage());
}
// okay at this point we've asserted our interest in editing the
// schema and made sure that no user session is logged in or can log in.
// Now we just need to make sure that we don't have any of the
// bases locked by anything that is skipping the semaphore, such
// as tasks.
// In fact, I believe that the server is now safe against lock
// races due to all tasks that might involve DBObjectBase access
// being guarded by the loginSemaphore, but there is little cost
// in sync'ing here.
// All the DBLock establish methods synchronize on the DBLockSync
// object referenced by Ganymede.db.lockSync, so we are safe
// against lock establish race conditions by synchronizing this
// section on Ganymede.db.lockSync.
synchronized (Ganymede.db.lockSync)
{
// "Admin console {0} entering editSchema synchronization block."
Ganymede.debug(ts.l("editSchema.synchronizing", this.toString()));
for (DBObjectBase base: Ganymede.db.bases())
{
if (base.isLocked())
{
// "Admin console {0} can''t edit Schema, lock held on {1}."
Ganymede.debug(ts.l("editSchema.locked_base", this.toString(), base.getName()));
GanymedeServer.lSemaphore.enable(this.schemaDisableToken);
return null;
}
}
// should be okay
// "Ok to create DBSchemaEdit for admin console {0}."
Ganymede.debug(ts.l("editSchema.okay_to_go", this.toString()));
// "Schema Edit In Progress"
GanymedeAdmin.setState(ts.l("editSchema.edit_state"));
try
{
DBSchemaEdit result = new DBSchemaEdit(this.adminName);
// we've created our copy of all of our DBObjectBase and
// DBObjectBaseField objects above. We're going to return
// and drop the synchronization on Ganymede.db.lockSync.
return result;
}
catch (RemoteException ex)
{
GanymedeServer.lSemaphore.enable(this.schemaDisableToken);
return null;
}
}
}
/**
* <p>Retrieves a multi-line String containing information about
* user and administrator login and logout data from the server's
* log.</p>
*
* <p>If the provided startDate is null, the server will return all
* login and logout activity since the server was last started.</p>
*
* <p>Otherwise, the server will return information about all logins
* and logouts that occurred after startDate.</p>
*
* @see arlut.csd.ganymede.rmi.adminSession
*/
public String getLoginHistory(Date startDate)
{
return Ganymede.log.retrieveHistory(null, startDate, null, false, false, true).toString();
}
/**
* <p>Return the Invid of the admin who is logged into this console.</p>
*/
public Invid getAdminInvid()
{
return this.adminInvid;
}
/**
* <p>Return the login name of the admin who is logged into this
* console.</p>
*/
public String getAdminName()
{
return this.adminName;
}
/**
* <p>Return the hostname from which the admin who is logged into
* this console is connected.</p>
*/
public String getAdminHost()
{
return this.clientHost;
}
}