001: /**
002: *
003: *
004: * @author Adrian Price
005: */package org.obe.spi.util;
006:
007: import org.obe.OBERuntimeException;
008: import org.obe.client.api.model.MIMETypes;
009: import org.obe.client.api.repository.RepositoryException;
010: import org.obe.spi.event.ApplicationEvent;
011: import org.obe.spi.runtime.BusinessCalendar;
012: import org.obe.spi.service.ApplicationEventBroker;
013: import org.obe.spi.service.ServerConfig;
014: import org.obe.spi.service.ServiceManager;
015: import org.obe.xpdl.model.misc.Duration;
016:
017: import java.util.Date;
018:
019: /**
020: * Provides utility methods for handling application events.
021: *
022: * @author Adrian Price
023: */
024: public class ApplicationEventUtil {
025: /**
026: * Handles the timeout for a temporal event subscription. The method
027: * decrements the subscription's event count. Then, for a recurring
028: * subscription that hasn't expired, it computes the time at which the next
029: * event should occur, according to whether the subscription is recoverable.
030: * If the subscription will have expired before the next occurrence or if
031: * its event count has reached 0, the subscription is cancelled.
032: * <p/>
033: * Finally, the listeners are notified asynchronously of the event
034: * occurrence.
035: *
036: * @param subscription The temporal event subscription.
037: * @param scheduledExecutionTime The time at which the timeout was scheduled
038: * to occur (not necessarily the time at which it <i>actually</i> occurred).
039: * @param svcMgr The service manager to use.
040: * @return For an unexpired recurring subscription, the time at which the
041: * next temporal event should occur, otherwise <code>null</code>.
042: */
043: public static Date handleTimeout(
044: ApplicationEventBroker.TemporalEventSubscription subscription,
045: Date scheduledExecutionTime, ServiceManager svcMgr) {
046:
047: // When to schedule the next temporal event occurrence.
048: Date next = null;
049:
050: // Whether to cancel the subscription after processing the timeout.
051: boolean cancel = true;
052:
053: // Reschedule recurring temporal subscription if appropriate.
054: boolean recoverable = subscription.isRecoverable();
055: if (subscription.decrementCount() != 0) {
056: try {
057: // Retrieve the business calendar to use for computations.
058: // TODO: figure out a better defaulting mechanism.
059: String calendarName = subscription.getCalendar();
060: if (calendarName == null)
061: calendarName = "default";
062: BusinessCalendar calendar = svcMgr.getCalendarFactory()
063: .findCalendar(calendarName);
064:
065: // Compute the next timeout for this recurring event. Skip
066: // stale intervening events if this is a non-recoverable
067: // subscription.
068: Duration interval = subscription.getInterval();
069: long earliest = System.currentTimeMillis()
070: - ServerConfig
071: .getMaximumTemporalEventTardiness();
072: next = scheduledExecutionTime;
073: do {
074: next = calendar.add(next, interval.getUnit(),
075: interval.getValue(), null);
076: } while (!recoverable && next.getTime() < earliest);
077:
078: // If the subscription would still be active at that time,
079: // schedule another timeout.
080: Date expiry = subscription.getExpiry();
081: if (expiry == null || expiry.after(next))
082: cancel = false;
083: else
084: next = null;
085: } catch (RepositoryException e) {
086: throw new OBERuntimeException(e);
087: }
088: }
089:
090: // Remember the event details required by listeners.
091: String eventType = subscription.getEventType();
092: String[] correlationKeys = subscription.getCorrelationKeys();
093:
094: // If the event subscription has expired, cancel it.
095: if (cancel)
096: subscription.cancel();
097:
098: // Finally, fire the event if it's recoverable or isn't too stale.
099: long earliest = System.currentTimeMillis()
100: - ServerConfig.getMaximumTemporalEventTardiness();
101: if (recoverable || scheduledExecutionTime.getTime() <= earliest) {
102: // We must enqueue the temporal event to be fired in a different
103: // thread because otherwise if the workflow engine called
104: // unsubscribe() this could result in a re-entrant call to cancel(),
105: // after which no further access to the subscription is permitted.
106: ApplicationEvent event = new ApplicationEvent(null,
107: scheduledExecutionTime, eventType, null,
108: MIMETypes.JAVA_OBJECT, Date.class.getName(), null,
109: recoverable ? null : new Date(earliest));
110: svcMgr.getAsyncManager().asyncRequest(
111: new AsyncFireApplicationEvent(event,
112: correlationKeys));
113: }
114:
115: return next;
116: }
117:
118: /**
119: * Validates the parameters for an application event subscription.
120: *
121: * @param eventType
122: * @param effective
123: * @param expiry
124: * @param count
125: * @param correlationKeys
126: * @throws IllegalArgumentException if any of the parameter values are
127: * incorrect.
128: */
129: public static void validateSubscription(String eventType,
130: Date effective, Date expiry, int count,
131: String[] correlationKeys) {
132:
133: if (eventType == null)
134: throw new IllegalArgumentException(
135: "eventType cannot be null");
136: if (effective != null && expiry != null
137: && expiry.before(effective)) {
138: throw new IllegalArgumentException(
139: "expiry date cannot precede effective date");
140: }
141: if (count == 0 || count < -1) {
142: throw new IllegalArgumentException(
143: "Illegal count value; use -1 for unlimited recurrence");
144: }
145: if (count == 1 && expiry != null) {
146: throw new IllegalArgumentException(
147: "Non-null expiry requires a count other than unity");
148: }
149: if (correlationKeys == null || correlationKeys.length == 0) {
150: throw new IllegalArgumentException(
151: "correlationKeys cannot be null or empty");
152: }
153: for (int i = 0; i < correlationKeys.length; i++) {
154: if (correlationKeys[i] == null) {
155: throw new IllegalArgumentException(
156: "correlationKeys cannot contain null elements");
157: }
158: }
159: }
160:
161: /**
162: * Validates the parameters for a temporal event subscription.
163: *
164: * @param eventType
165: * @param effective
166: * @param expiry
167: * @param count
168: * @param interval
169: * @param correlationKeys
170: * @throws IllegalArgumentException if any of the parameter values are
171: * incorrect.
172: */
173: public static void validateSubscription(String eventType,
174: Date effective, Date expiry, int count, Duration interval,
175: String[] correlationKeys) {
176:
177: validateSubscription(eventType, effective, expiry, count,
178: correlationKeys);
179: if (effective == null) {
180: throw new IllegalArgumentException(
181: "effective date cannot be null");
182: }
183: if ((count != 1 || expiry != null) && interval == null) {
184: throw new IllegalArgumentException(
185: "Recurring temporal subscription requires an interval");
186: }
187: }
188:
189: /**
190: * Converts the elements of an array to strings.
191: *
192: * @param array An array of objects.
193: * @return The input <code>array</code>, with all elements replaced by the
194: * result of calling <code>toString()</code> on each.
195: */
196: public static Object[] toStrings(Object[] array) {
197: if (array != null) {
198: for (int i = 0; i < array.length; i++) {
199: Object o = array[i];
200: if (o != null)
201: array[i] = o.toString();
202: }
203: }
204: return array;
205: }
206:
207: private ApplicationEventUtil() {
208: }
209: }
|