/* GanymedeExpirationTask.java This class goes through all objects in the database and processes any expirations or removals. Created: 4 February 1998 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- 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 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.rmi.RemoteException; import java.util.Date; import java.util.Vector; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.Query; import arlut.csd.ganymede.common.QueryDataNode; import arlut.csd.ganymede.common.Result; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; /*------------------------------------------------------------------------------ class GanymedeExpirationTask ------------------------------------------------------------------------------*/ /** * <p>This is a Ganymede server task, for use with the {@link * arlut.csd.ganymede.server.GanymedeScheduler GanymedeScheduler}.</p> * * <p>The GanymedeExpirationTask scans through all objects in the * database and handles expiration and/or removal for any object whose * Expiration or Removal timestamps have passed.</p> * * <p>GanymedeExpirationTask is designed to be run once a day by the * GanymedeScheduler, but running it more often won't hurt * anything.</p> * * <p>The standard GanymedeExpirationTask is paired with the standard * {@link arlut.csd.ganymede.server.GanymedeWarningTask * GanymedeWarningTask} task, which sends out email warning of * expirations and removals to occur in the near future.</p> */ public class GanymedeExpirationTask implements Runnable { public static final boolean debug = false; // --- public GanymedeExpirationTask() { } /** * Just Do It (tm) * * @see java.lang.Runnable */ public void run() { GanymedeSession mySession = null; DBSession myDBSession = null; boolean started = false; boolean finished = false; Thread currentThread = java.lang.Thread.currentThread(); /* -- */ Ganymede.debug("Expiration Task: Starting"); String error = GanymedeServer.lSemaphore.checkEnabled(); if (error != null) { Ganymede.debug("Deferring expiration task - semaphore disabled: " + error); return; } try { try { mySession = new GanymedeSession("expiration"); myDBSession = mySession.getDBSession(); } catch (RemoteException ex) { Ganymede.debug("Expiration Task: Couldn't establish session"); return; } // we don't want no wizards mySession.enableWizards(false); // and we don't want forced required fields oversight.. this // can leave us with some invalid objects, but we can do a // query to scan for them, and if someone edits the objects // later, they'll be requested to fix the problem. mySession.enableOversight(false); ReturnVal retVal = mySession.openTransaction("expiration task", false); // non-interactive if (!ReturnVal.didSucceed(retVal)) { Ganymede.debug("Expiration Task: Couldn't open transaction"); return; } started = true; Date currentTime = new Date(); QueryDataNode expireNode = new QueryDataNode(SchemaConstants.ExpirationField, QueryDataNode.LESSEQ, currentTime); QueryDataNode removeNode = new QueryDataNode(SchemaConstants.RemovalField, QueryDataNode.LESSEQ, currentTime); // -- // we do each query on one object type.. we have to iterate // over all the object types defined in the server and scan // each for objects to be inactivated and/or removed. for (DBObjectBase base: Ganymede.db.bases()) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } // embedded objects are inactivated with their parents, we don't // handle them separately if (base.isEmbedded()) { continue; } if (debug) { Ganymede.debug("Sweeping base " + base.getName() + " for expired objects"); } Query q = new Query(base.getTypeID(), expireNode, false); for (Result result: mySession.internalQuery(q)) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } Invid invid = result.getInvid(); if (debug) { Ganymede.debug("Need to inactivate object " + base.getName() + ":" + result.toString()); } String checkpointKey = "inactivating " + invid.toString(); myDBSession.checkpoint(checkpointKey); try { retVal = mySession.inactivate_db_object(invid); } catch (Throwable ex) { myDBSession.rollback(checkpointKey); throw new RuntimeException(ex); } if (!ReturnVal.didSucceed(retVal)) { myDBSession.rollback(checkpointKey); Ganymede.debug("Expiration task was not able to inactivate object " + base.getName() + ":" + result.toString()); } else { myDBSession.popCheckpoint(checkpointKey); Ganymede.debug("Expiration task inactivated object " + base.getName() + ":" + result.toString()); } } } // now the removals for (DBObjectBase base: Ganymede.db.bases()) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } // embedded objects are removed with their parents, we don't // handle them separately if (base.isEmbedded()) { continue; } if (debug) { Ganymede.debug("Sweeping base " + base.getName() + " for objects to be removed"); } Query q = new Query(base.getTypeID(), removeNode, false); for (Result result: mySession.internalQuery(q)) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } Invid invid = result.getInvid(); if (debug) { Ganymede.debug("Need to remove object " + base.getName() + ":" + result.toString()); } String checkpointKey = "removing " + invid.toString(); myDBSession.checkpoint(checkpointKey); try { retVal = mySession.remove_db_object(invid); } catch (Throwable ex) { myDBSession.rollback(checkpointKey); throw new RuntimeException(ex); } if (!ReturnVal.didSucceed(retVal)) { myDBSession.rollback(checkpointKey); Ganymede.debug("Expiration task was not able to remove object " + base.getName() + ":" + result.toString()); } else { myDBSession.popCheckpoint(checkpointKey); Ganymede.debug("Expiration task deleted object " + base.getName() + ":" + result.toString()); } } } retVal = mySession.commitTransaction(); if (!ReturnVal.didSucceed(retVal)) { // if doNormalProcessing is true, the // transaction was not cleared, but was // left open for a re-try. Abort it. if (retVal.doNormalProcessing) { Ganymede.debug("Expiration Task: couldn't fully commit, trying to abort."); mySession.abortTransaction(); } } mySession.logout(); finished = true; // minimize chance of attempting to abort an open transaction in finally if (!ReturnVal.didSucceed(retVal)) { Ganymede.debug("Expiration Task: Couldn't successfully commit transaction"); } else { Ganymede.debug("Expiration Task: Transaction committed"); } } catch (NotLoggedInException ex) { Ganymede.debug("Mysterious not logged in exception: " + ex.getMessage()); } catch (InterruptedException ex) { } finally { if (started && !finished) { // we'll get here if this task's thread is stopped early Ganymede.debug("Expiration Task: Forced to terminate early, aborting transaction"); if (mySession != null) { try { mySession.abortTransaction(); Ganymede.debug("Expiration Task: Transaction aborted"); mySession.logout(); } catch (NotLoggedInException ex) { } } } } } }