/* GanymedeWarningTask.java This class goes through all objects in the database and sends out any warnings for objects that are going to expire within a whole number of weeks in the future. 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 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.Calendar; import java.util.Date; import java.util.Vector; import arlut.csd.Util.TranslationService; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.Query; import arlut.csd.ganymede.common.QueryAndNode; import arlut.csd.ganymede.common.QueryDataNode; import arlut.csd.ganymede.common.QueryNode; import arlut.csd.ganymede.common.Result; import arlut.csd.ganymede.common.SchemaConstants; /*------------------------------------------------------------------------------ class GanymedeWarningTask ------------------------------------------------------------------------------*/ /** * <p>This is a Ganymede server task, for use with the {@link * arlut.csd.ganymede.server.GanymedeScheduler GanymedeScheduler}.</p> * * <p>The standard GanymedeWarningTask class scans through all objects * in the database and mails out warnings for those objects that are * going to expire on this day one, two, or three weeks in the future, * as well as those objects that are going to expire within the * following 24 hours. The email messages sent are based on the * server's Object Events configuration settings, and will also be * sent to the list of email addresses returned by the {@link * arlut.csd.ganymede.server.DBEditObject#getEmailTargets(arlut.csd.ganymede.server.DBObject) * getEmailTargets()} customization method in each object's {@link * arlut.csd.ganymede.server.DBEditObject DBEditObject} customization * class, if any such is defined.</p> * * <p>GanymedeWarningTask must not be run more than once a day by the * GanymedeScheduler, or else users and admins may receive redundant * warnings.</p> * * <p>The GanymedeWarningTask is paired with the standard {@link * arlut.csd.ganymede.server.GanymedeExpirationTask * GanymedeExpirationTask} task, which handles the actual expiration * and removal of database objects.</p> */ public class GanymedeWarningTask implements Runnable { /** * TranslationService object for handling string localization in the * Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.GanymedeWarningTask"); public GanymedeWarningTask() { } /** * Just Do It (tm) * * @see java.lang.Runnable */ public void run() { GanymedeSession mySession = null; boolean started = false; boolean finished = false; String semaphoreCondition; Thread currentThread = java.lang.Thread.currentThread(); /* -- */ // we have to increment the GanymedeServer loginSemaphore, because // we are using a non-semaphored local GanymedeSession semaphoreCondition = GanymedeServer.lSemaphore.increment(); if (semaphoreCondition != null) { Ganymede.debug("Deferring warning task - semaphore disabled: " + semaphoreCondition); return; } try { Ganymede.debug("Warning Task: Starting"); try { mySession = new GanymedeSession("warning"); // supergash } catch (RemoteException ex) { Ganymede.debug("Warning Task: Couldn't establish session"); return; } started = true; Date currentTime = new Date(); Calendar cal = Calendar.getInstance(); Calendar cal2; Date loTime, hiTime; QueryNode expireNode, removeNode; StringBuilder tempString = new StringBuilder(); Vector<Invid> objects = new Vector<Invid>(); // -- cal.setTime(currentTime); // do the weekly warnings for (int i = 0; i < 3; i++) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } cal.add(Calendar.DATE, 7); loTime = cal.getTime(); cal2 = Calendar.getInstance(); cal2.setTime(loTime); cal2.add(Calendar.DATE, 1); hiTime = cal2.getTime(); expireNode = new QueryAndNode(new QueryDataNode(SchemaConstants.ExpirationField, QueryDataNode.GREATEQ, loTime), new QueryDataNode(SchemaConstants.ExpirationField, QueryDataNode.LESSEQ, hiTime)); for (DBObjectBase base: Ganymede.db.bases()) { if (base.isEmbedded()) { continue; } DBEditObject objectHook = base.getObjectHook(); if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } 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(); String title; if (i == 0) { // "{0} {1} expires in one week" title = ts.l("run.expire_one_week_email_subj", base.getName(), mySession.getDBSession().getObjectLabel(invid)); } else { // "{0} {1} expires in {2,num,#} weeks" title = ts.l("run.expire_multi_week_email_subj", base.getName(), mySession.getDBSession().getObjectLabel(invid), Integer.valueOf(i+1)); } DBObject obj = mySession.getDBSession().viewDBObject(invid); if (!objectHook.reactToExpirationWarning(obj, 7 * i)) { tempString.setLength(0); tempString.append(title); tempString.append(getExpirationWarningMesg(obj)); objects.setSize(0); objects.add(invid); sendMail("expirationwarn", title, tempString.toString(), objects); } } } // now the removals removeNode = new QueryAndNode(new QueryDataNode(SchemaConstants.RemovalField, QueryDataNode.GREATEQ, loTime), new QueryDataNode(SchemaConstants.RemovalField, QueryDataNode.LESSEQ, hiTime)); for (DBObjectBase base: Ganymede.db.bases()) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } if (base.isEmbedded()) { continue; } DBEditObject objectHook = base.getObjectHook(); 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(); String title; if (i == 0) { // "{0} {1} will be removed in one week" title = ts.l("run.remove_one_week_email_subj", base.getName(), mySession.getDBSession().getObjectLabel(invid)); } else { // "{0} {1} will be removed in {2,num,#} weeks" title = ts.l("run.remove_multi_week_email_subj", base.getName(), mySession.getDBSession().getObjectLabel(invid), Integer.valueOf(i+1)); } DBObject obj = mySession.getDBSession().viewDBObject(invid); if (!objectHook.reactToRemovalWarning(obj, 7 * i)) { Date actionDate = (Date) obj.getFieldValueLocal(SchemaConstants.RemovalField); tempString.setLength(0); tempString.append(title); // "\n\nRemoval scheduled to take place on or after {0,date}." tempString.append(ts.l("run.removal_scheduled", actionDate)); objects.setSize(0); objects.add(invid); sendMail("removalwarn", title, tempString.toString(), objects); } } } } // now the next-day warnings loTime = currentTime; cal2 = Calendar.getInstance(); cal2.setTime(loTime); cal2.add(Calendar.DATE, 1); hiTime = cal2.getTime(); expireNode = new QueryAndNode(new QueryDataNode(SchemaConstants.ExpirationField, QueryDataNode.GREATEQ, loTime), new QueryDataNode(SchemaConstants.ExpirationField, QueryDataNode.LESSEQ, hiTime)); for (DBObjectBase base: Ganymede.db.bases()) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } if (base.isEmbedded()) { continue; } 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(); // "** {0} {1} expires within 24 hours **" String title = ts.l("run.expire_real_soon_now", base.getName(), mySession.getDBSession().getObjectLabel(invid)); tempString.setLength(0); tempString.append(title); DBObject obj = mySession.getDBSession().viewDBObject(invid); tempString.append(getExpirationWarningMesg(obj)); objects.setSize(0); objects.add(invid); sendMail("expirationwarn", title, tempString.toString(), objects); } } // now the removals removeNode = new QueryAndNode(new QueryDataNode(SchemaConstants.RemovalField, QueryDataNode.GREATEQ, loTime), new QueryDataNode(SchemaConstants.RemovalField, QueryDataNode.LESSEQ, hiTime)); for (DBObjectBase base: Ganymede.db.bases()) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } if (base.isEmbedded()) { continue; } 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(); // "** {0} {1} will be removed within the next 24 hours! **" String title = ts.l("run.remove_real_soon_now", base.getName(), mySession.getDBSession().getObjectLabel(invid)); DBObject obj = mySession.getDBSession().viewDBObject(invid); Date actionDate = (Date) obj.getFieldValueLocal(SchemaConstants.RemovalField); tempString.setLength(0); tempString.append(title); // "\n\nRemoval scheduled to take place on or after {0,date}." tempString.append(ts.l("run.removal_scheduled", actionDate)); objects.setSize(0); objects.add(invid); sendMail("removalwarn", title, tempString.toString(), objects); } } mySession.logout(); finished = true; } catch (InterruptedException ex) { Ganymede.debug("Warning task aborted due to task stop command."); } finally { GanymedeServer.lSemaphore.decrement(); if (started && !finished) { // we'll get here if this task's thread is stopped early Ganymede.debug("Warning Task: Forced to terminate early, aborting."); if (mySession != null) { mySession.logout(); } } else { Ganymede.debug("Warning Task: Completed"); } } } private void sendMail(String type, String title, String description, Vector<Invid> invids) { DBLogEvent event; /* -- */ // create a log event event = new DBLogEvent(type, description, null, null, invids, null); // we've already put the description in the event, don't need // to provide a separate description string to mailNotify Ganymede.log.mailNotify(title, null, event, DBLog.MailMode.BOTH, null); // Ganymede.debug(description); } public String getExpirationWarningMesg(DBObject object) { Date actionDate = (Date) object.getFieldValueLocal(SchemaConstants.ExpirationField); String typeName = object.getTypeName(); String label = object.getLabel(); /* \n\ \n\ {0} {1} is scheduled to expire after {2,date}. In order to prevent this {0} from \ expiring, this object''s Expiration Date field must be cleared or changed in Ganymede.\n \ \n\ Object expiration typically means that the object in question is to be rendered unusable, \ but the object will not be immediately removed from the Ganymede database. Objects that \ have expired will typically be scheduled for removal from the \ Ganymede database after a delay period.\n\ \n\ Depending on the type of object, the object may be made usable again by a Ganymede administrator \ taking the appropriate action prior to the object''s formal removal.\n\ \n\ As with all Ganymede messages, if you have questions about this action, please contact \ your Ganymede management team. */ return ts.l("getExpirationWarningMesg.message", typeName, label, actionDate); } }