001: /*
002: * Event.java
003: *
004: * Version: $Revision: 2074 $
005: *
006: * Date: $Date: 2007-07-19 14:40:11 -0500 (Thu, 19 Jul 2007) $
007: *
008: * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */
040:
041: package org.dspace.event;
042:
043: import java.io.Serializable;
044: import java.sql.SQLException;
045: import java.util.BitSet;
046: import java.util.HashMap;
047: import java.util.Iterator;
048: import java.util.List;
049: import java.util.Map;
050:
051: import org.apache.log4j.Logger;
052: import org.dspace.content.DSpaceObject;
053: import org.dspace.core.Constants;
054: import org.dspace.core.Context;
055:
056: /**
057: * An Event object represents a single action that changed one object in the
058: * DSpace data model. An "atomic" action at the application or business-logic
059: * API level may spawn many of these events.
060: * <p>
061: * This class includes tools to help set and use the contents of the event. Note
062: * that it describes DSpace data object types in two ways: by the type
063: * identifiers in the Constants class, and also by an Event-specific bitmask
064: * (used by its internal filters). All public API calls use the Constants
065: * version of the data model types.
066: * <p>
067: * Note that the type of the event itself is actually descriptive of the
068: * <em>action</em> it performs: ADD, MODIFY, etc. The most significant
069: * elements of the event are:
070: * <p>
071: * <br> - (Action) Type <br> - Subject -- DSpace object to which the action
072: * applies, e.g. the Collection to which an ADD adds a member. <br> - Object --
073: * optional, when present it is the other object effected by an action, e.g. the
074: * Item ADDed to a Collection by an ADD. <br> - detail -- a textual summary of
075: * what changed, content and its significance varies by the combination of
076: * action and subject type. <br> - timestamp -- exact millisecond timestamp at
077: * which event was logged.
078: *
079: * @version $Revision: 2074 $
080: */
081: public class Event implements Serializable {
082: /** ---------- Constants ------------- * */
083:
084: /** Event (Action) types */
085: public static final int CREATE = 1 << 0; // create new object
086:
087: public static final int MODIFY = 1 << 1; // modify object
088:
089: public static final int MODIFY_METADATA = 1 << 2; // modify object
090:
091: public static final int ADD = 1 << 3; // add content to container
092:
093: public static final int REMOVE = 1 << 4; // remove content from container
094:
095: public static final int DELETE = 1 << 5; // destroy object
096:
097: /** Index of filter parts in their array: */
098: public static final int SUBJECT_MASK = 0; // mask of subject types
099:
100: public static final int EVENT_MASK = 1; // mask of event type
101:
102: // XXX NOTE: keep this up to date with any changes to event (action) types.
103: private static final String eventTypeText[] = { "CREATE", "MODIFY",
104: "MODIFY_METADATA", "ADD", "REMOVE", "DELETE" };
105:
106: /** XXX NOTE: These constants must be kept synchronized * */
107: /** XXX NOTE: with ALL_OBJECTS_MASK *AND* objTypeToMask hash * */
108: private static final int NONE = 0;
109:
110: private static final int BITSTREAM = 1 << Constants.BITSTREAM; // 0
111:
112: private static final int BUNDLE = 1 << Constants.BUNDLE; // 1
113:
114: private static final int ITEM = 1 << Constants.ITEM; // 2
115:
116: private static final int COLLECTION = 1 << Constants.COLLECTION; // 3
117:
118: private static final int COMMUNITY = 1 << Constants.COMMUNITY; // 4
119:
120: private static final int SITE = 1 << Constants.SITE; // 5
121:
122: private static final int GROUP = 1 << Constants.GROUP; // 6
123:
124: private static final int EPERSON = 1 << Constants.EPERSON; // 7
125:
126: private static final int ALL_OBJECTS_MASK = BITSTREAM | BUNDLE
127: | ITEM | COLLECTION | COMMUNITY | SITE | GROUP | EPERSON;
128:
129: private static Map<Integer, Integer> objTypeToMask = new HashMap<Integer, Integer>();
130:
131: private static Map<Integer, Integer> objMaskToType = new HashMap<Integer, Integer>();
132: static {
133: objTypeToMask.put(new Integer(Constants.BITSTREAM),
134: new Integer(BITSTREAM));
135: objMaskToType.put(new Integer(BITSTREAM), new Integer(
136: Constants.BITSTREAM));
137:
138: objTypeToMask.put(new Integer(Constants.BUNDLE), new Integer(
139: BUNDLE));
140: objMaskToType.put(new Integer(BUNDLE), new Integer(
141: Constants.BUNDLE));
142:
143: objTypeToMask.put(new Integer(Constants.ITEM),
144: new Integer(ITEM));
145: objMaskToType.put(new Integer(ITEM),
146: new Integer(Constants.ITEM));
147:
148: objTypeToMask.put(new Integer(Constants.COLLECTION),
149: new Integer(COLLECTION));
150: objMaskToType.put(new Integer(COLLECTION), new Integer(
151: Constants.COLLECTION));
152:
153: objTypeToMask.put(new Integer(Constants.COMMUNITY),
154: new Integer(COMMUNITY));
155: objMaskToType.put(new Integer(COMMUNITY), new Integer(
156: Constants.COMMUNITY));
157:
158: objTypeToMask.put(new Integer(Constants.SITE),
159: new Integer(SITE));
160: objMaskToType.put(new Integer(SITE),
161: new Integer(Constants.SITE));
162:
163: objTypeToMask.put(new Integer(Constants.GROUP), new Integer(
164: GROUP));
165: objMaskToType.put(new Integer(GROUP), new Integer(
166: Constants.GROUP));
167:
168: objTypeToMask.put(new Integer(Constants.EPERSON), new Integer(
169: EPERSON));
170: objMaskToType.put(new Integer(EPERSON), new Integer(
171: Constants.EPERSON));
172: }
173:
174: /** ---------- Event Fields ------------- * */
175:
176: /** identifier of Dispatcher that created this event (hash of its name) */
177: private int dispatcher;
178:
179: /** event (action) type - above enumeration */
180: private int eventType;
181:
182: /** object-type of SUBJECT - see above enumeration */
183: private int subjectType;
184:
185: /** content model identifier */
186: private int subjectID;
187:
188: /** object-type of SUBJECT - see above enumeration */
189: private int objectType = NONE;
190:
191: /** content model identifier */
192: private int objectID = -1;
193:
194: /** timestamp */
195: private long timeStamp;
196:
197: /** "detail" - arbitrary field for relevant detail, */
198: /** e.g. former handle for DELETE event since obj is no longer available. */
199: /**
200: * FIXME This field is not a complete view of the DSpaceObject that was
201: * modified. Providing these objects to the consumer (e.g. by storing
202: * lifecycle versions of the changed objects in the context) would provide
203: * for more complex consumer abilities that are beyond our purview.
204: */
205: private String detail;
206:
207: /** unique key to bind together events from one context's transaction */
208: private String transactionID;
209:
210: /** identity of authenticated user, i.e. context.getCurrentUser() */
211: /** only needed in the event for marshalling for asynch event messages */
212: private int currentUser = -1;
213:
214: /** copy of context's "extraLogInfo" filed, used only for */
215: /** marshalling for asynch event messages */
216: private String extraLogInfo = null;
217:
218: private BitSet consumedBy = new BitSet();
219:
220: /** log4j category */
221: private static Logger log = Logger.getLogger(Event.class);
222:
223: /**
224: * Constructor.
225: *
226: * @param eventType
227: * action type, e.g. Event.ADD
228: * @param subjectType
229: * DSpace Object Type of subject e.g. Constants.ITEM.
230: * @param subjectID
231: * database ID of subject instance.
232: * @param detail
233: * detail information that depends on context.
234: */
235: public Event(int eventType, int subjectType, int subjectID,
236: String detail) {
237: this .eventType = eventType;
238: this .subjectType = coreTypeToMask(subjectType);
239: this .subjectID = subjectID;
240: timeStamp = System.currentTimeMillis();
241: this .detail = detail;
242: }
243:
244: /**
245: * Constructor.
246: *
247: * @param eventType
248: * action type, e.g. Event.ADD
249: * @param subjectType
250: * DSpace Object Type of subject e.g. Constants.ITEM.
251: * @param subjectID
252: * database ID of subject instance.
253: * @param objectType
254: * DSpace Object Type of object e.g. Constants.BUNDLE.
255: * @param objectID
256: * database ID of object instance.
257: * @param detail
258: * detail information that depends on context.
259: * @param
260: */
261: public Event(int eventType, int subjectType, int subjectID,
262: int objectType, int objectID, String detail) {
263: this .eventType = eventType;
264: this .subjectType = coreTypeToMask(subjectType);
265: this .subjectID = subjectID;
266: this .objectType = coreTypeToMask(objectType);
267: this .objectID = objectID;
268: timeStamp = System.currentTimeMillis();
269: this .detail = detail;
270: }
271:
272: /**
273: * Compare two events. Ignore any difference in the timestamps. Also ignore
274: * transactionID since that is not always set initially.
275: *
276: * @param other
277: * the event to compare this one to
278: * @returns true if events are "equal", false otherwise.
279: */
280: public boolean equals(Event other) {
281: return (this .detail == null ? other.detail == null
282: : this .detail.equals(other.detail))
283: && this .eventType == other.eventType
284: && this .subjectType == other.subjectType
285: && this .subjectID == other.subjectID
286: && this .objectType == other.objectType
287: && this .objectID == other.objectID;
288: }
289:
290: /**
291: * Set the identifier of the dispatcher that first processed this event.
292: *
293: * @param id
294: * the unique (hash code) value characteristic of the dispatcher.
295: */
296: public void setDispatcher(int id) {
297: dispatcher = id;
298: }
299:
300: // translate a "core.Constants" object type value to local bitmask value.
301: private static int coreTypeToMask(int core) {
302: Integer mask = (Integer) objTypeToMask.get(new Integer(core));
303: if (mask == null)
304: return -1;
305: else
306: return mask.intValue();
307: }
308:
309: // translate bitmask object-type to "core.Constants" object type.
310: private static int maskTypeToCore(int mask) {
311: Integer core = (Integer) objMaskToType.get(new Integer(mask));
312: if (core == null)
313: return -1;
314: else
315: return core.intValue();
316: }
317:
318: /**
319: * Get the DSpace object which is the "object" of an event.
320: *
321: * @returns DSpaceObject or null if none can be found or no object was set.
322: */
323: public DSpaceObject getObject(Context context) throws SQLException {
324: int type = getObjectType();
325: int id = getObjectID();
326: if (type < 0 || id < 0)
327: return null;
328: else
329: return DSpaceObject.find(context, type, id);
330: }
331:
332: /**
333: * Syntactic sugar to get the DSpace object which is the "subject" of an
334: * event.
335: *
336: * @returns DSpaceObject or null if none can be found.
337: */
338: public DSpaceObject getSubject(Context context) throws SQLException {
339: return DSpaceObject.find(context, getSubjectType(),
340: getSubjectID());
341: }
342:
343: /**
344: * @returns database ID of subject of this event.
345: */
346: public int getSubjectID() {
347: return subjectID;
348: }
349:
350: /**
351: * @returns database ID of object of this event, or -1 if none was set.
352: */
353: public int getObjectID() {
354: return objectID;
355: }
356:
357: /**
358: * @returns type number (e.g. Constants.ITEM) of subject of this event.
359: */
360: public int getSubjectType() {
361: return maskTypeToCore(subjectType);
362: }
363:
364: /**
365: * @returns type number (e.g. Constants.ITEM) of object of this event, or -1
366: * if none was set.
367: */
368: public int getObjectType() {
369: return maskTypeToCore(objectType);
370: }
371:
372: /**
373: * @returns type of subject of this event as a String, e.g. for logging.
374: */
375: public String getSubjectTypeAsString() {
376: int i = log2(subjectType);
377: if (i >= 0 && i < Constants.typeText.length)
378: return Constants.typeText[i];
379: else
380: return "(Unknown)";
381: }
382:
383: /**
384: * @returns type of object of this event as a String, e.g. for logging.
385: */
386: public String getObjectTypeAsString() {
387: int i = log2(objectType);
388: if (i >= 0 && i < Constants.typeText.length)
389: return Constants.typeText[i];
390: else
391: return "(Unknown)";
392: }
393:
394: /**
395: * Translate a textual DSpace Object type name into an event subject-type
396: * mask. NOTE: This returns a BIT-MASK, not a numeric type value; the mask
397: * is only used within the event system.
398: *
399: * @param s
400: * text name of object type.
401: * @returns numeric value of object type or 0 for error.
402: */
403: public static int parseObjectType(String s) {
404: if (s.equals("*") | s.equalsIgnoreCase("all"))
405: return ALL_OBJECTS_MASK;
406: else {
407: int id = Constants.getTypeID(s.toUpperCase());
408: if (id >= 0)
409: return 1 << id;
410: }
411: return 0;
412: }
413:
414: /**
415: * @returns event-type (i.e. action) this event, one of the masks like
416: * Event.ADD defined above.
417: */
418: public int getEventType() {
419: return eventType;
420: }
421:
422: /**
423: * Get the text name of event (action) type.
424: *
425: * @returns event-type (i.e. action) this event as a String, e.g. for
426: * logging.
427: */
428: public String getEventTypeAsString() {
429: int i = log2(eventType);
430: if (i >= 0 && i < eventTypeText.length)
431: return eventTypeText[i];
432: else
433: return "(Unknown)";
434: }
435:
436: /**
437: * Interpret named event type.
438: *
439: * @param text
440: * name of event type.
441: * @returns numeric value of event type or 0 for error.
442: */
443: public static int parseEventType(String s) {
444: if (s.equals("*") | s.equalsIgnoreCase("all")) {
445: int result = 0;
446: for (int i = 0; i < eventTypeText.length; ++i)
447: result |= (1 << i);
448: return result;
449: }
450:
451: for (int i = 0; i < eventTypeText.length; ++i)
452: if (eventTypeText[i].equalsIgnoreCase(s))
453: return 1 << i;
454: return 0;
455: }
456:
457: /**
458: * @returns timestamp at which event occurred, as a count of milliseconds
459: * since the epoch (standard Java format).
460: */
461: public long getTimeStamp() {
462: return timeStamp;
463: }
464:
465: /**
466: * @returns hashcode identifier of name of Dispatcher which first dispatched
467: * this event. (Needed by asynch dispatch code.)
468: */
469: public int getDispatcher() {
470: return dispatcher;
471: }
472:
473: /**
474: * @returns value of detail element of the event.
475: */
476: public String getDetail() {
477: return detail;
478: }
479:
480: /**
481: * @returns value of transactionID element of the event.
482: */
483: public String getTransactionID() {
484: return transactionID;
485: }
486:
487: /**
488: * Sets value of transactionID element of the event.
489: *
490: * @param tid
491: * new value of transactionID.
492: */
493: public void setTransactionID(String tid) {
494: transactionID = tid;
495: }
496:
497: public void setCurrentUser(int uid) {
498: currentUser = uid;
499: }
500:
501: public int getCurrentUser() {
502: return currentUser;
503: }
504:
505: public void setExtraLogInfo(String info) {
506: extraLogInfo = info;
507: }
508:
509: public String getExtraLogInfo() {
510: return extraLogInfo;
511: }
512:
513: /**
514: * @param filters
515: * list of filter masks; each one is an Array of two ints.
516: * @returns true if this event would be passed through the given filter
517: * list.
518: */
519: public boolean pass(List filters) {
520: boolean result = false;
521:
522: for (Iterator fi = filters.iterator(); fi.hasNext();) {
523: int filter[] = (int[]) fi.next();
524: if ((subjectType & filter[SUBJECT_MASK]) != 0
525: && (eventType & filter[EVENT_MASK]) != 0)
526: result = true;
527: }
528:
529: if (log.isDebugEnabled())
530: log.debug("Filtering event: " + "eventType="
531: + String.valueOf(eventType) + ", subjectType="
532: + String.valueOf(subjectType) + ", result="
533: + String.valueOf(result));
534:
535: return result;
536: }
537:
538: // dumb integer "log base 2", returns -1 if there are no 1's in number.
539: private static int log2(int n) {
540: for (int i = 0; i < 32; ++i)
541: if (n == 1)
542: return i;
543: else
544: n = n >> 1;
545: return -1;
546: }
547:
548: /**
549: * Keeps track of which consumers the event has been consumed by. Should be
550: * called by a dispatcher when calling consume(Context ctx, String name,
551: * Event event) on an event.
552: *
553: * @param consumerName
554: */
555: public void setBitSet(String consumerName) {
556: consumedBy.set(EventManager.getConsumerIndex(consumerName));
557:
558: }
559:
560: public BitSet getBitSet() {
561: return consumedBy;
562: }
563:
564: /**
565: * @returns Detailed string representation of contents of this event, to
566: * help in logging and debugging.
567: */
568: public String toString() {
569: return "org.dspace.event.Event(eventType="
570: + this .getEventTypeAsString()
571: + ", SubjectType="
572: + this .getSubjectTypeAsString()
573: + ", SubjectID="
574: + String.valueOf(subjectID)
575: + ", ObjectType="
576: + this .getObjectTypeAsString()
577: + ", ObjectID="
578: + String.valueOf(objectID)
579: + ", TimeStamp="
580: + String.valueOf(timeStamp)
581: + ", dispatcher="
582: + String.valueOf(dispatcher)
583: + ", detail="
584: + (detail == null ? "[null]" : "\"" + detail + "\"")
585: + ", transactionID="
586: + (transactionID == null ? "[null]" : "\""
587: + transactionID + "\"") + ")";
588: }
589: }
|