0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/email/tags/sakai_2-4-1/email-impl/impl/src/java/org/sakaiproject/email/impl/BaseDigestService.java $
0003: * $Id: BaseDigestService.java 7516 2006-04-09 13:01:18Z ggolden@umich.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.email.impl;
0021:
0022: import java.util.Hashtable;
0023: import java.util.Iterator;
0024: import java.util.List;
0025: import java.util.Map;
0026: import java.util.Stack;
0027: import java.util.Vector;
0028:
0029: import org.apache.commons.logging.Log;
0030: import org.apache.commons.logging.LogFactory;
0031: import org.sakaiproject.authz.api.SecurityService;
0032: import org.sakaiproject.component.api.ServerConfigurationService;
0033: import org.sakaiproject.component.cover.ComponentManager;
0034: import org.sakaiproject.email.api.Digest;
0035: import org.sakaiproject.email.api.DigestEdit;
0036: import org.sakaiproject.email.api.DigestMessage;
0037: import org.sakaiproject.email.api.DigestService;
0038: import org.sakaiproject.email.api.EmailService;
0039: import org.sakaiproject.entity.api.Edit;
0040: import org.sakaiproject.entity.api.Entity;
0041: import org.sakaiproject.entity.api.ResourceProperties;
0042: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
0043: import org.sakaiproject.event.api.EventTrackingService;
0044: import org.sakaiproject.exception.IdUnusedException;
0045: import org.sakaiproject.exception.IdUsedException;
0046: import org.sakaiproject.exception.InUseException;
0047: import org.sakaiproject.exception.PermissionException;
0048: import org.sakaiproject.time.api.Time;
0049: import org.sakaiproject.time.api.TimeBreakdown;
0050: import org.sakaiproject.time.api.TimeRange;
0051: import org.sakaiproject.time.api.TimeService;
0052: import org.sakaiproject.user.api.UserDirectoryService;
0053: import org.sakaiproject.util.BaseResourcePropertiesEdit;
0054: import org.sakaiproject.util.ResourceLoader;
0055: import org.sakaiproject.util.StorageUser;
0056: import org.sakaiproject.util.Xml;
0057: import org.sakaiproject.tool.api.SessionBindingEvent;
0058: import org.sakaiproject.tool.api.SessionBindingListener;
0059: import org.sakaiproject.tool.api.SessionManager;
0060: import org.w3c.dom.Document;
0061: import org.w3c.dom.Element;
0062: import org.w3c.dom.Node;
0063: import org.w3c.dom.NodeList;
0064:
0065: /**
0066: * <p>
0067: * BaseDigestService is the base service for DigestService.
0068: * </p>
0069: */
0070: public abstract class BaseDigestService implements DigestService,
0071: StorageUser, Runnable {
0072: /** Our logger. */
0073: private static Log M_log = LogFactory
0074: .getLog(BasicEmailService.class);
0075:
0076: private ResourceLoader rb = new ResourceLoader("email-impl");
0077:
0078: /** Storage manager for this service. */
0079: protected Storage m_storage = null;
0080:
0081: /** The initial portion of a relative access point URL. */
0082: protected String m_relativeAccessPoint = null;
0083:
0084: /** The queue of digests waiting to be added (DigestMessage). */
0085: protected List m_digestQueue = new Vector();
0086:
0087: /** The thread I run my periodic clean and report on. */
0088: protected Thread m_thread = null;
0089:
0090: /** My thread's quit flag. */
0091: protected boolean m_threadStop = false;
0092:
0093: /** How long to wait between runnable runs (ms). */
0094: protected static final long PERIOD = 1000;
0095:
0096: /** True if we are in the mode of sending out digests, false if we are waiting. */
0097: protected boolean m_sendDigests = true;
0098:
0099: /** The time period last time the sendDigests() was called. */
0100: protected String m_lastSendPeriod = null;
0101:
0102: /**********************************************************************************************************************************************************************************************************************************************************
0103: * Runnable
0104: *********************************************************************************************************************************************************************************************************************************************************/
0105:
0106: /**
0107: * Start the clean and report thread.
0108: */
0109: protected void start() {
0110: m_threadStop = false;
0111:
0112: m_thread = new Thread(this , getClass().getName());
0113: m_thread.start();
0114: }
0115:
0116: /**
0117: * Stop the clean and report thread.
0118: */
0119: protected void stop() {
0120: if (m_thread == null)
0121: return;
0122:
0123: // signal the thread to stop
0124: m_threadStop = true;
0125:
0126: // wake up the thread
0127: m_thread.interrupt();
0128:
0129: m_thread = null;
0130: }
0131:
0132: /**
0133: * Run the clean and report thread.
0134: */
0135: public void run() {
0136: // since we might be running while the component manager is still being created and populated, such as at server
0137: // startup, wait here for a complete component manager
0138: ComponentManager.waitTillConfigured();
0139:
0140: // loop till told to stop
0141: while ((!m_threadStop)
0142: && (!Thread.currentThread().isInterrupted())) {
0143: try {
0144: // process the queue of digest requests
0145: processQueue();
0146:
0147: // check for a digest mailing time
0148: sendDigests();
0149: } catch (Throwable e) {
0150: M_log.warn(": exception: ", e);
0151: }
0152:
0153: // take a small nap
0154: try {
0155: Thread.sleep(PERIOD);
0156: } catch (Throwable ignore) {
0157: }
0158: }
0159: }
0160:
0161: /**
0162: * Attempt to process all the queued digest requests. Ones that cannot be processed now will be returned to the queue.
0163: */
0164: protected void processQueue() {
0165: // setup a re-try queue
0166: List retry = new Vector();
0167:
0168: // grab the queue - any new stuff will be processed next time
0169: List queue = new Vector();
0170: synchronized (m_digestQueue) {
0171: queue.addAll(m_digestQueue);
0172: m_digestQueue.clear();
0173: }
0174:
0175: for (Iterator iQueue = queue.iterator(); iQueue.hasNext();) {
0176: DigestMessage message = (DigestMessage) iQueue.next();
0177: try {
0178: DigestEdit edit = edit(message.getTo());
0179: edit.add(message);
0180: commit(edit);
0181: // %%% could do this by pulling all for id from the queue in one commit -ggolden
0182: } catch (InUseException e) {
0183: // retry next time
0184: retry.add(message);
0185: }
0186: }
0187:
0188: // requeue the retrys
0189: if (retry.size() > 0) {
0190: synchronized (m_digestQueue) {
0191: m_digestQueue.addAll(retry);
0192: }
0193: }
0194: }
0195:
0196: /**
0197: * If it's time, send out any digested messages. Send once daily, after a certiain time of day (local time).
0198: */
0199: protected void sendDigests() {
0200: // compute the current period
0201: String curPeriod = computeRange(timeService().newTime())
0202: .toString();
0203:
0204: // if we are in a new period, start sending again
0205: if (!curPeriod.equals(m_lastSendPeriod)) {
0206: m_sendDigests = true;
0207:
0208: // remember this period for next check
0209: m_lastSendPeriod = curPeriod;
0210: }
0211:
0212: // if we are not sending, early out
0213: if (!m_sendDigests)
0214: return;
0215:
0216: if (M_log.isDebugEnabled())
0217: M_log.debug("checking for sending digests");
0218:
0219: // count send candidate digests
0220: int count = 0;
0221:
0222: // process each digest
0223: List digests = getDigests();
0224: for (Iterator iDigests = digests.iterator(); iDigests.hasNext();) {
0225: Digest digest = (Digest) iDigests.next();
0226:
0227: // see if this one has any prior periods
0228: List periods = digest.getPeriods();
0229: if (periods.size() == 0)
0230: continue;
0231:
0232: boolean found = false;
0233: for (Iterator iPeriods = periods.iterator(); iPeriods
0234: .hasNext();) {
0235: String period = (String) iPeriods.next();
0236: if (!curPeriod.equals(period)) {
0237: found = true;
0238: break;
0239: }
0240: }
0241: if (!found)
0242: continue;
0243:
0244: // this digest is a send candidate
0245: count++;
0246:
0247: // get a lock
0248: DigestEdit edit = null;
0249: try {
0250: boolean changed = false;
0251: edit = edit(digest.getId());
0252:
0253: // process each non-current period
0254: for (Iterator iPeriods = edit.getPeriods().iterator(); iPeriods
0255: .hasNext();) {
0256: String period = (String) iPeriods.next();
0257:
0258: // process if it's not the current period
0259: if (!curPeriod.equals(period)) {
0260: TimeRange periodRange = timeService()
0261: .newTimeRange(period);
0262: Time timeInPeriod = periodRange.firstTime();
0263:
0264: // any messages?
0265: List msgs = edit.getMessages(timeInPeriod);
0266: if (msgs.size() > 0) {
0267: // send this one
0268: send(edit.getId(), msgs, periodRange);
0269: }
0270:
0271: // clear this period
0272: edit.clear(timeInPeriod);
0273:
0274: changed = true;
0275: }
0276: }
0277:
0278: // commit, release the lock
0279: if (changed) {
0280: // delete it if empty
0281: if (edit.getPeriods().size() == 0) {
0282: remove(edit);
0283: } else {
0284: commit(edit);
0285: }
0286: edit = null;
0287: } else {
0288: cancel(edit);
0289: edit = null;
0290: }
0291: }
0292: // if in use, missing, whatever, skip on
0293: catch (Throwable any) {
0294: } finally {
0295: if (edit != null) {
0296: cancel(edit);
0297: edit = null;
0298: }
0299: }
0300:
0301: } // for (Iterator iDigests = digests.iterator(); iDigests.hasNext();)
0302:
0303: // if we didn't see any send candidates, we will stop sending till next period
0304: if (count == 0) {
0305: m_sendDigests = false;
0306: }
0307: }
0308:
0309: /**
0310: * Send a single digest message
0311: *
0312: * @param id
0313: * The use id to send the message to.
0314: * @param msgs
0315: * The List (DigestMessage) of message to digest.
0316: * @param period
0317: * The time period of the digested messages.
0318: */
0319: protected void send(String id, List msgs, TimeRange period) {
0320: // sanity check
0321: if (msgs.size() == 0)
0322: return;
0323:
0324: try {
0325: String to = userDirectoryService().getUser(id).getEmail();
0326:
0327: // if use has no email address we can't send it
0328: if ((to == null) || (to.length() == 0))
0329: return;
0330:
0331: String from = "postmaster@"
0332: + serverConfigurationService().getServerName();
0333: String subject = serverConfigurationService().getString(
0334: "ui.service", "Sakai")
0335: + " "
0336: + rb.getString("notif")
0337: + " "
0338: + period.firstTime().toStringLocalDate();
0339:
0340: StringBuffer body = new StringBuffer();
0341: body.append(subject);
0342: body.append("\n\n");
0343:
0344: // toc
0345: int count = 1;
0346: for (Iterator iMsgs = msgs.iterator(); iMsgs.hasNext();) {
0347: DigestMessage msg = (DigestMessage) iMsgs.next();
0348:
0349: body.append(Integer.toString(count));
0350: body.append(". ");
0351: body.append(msg.getSubject());
0352: body.append("\n");
0353: count++;
0354: }
0355: body.append("\n----------------------\n\n");
0356:
0357: // for each msg
0358: count = 1;
0359: for (Iterator iMsgs = msgs.iterator(); iMsgs.hasNext();) {
0360: DigestMessage msg = (DigestMessage) iMsgs.next();
0361:
0362: // repeate toc entry
0363: body.append(Integer.toString(count));
0364: body.append(". ");
0365: body.append(msg.getSubject());
0366: body.append("\n\n");
0367:
0368: // message body
0369: body.append(msg.getBody());
0370:
0371: body.append("\n----------------------\n\n");
0372: count++;
0373: }
0374:
0375: // tag
0376: body.append(rb.getString("thiaut")
0377: + " "
0378: + serverConfigurationService().getString(
0379: "ui.service", "Sakai") + " " + "("
0380: + serverConfigurationService().getServerUrl() + ")"
0381: + "\n" + rb.getString("youcan") + "\n");
0382:
0383: if (M_log.isDebugEnabled())
0384: M_log.debug(this + " sending digest email to: " + to);
0385:
0386: emailService().send(from, to, subject, body.toString(), to,
0387: null, null);
0388: } catch (Throwable any) {
0389: M_log.warn(".send: digest to: " + id + " not sent: "
0390: + any.toString());
0391: }
0392: }
0393:
0394: /**********************************************************************************************************************************************************************************************************************************************************
0395: * Abstractions, etc.
0396: *********************************************************************************************************************************************************************************************************************************************************/
0397:
0398: /**
0399: * Construct storage for this service.
0400: */
0401: protected abstract Storage newStorage();
0402:
0403: /**
0404: * Access the partial URL that forms the root of resource URLs.
0405: *
0406: * @param relative
0407: * if true, form within the access path only (i.e. starting with /content)
0408: * @return the partial URL that forms the root of resource URLs.
0409: */
0410: protected String getAccessPoint(boolean relative) {
0411: return (relative ? "" : serverConfigurationService()
0412: .getAccessUrl())
0413: + m_relativeAccessPoint;
0414: }
0415:
0416: /**
0417: * Access the internal reference which can be used to access the resource from within the system.
0418: *
0419: * @param id
0420: * The digest id string.
0421: * @return The the internal reference which can be used to access the resource from within the system.
0422: */
0423: public String digestReference(String id) {
0424: return getAccessPoint(true) + Entity.SEPARATOR + id;
0425: }
0426:
0427: /**
0428: * Access the digest id extracted from a digest reference.
0429: *
0430: * @param ref
0431: * The digest reference string.
0432: * @return The the digest id extracted from a digest reference.
0433: */
0434: protected String digestId(String ref) {
0435: String start = getAccessPoint(true) + Entity.SEPARATOR;
0436: int i = ref.indexOf(start);
0437: if (i == -1)
0438: return ref;
0439: String id = ref.substring(i + start.length());
0440: return id;
0441: }
0442:
0443: /**
0444: * Check security permission.
0445: *
0446: * @param lock
0447: * The lock id string.
0448: * @param resource
0449: * The resource reference string, or null if no resource is involved.
0450: * @return true if allowd, false if not
0451: */
0452: protected boolean unlockCheck(String lock, String resource) {
0453: if (!securityService().unlock(lock, resource)) {
0454: return false;
0455: }
0456:
0457: return true;
0458: }
0459:
0460: /**
0461: * Check security permission.
0462: *
0463: * @param lock
0464: * The lock id string.
0465: * @param resource
0466: * The resource reference string, or null if no resource is involved.
0467: * @exception PermissionException
0468: * Thrown if the user does not have access
0469: */
0470: protected void unlock(String lock, String resource)
0471: throws PermissionException {
0472: if (!unlockCheck(lock, resource)) {
0473: throw new PermissionException(sessionManager()
0474: .getCurrentSessionUserId(), lock, resource);
0475: }
0476: }
0477:
0478: /**********************************************************************************************************************************************************************************************************************************************************
0479: * Dependencies
0480: *********************************************************************************************************************************************************************************************************************************************************/
0481:
0482: /**
0483: * @return the TimeService collaborator.
0484: */
0485: protected abstract TimeService timeService();
0486:
0487: /**
0488: * @return the ServerConfigurationService collaborator.
0489: */
0490: protected abstract ServerConfigurationService serverConfigurationService();
0491:
0492: /**
0493: * @return the EmailService collaborator.
0494: */
0495: protected abstract EmailService emailService();
0496:
0497: /**
0498: * @return the EventTrackingService collaborator.
0499: */
0500: protected abstract EventTrackingService eventTrackingService();
0501:
0502: /**
0503: * @return the MemoryServiSecurityServicece collaborator.
0504: */
0505: protected abstract SecurityService securityService();
0506:
0507: /**
0508: * @return the UserDirectoryService collaborator.
0509: */
0510: protected abstract UserDirectoryService userDirectoryService();
0511:
0512: /**
0513: * @return the SessionManager collaborator.
0514: */
0515: protected abstract SessionManager sessionManager();
0516:
0517: /**********************************************************************************************************************************************************************************************************************************************************
0518: * Init and Destroy
0519: *********************************************************************************************************************************************************************************************************************************************************/
0520:
0521: /**
0522: * Final initialization, once all dependencies are set.
0523: */
0524: public void init() {
0525: m_relativeAccessPoint = REFERENCE_ROOT;
0526:
0527: // construct storage and read
0528: m_storage = newStorage();
0529: m_storage.open();
0530:
0531: // setup the queue
0532: m_digestQueue.clear();
0533:
0534: start();
0535:
0536: M_log.info("init()");
0537: }
0538:
0539: /**
0540: * Returns to uninitialized state.
0541: */
0542: public void destroy() {
0543: stop();
0544:
0545: m_storage.close();
0546: m_storage = null;
0547:
0548: if (m_digestQueue.size() > 0) {
0549: M_log.warn(".shutdown: with items in digest queue"); // %%%
0550: }
0551: m_digestQueue.clear();
0552:
0553: M_log.info("destroy()");
0554: }
0555:
0556: /**********************************************************************************************************************************************************************************************************************************************************
0557: * DigestService implementation
0558: *********************************************************************************************************************************************************************************************************************************************************/
0559:
0560: /**
0561: * @inheritDoc
0562: */
0563: public Digest getDigest(String id) throws IdUnusedException {
0564: Digest digest = findDigest(id);
0565: if (digest == null)
0566: throw new IdUnusedException(id);
0567:
0568: return digest;
0569: }
0570:
0571: /**
0572: * @inheritDoc
0573: */
0574: public List getDigests() {
0575: List digests = m_storage.getAll();
0576:
0577: return digests;
0578: }
0579:
0580: /**
0581: * @inheritDoc
0582: */
0583: public void digest(String to, String subject, String body) {
0584: DigestMessage message = new org.sakaiproject.email.impl.DigestMessage(
0585: to, subject, body);
0586:
0587: // queue this for digesting
0588: synchronized (m_digestQueue) {
0589: m_digestQueue.add(message);
0590: }
0591: }
0592:
0593: /**
0594: * @inheritDoc
0595: */
0596: public DigestEdit edit(String id) throws InUseException {
0597: // security
0598: // unlock(SECURE_EDIT_DIGEST, digestReference(id));
0599:
0600: // one add/edit at a time, please, to make sync. only one digest per user
0601: // TODO: I don't link sync... could just do the add and let it fail if it already exists -ggolden
0602: synchronized (m_storage) {
0603: // check for existance
0604: if (!m_storage.check(id)) {
0605: try {
0606: return add(id);
0607: } catch (IdUsedException e) {
0608: M_log.warn(".edit: from the add: " + e);
0609: }
0610: }
0611:
0612: // ignore the cache - get the user with a lock from the info store
0613: DigestEdit edit = m_storage.edit(id);
0614: if (edit == null)
0615: throw new InUseException(id);
0616:
0617: ((BaseDigest) edit).setEvent(SECURE_EDIT_DIGEST);
0618:
0619: return edit;
0620: }
0621: }
0622:
0623: /**
0624: * @inheritDoc
0625: */
0626: public void commit(DigestEdit edit) {
0627: // check for closed edit
0628: if (!edit.isActiveEdit()) {
0629: try {
0630: throw new Exception();
0631: } catch (Exception e) {
0632: M_log.warn(".commit(): closed DigestEdit", e);
0633: }
0634: return;
0635: }
0636:
0637: // update the properties
0638: // addLiveUpdateProperties(user.getPropertiesEdit());
0639:
0640: // complete the edit
0641: m_storage.commit(edit);
0642:
0643: // track it
0644: eventTrackingService().post(
0645: eventTrackingService().newEvent(
0646: ((BaseDigest) edit).getEvent(),
0647: edit.getReference(), true));
0648:
0649: // close the edit object
0650: ((BaseDigest) edit).closeEdit();
0651: }
0652:
0653: /**
0654: * @inheritDoc
0655: */
0656: public void cancel(DigestEdit edit) {
0657: // check for closed edit
0658: if (!edit.isActiveEdit()) {
0659: try {
0660: throw new Exception();
0661: } catch (Exception e) {
0662: M_log.warn(".cancel(): closed DigestEdit", e);
0663: }
0664: return;
0665: }
0666:
0667: // release the edit lock
0668: m_storage.cancel(edit);
0669:
0670: // close the edit object
0671: ((BaseDigest) edit).closeEdit();
0672: }
0673:
0674: /**
0675: * @inheritDoc
0676: */
0677: public void remove(DigestEdit edit) {
0678: // check for closed edit
0679: if (!edit.isActiveEdit()) {
0680: try {
0681: throw new Exception();
0682: } catch (Exception e) {
0683: M_log.warn(".remove(): closed DigestEdit", e);
0684: }
0685: return;
0686: }
0687:
0688: // complete the edit
0689: m_storage.remove(edit);
0690:
0691: // track it
0692: eventTrackingService().post(
0693: eventTrackingService().newEvent(SECURE_REMOVE_DIGEST,
0694: edit.getReference(), true));
0695:
0696: // close the edit object
0697: ((BaseDigest) edit).closeEdit();
0698: }
0699:
0700: /**
0701: * @inheritDoc
0702: */
0703: protected BaseDigest findDigest(String id) {
0704: BaseDigest digest = (BaseDigest) m_storage.get(id);
0705:
0706: return digest;
0707: }
0708:
0709: /**
0710: * @inheritDoc
0711: */
0712: public DigestEdit add(String id) throws IdUsedException {
0713: // check security (throws if not permitted)
0714: // unlock(SECURE_ADD_DIGEST, digestReference(id));
0715:
0716: // one add/edit at a time, please, to make sync. only one digest per user
0717: synchronized (m_storage) {
0718: // reserve a user with this id from the info store - if it's in use, this will return null
0719: DigestEdit edit = m_storage.put(id);
0720: if (edit == null) {
0721: throw new IdUsedException(id);
0722: }
0723:
0724: return edit;
0725: }
0726: }
0727:
0728: /**********************************************************************************************************************************************************************************************************************************************************
0729: * Digest implementation
0730: *********************************************************************************************************************************************************************************************************************************************************/
0731:
0732: public class BaseDigest implements DigestEdit,
0733: SessionBindingListener {
0734: /** The user id. */
0735: protected String m_id = null;
0736:
0737: /** The properties. */
0738: protected ResourcePropertiesEdit m_properties = null;
0739:
0740: /** The digest time ranges (Map TimeRange string to List of DigestMessage). */
0741: protected Map m_ranges = null;
0742:
0743: /**
0744: * Construct.
0745: *
0746: * @param id
0747: * The user id.
0748: */
0749: public BaseDigest(String id) {
0750: m_id = id;
0751:
0752: // setup for properties
0753: ResourcePropertiesEdit props = new BaseResourcePropertiesEdit();
0754: m_properties = props;
0755:
0756: // setup for ranges
0757: m_ranges = new Hashtable();
0758:
0759: // if the id is not null (a new user, rather than a reconstruction)
0760: // and not the anon (id == "") user,
0761: // add the automatic (live) properties
0762: // %%% if ((m_id != null) && (m_id.length() > 0)) addLiveProperties(props);
0763: }
0764:
0765: /**
0766: * Construct from another Digest object.
0767: *
0768: * @param user
0769: * The user object to use for values.
0770: */
0771: public BaseDigest(Digest digest) {
0772: setAll(digest);
0773: }
0774:
0775: /**
0776: * Construct from information in XML.
0777: *
0778: * @param el
0779: * The XML DOM Element definining the user.
0780: */
0781: public BaseDigest(Element el) {
0782: // setup for properties
0783: m_properties = new BaseResourcePropertiesEdit();
0784:
0785: // setup for ranges
0786: m_ranges = new Hashtable();
0787:
0788: m_id = el.getAttribute("id");
0789:
0790: // the children (properties, messages)
0791: NodeList children = el.getChildNodes();
0792: final int length = children.getLength();
0793: for (int i = 0; i < length; i++) {
0794: Node child = children.item(i);
0795: if (child.getNodeType() != Node.ELEMENT_NODE)
0796: continue;
0797: Element element = (Element) child;
0798:
0799: // look for properties
0800: if (element.getTagName().equals("properties")) {
0801: // re-create properties
0802: m_properties = new BaseResourcePropertiesEdit(
0803: element);
0804: }
0805:
0806: // look for a messages
0807: else if (element.getTagName().equals("messages")) {
0808: String period = element.getAttribute("period");
0809:
0810: // find the range
0811: List msgs = (List) m_ranges.get(period);
0812: if (msgs == null) {
0813: msgs = new Vector();
0814: m_ranges.put(period, msgs);
0815: }
0816:
0817: // do these children for messages
0818: NodeList msgChildren = element.getChildNodes();
0819: final int msgChildrenLen = msgChildren.getLength();
0820: for (int m = 0; m < msgChildrenLen; m++) {
0821: Node msgChild = msgChildren.item(m);
0822: if (msgChild.getNodeType() != Node.ELEMENT_NODE)
0823: continue;
0824: Element msgChildEl = (Element) msgChild;
0825:
0826: if (msgChildEl.getTagName().equals("message")) {
0827: String subject = Xml.decodeAttribute(
0828: msgChildEl, "subject");
0829: String body = Xml.decodeAttribute(
0830: msgChildEl, "body");
0831: msgs
0832: .add(new org.sakaiproject.email.impl.DigestMessage(
0833: m_id, subject, body));
0834: }
0835: }
0836: }
0837: }
0838: }
0839:
0840: /**
0841: * Take all values from this object.
0842: *
0843: * @param user
0844: * The user object to take values from.
0845: */
0846: protected void setAll(Digest digest) {
0847: m_id = digest.getId();
0848:
0849: m_properties = new BaseResourcePropertiesEdit();
0850: m_properties.addAll(digest.getProperties());
0851:
0852: m_ranges = new Hashtable();
0853: // %%% deep enough? -ggolden
0854: m_ranges.putAll(((BaseDigest) digest).m_ranges);
0855: }
0856:
0857: /**
0858: * @inheritDoc
0859: */
0860: public Element toXml(Document doc, Stack stack) {
0861: Element digest = doc.createElement("digest");
0862:
0863: if (stack.isEmpty()) {
0864: doc.appendChild(digest);
0865: } else {
0866: ((Element) stack.peek()).appendChild(digest);
0867: }
0868:
0869: stack.push(digest);
0870:
0871: digest.setAttribute("id", getId());
0872:
0873: // properties
0874: m_properties.toXml(doc, stack);
0875:
0876: // for each message range
0877: for (Iterator it = m_ranges.entrySet().iterator(); it
0878: .hasNext();) {
0879: Map.Entry entry = (Map.Entry) it.next();
0880:
0881: Element messages = doc.createElement("messages");
0882: digest.appendChild(messages);
0883: messages
0884: .setAttribute("period", (String) entry.getKey());
0885:
0886: // for each message
0887: for (Iterator iMsgs = ((List) entry.getValue())
0888: .iterator(); iMsgs.hasNext();) {
0889: DigestMessage msg = (DigestMessage) iMsgs.next();
0890:
0891: Element message = doc.createElement("message");
0892: messages.appendChild(message);
0893: Xml.encodeAttribute(message, "subject", msg
0894: .getSubject());
0895: Xml.encodeAttribute(message, "body", msg.getBody());
0896: }
0897: }
0898:
0899: stack.pop();
0900:
0901: return digest;
0902: }
0903:
0904: /**
0905: * @inheritDoc
0906: */
0907: public String getId() {
0908: if (m_id == null)
0909: return "";
0910: return m_id;
0911: }
0912:
0913: /**
0914: * @inheritDoc
0915: */
0916: public String getUrl() {
0917: return getAccessPoint(false) + m_id;
0918: }
0919:
0920: /**
0921: * @inheritDoc
0922: */
0923: public String getReference() {
0924: return digestReference(m_id);
0925: }
0926:
0927: /**
0928: * @inheritDoc
0929: */
0930: public String getReference(String rootProperty) {
0931: return getReference();
0932: }
0933:
0934: /**
0935: * @inheritDoc
0936: */
0937: public String getUrl(String rootProperty) {
0938: return getUrl();
0939: }
0940:
0941: /**
0942: * @inheritDoc
0943: */
0944: public ResourceProperties getProperties() {
0945: return m_properties;
0946: }
0947:
0948: /**
0949: * @inheritDoc
0950: */
0951: public List getMessages(Time period) {
0952: synchronized (m_ranges) {
0953: // find the range
0954: String range = computeRange(period).toString();
0955: List msgs = (List) m_ranges.get(range);
0956:
0957: List rv = new Vector();
0958: if (msgs != null) {
0959: rv.addAll(msgs);
0960: }
0961:
0962: return rv;
0963: }
0964: }
0965:
0966: /**
0967: * @inheritDoc
0968: */
0969: public List getPeriods() {
0970: synchronized (m_ranges) {
0971: List rv = new Vector();
0972: rv.addAll(m_ranges.keySet());
0973:
0974: return rv;
0975: }
0976: }
0977:
0978: /**
0979: * @inheritDoc
0980: */
0981: public boolean equals(Object obj) {
0982: if (!(obj instanceof Digest))
0983: return false;
0984: return ((Digest) obj).getId().equals(getId());
0985: }
0986:
0987: /**
0988: * @inheritDoc
0989: */
0990: public int hashCode() {
0991: return getId().hashCode();
0992: }
0993:
0994: /**
0995: * @inheritDoc
0996: */
0997: public int compareTo(Object obj) {
0998: if (!(obj instanceof Digest))
0999: throw new ClassCastException();
1000:
1001: // if the object are the same, say so
1002: if (obj == this )
1003: return 0;
1004:
1005: // sort based on (unique) id
1006: int compare = getId().compareTo(((Digest) obj).getId());
1007:
1008: return compare;
1009: }
1010:
1011: /******************************************************************************************************************************************************************************************************************************************************
1012: * Edit implementation
1013: *****************************************************************************************************************************************************************************************************************************************************/
1014:
1015: /** The event code for this edit. */
1016: protected String m_event = null;
1017:
1018: /** Active flag. */
1019: protected boolean m_active = false;
1020:
1021: /**
1022: * @inheritDoc
1023: */
1024: public void add(DigestMessage msg) {
1025: synchronized (m_ranges) {
1026: // find the current range
1027: String range = computeRange(timeService().newTime())
1028: .toString();
1029: List msgs = (List) m_ranges.get(range);
1030: if (msgs == null) {
1031: msgs = new Vector();
1032: m_ranges.put(range, msgs);
1033: }
1034: msgs.add(msg);
1035: }
1036: }
1037:
1038: /**
1039: * @inheritDoc
1040: */
1041: public void add(String to, String subject, String body) {
1042: DigestMessage msg = new org.sakaiproject.email.impl.DigestMessage(
1043: to, subject, body);
1044:
1045: synchronized (m_ranges) {
1046: // find the current range
1047: String range = computeRange(timeService().newTime())
1048: .toString();
1049: List msgs = (List) m_ranges.get(range);
1050: if (msgs == null) {
1051: msgs = new Vector();
1052: m_ranges.put(range, msgs);
1053: }
1054: msgs.add(msg);
1055: }
1056: }
1057:
1058: /**
1059: * @inheritDoc
1060: */
1061: public void clear(Time period) {
1062: synchronized (m_ranges) {
1063: // find the range
1064: String range = computeRange(period).toString();
1065: List msgs = (List) m_ranges.get(range);
1066: if (msgs != null) {
1067: m_ranges.remove(range);
1068: }
1069: }
1070: }
1071:
1072: /**
1073: * Clean up.
1074: */
1075: protected void finalize() {
1076: // catch the case where an edit was made but never resolved
1077: if (m_active) {
1078: cancel(this );
1079: }
1080: }
1081:
1082: /**
1083: * Take all values from this object.
1084: *
1085: * @param user
1086: * The user object to take values from.
1087: */
1088: protected void set(Digest digest) {
1089: setAll(digest);
1090: }
1091:
1092: /**
1093: * Access the event code for this edit.
1094: *
1095: * @return The event code for this edit.
1096: */
1097: protected String getEvent() {
1098: return m_event;
1099: }
1100:
1101: /**
1102: * Set the event code for this edit.
1103: *
1104: * @param event
1105: * The event code for this edit.
1106: */
1107: protected void setEvent(String event) {
1108: m_event = event;
1109: }
1110:
1111: /**
1112: * @inheritDoc
1113: */
1114: public ResourcePropertiesEdit getPropertiesEdit() {
1115: return m_properties;
1116: }
1117:
1118: /**
1119: * Enable editing.
1120: */
1121: protected void activate() {
1122: m_active = true;
1123: }
1124:
1125: /**
1126: * @inheritDoc
1127: */
1128: public boolean isActiveEdit() {
1129: return m_active;
1130: }
1131:
1132: /**
1133: * Close the edit object - it cannot be used after this.
1134: */
1135: protected void closeEdit() {
1136: m_active = false;
1137: }
1138:
1139: /******************************************************************************************************************************************************************************************************************************************************
1140: * SessionBindingListener implementation
1141: *****************************************************************************************************************************************************************************************************************************************************/
1142:
1143: /**
1144: * @inheritDoc
1145: */
1146: public void valueBound(SessionBindingEvent event) {
1147: }
1148:
1149: /**
1150: * @inheritDoc
1151: */
1152: public void valueUnbound(SessionBindingEvent event) {
1153: if (M_log.isDebugEnabled())
1154: M_log.debug(this + ".valueUnbound()");
1155:
1156: // catch the case where an edit was made but never resolved
1157: if (m_active) {
1158: cancel(this );
1159: }
1160: }
1161: }
1162:
1163: /**********************************************************************************************************************************************************************************************************************************************************
1164: * Storage
1165: *********************************************************************************************************************************************************************************************************************************************************/
1166:
1167: protected interface Storage {
1168: /**
1169: * Open.
1170: */
1171: public void open();
1172:
1173: /**
1174: * Close.
1175: */
1176: public void close();
1177:
1178: /**
1179: * Check if a digest by this id exists.
1180: *
1181: * @param id
1182: * The user id.
1183: * @return true if a digest for this id exists, false if not.
1184: */
1185: public boolean check(String id);
1186:
1187: /**
1188: * Get the digest with this id, or null if not found.
1189: *
1190: * @param id
1191: * The digest id.
1192: * @return The digest with this id, or null if not found.
1193: */
1194: public Digest get(String id);
1195:
1196: /**
1197: * Get all digests.
1198: *
1199: * @return The list of all digests.
1200: */
1201: public List getAll();
1202:
1203: /**
1204: * Add a new digest with this id.
1205: *
1206: * @param id
1207: * The digest id.
1208: * @return The locked Digest object with this id, or null if the id is in use.
1209: */
1210: public DigestEdit put(String id);
1211:
1212: /**
1213: * Get a lock on the digest with this id, or null if a lock cannot be gotten.
1214: *
1215: * @param id
1216: * The digest id.
1217: * @return The locked Digest with this id, or null if this records cannot be locked.
1218: */
1219: public DigestEdit edit(String id);
1220:
1221: /**
1222: * Commit the changes and release the lock.
1223: *
1224: * @param user
1225: * The edit to commit.
1226: */
1227: public void commit(DigestEdit edit);
1228:
1229: /**
1230: * Cancel the changes and release the lock.
1231: *
1232: * @param user
1233: * The edit to commit.
1234: */
1235: public void cancel(DigestEdit edit);
1236:
1237: /**
1238: * Remove this edit and release the lock.
1239: *
1240: * @param user
1241: * The edit to remove.
1242: */
1243: public void remove(DigestEdit edit);
1244: }
1245:
1246: /**********************************************************************************************************************************************************************************************************************************************************
1247: * StorageUser implementation (no container)
1248: *********************************************************************************************************************************************************************************************************************************************************/
1249:
1250: /**
1251: * @inheritDoc
1252: */
1253: public Entity newContainer(String ref) {
1254: return null;
1255: }
1256:
1257: /**
1258: * @inheritDoc
1259: */
1260: public Entity newContainer(Element element) {
1261: return null;
1262: }
1263:
1264: /**
1265: * @inheritDoc
1266: */
1267: public Entity newContainer(Entity other) {
1268: return null;
1269: }
1270:
1271: /**
1272: * @inheritDoc
1273: */
1274: public Entity newResource(Entity container, String id,
1275: Object[] others) {
1276: return new BaseDigest(id);
1277: }
1278:
1279: /**
1280: * @inheritDoc
1281: */
1282: public Entity newResource(Entity container, Element element) {
1283: return new BaseDigest(element);
1284: }
1285:
1286: /**
1287: * @inheritDoc
1288: */
1289: public Entity newResource(Entity container, Entity other) {
1290: return new BaseDigest((Digest) other);
1291: }
1292:
1293: /**
1294: * @inheritDoc
1295: */
1296: public Edit newContainerEdit(String ref) {
1297: return null;
1298: }
1299:
1300: /**
1301: * @inheritDoc
1302: */
1303: public Edit newContainerEdit(Element element) {
1304: return null;
1305: }
1306:
1307: /**
1308: * @inheritDoc
1309: */
1310: public Edit newContainerEdit(Entity other) {
1311: return null;
1312: }
1313:
1314: /**
1315: * @inheritDoc
1316: */
1317: public Edit newResourceEdit(Entity container, String id,
1318: Object[] others) {
1319: BaseDigest e = new BaseDigest(id);
1320: e.activate();
1321: return e;
1322: }
1323:
1324: /**
1325: * @inheritDoc
1326: */
1327: public Edit newResourceEdit(Entity container, Element element) {
1328: BaseDigest e = new BaseDigest(element);
1329: e.activate();
1330: return e;
1331: }
1332:
1333: /**
1334: * @inheritDoc
1335: */
1336: public Edit newResourceEdit(Entity container, Entity other) {
1337: BaseDigest e = new BaseDigest((Digest) other);
1338: e.activate();
1339: return e;
1340: }
1341:
1342: /**
1343: * @inheritDoc
1344: */
1345: public Object[] storageFields(Entity r) {
1346: return null;
1347: }
1348:
1349: /**
1350: * @inheritDoc
1351: */
1352: public boolean isDraft(Entity r) {
1353: return false;
1354: }
1355:
1356: /**
1357: * @inheritDoc
1358: */
1359: public String getOwnerId(Entity r) {
1360: return null;
1361: }
1362:
1363: /**
1364: * @inheritDoc
1365: */
1366: public Time getDate(Entity r) {
1367: return null;
1368: }
1369:
1370: /**
1371: * Compute a time range based on a specific time.
1372: *
1373: * @return The time range that encloses the specific time.
1374: */
1375: protected TimeRange computeRange(Time time) {
1376: // set the period to "today" (local!) from day start to next day start, not end inclusive
1377: TimeBreakdown brk = time.breakdownLocal();
1378: brk.setMs(0);
1379: brk.setSec(0);
1380: brk.setMin(0);
1381: brk.setHour(0);
1382: Time start = timeService().newTimeLocal(brk);
1383: Time end = timeService().newTime(
1384: start.getTime() + 24 * 60 * 60 * 1000);
1385: return timeService().newTimeRange(start, end, true, false);
1386: }
1387: }
|