001: /*
002: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License version
007: * 2 only, as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful, but
010: * WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * General Public License version 2 for more details (a copy is
013: * included at /legal/license.txt).
014: *
015: * You should have received a copy of the GNU General Public License
016: * version 2 along with this work; if not, write to the Free Software
017: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
018: * 02110-1301 USA
019: *
020: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
021: * Clara, CA 95054 or visit www.sun.com if you need additional
022: * information or have any questions.
023: */
024:
025: package com.sun.midp.jump.push.executive;
026:
027: import com.sun.midp.jump.push.executive.persistence.Store;
028: import java.io.IOException;
029: import java.util.Date;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.Map;
033: import java.util.Timer;
034: import java.util.TimerTask;
035: import javax.microedition.io.ConnectionNotFoundException;
036:
037: /**
038: * Controller that manages alarms.
039: *
040: * <p>
041: * IMPORTANT_NOTE: As this class uses <code>Store</code> to keep
042: * alarms data in persistent store, the clients of this class must ensure
043: * that the underlying content store can be exclusively locked when public
044: * methods of this class get invoked.
045: * <p>
046: */
047: final class AlarmController {
048:
049: /** Timer to track alarms. */
050: private final Timer timer;
051:
052: /** Map of registered alarms. */
053: private final Map alarms;
054:
055: /** Store to save alarm info. */
056: private final Store store;
057:
058: /** Lifecycle adapter implementation. */
059: private final LifecycleAdapter lifecycleAdapter;
060:
061: /**
062: * Constructs an alarm controller.
063: *
064: * <p>
065: * NOTE: both <code>store</code> and <code>lifecycleAdapter</code>
066: * MUST be not <code>null</code>. There is no checks and passing
067: * <code>null</code> leads to undefined behaviour.
068: * </p>
069: *
070: * @param store persistent store to save alarm info into
071: * (cannot be <code>null</code>)
072: *
073: * @param lifecycleAdapter adapter to launch <code>MIDlet</code>
074: * (cannot be <code>null</code>)
075: */
076: public AlarmController(final Store store,
077: final LifecycleAdapter lifecycleAdapter) {
078: this .timer = new Timer();
079: this .alarms = new HashMap();
080: this .store = store;
081: this .lifecycleAdapter = lifecycleAdapter;
082:
083: readAlarmsFromStore();
084: }
085:
086: /**
087: * Reads alarms from the persistent store and registers them.
088: */
089: private synchronized void readAlarmsFromStore() {
090: store.listAlarms(new Store.AlarmsConsumer() {
091: public void consume(final int midletSuiteID,
092: final Map suiteAlarms) {
093: for (Iterator it = suiteAlarms.entrySet().iterator(); it
094: .hasNext();) {
095: final Map.Entry entry = (Map.Entry) it.next();
096: final String midlet = (String) entry.getKey();
097: final Long time = (Long) entry.getValue();
098: scheduleAlarm(new MIDPApp(midletSuiteID, midlet),
099: time.longValue());
100: }
101: }
102: });
103: }
104:
105: /**
106: * Registers an alarm.
107: *
108: * <p>
109: * NOTE: <code>midletSuiteID</code> parameter should refer to a valid
110: * <code>MIDlet</code> suite and <code>midlet</code> should refer to
111: * valid <code>MIDlet</code> from the given suite. <code>timer</code>
112: * parameters is the same as for corresponding <code>Date</code>
113: * constructor. No checks are performed and no guarantees are
114: * given if parameters are invalid.
115: * </p>
116: *
117: * @param midletSuiteID <code>MIDlet suite</code> ID
118: * @param midlet <code>MIDlet</code> class name
119: * @param time alarm time
120: *
121: * @throws ConnectionNotFoundException if for any reason alarm cannot be
122: * registered
123: *
124: * @return previous alarm time or 0 if none
125: */
126: public synchronized long registerAlarm(final int midletSuiteID,
127: final String midlet, final long time)
128: throws ConnectionNotFoundException {
129: final MIDPApp midpApp = new MIDPApp(midletSuiteID, midlet);
130:
131: final AlarmTask oldTask = (AlarmTask) alarms.get(midpApp);
132: long oldTime = 0L;
133: if (oldTask != null) {
134: oldTime = oldTask.scheduledExecutionTime();
135: oldTask.cancel(); // Safe to ignore return
136: }
137:
138: try {
139: store.addAlarm(midletSuiteID, midlet, time);
140: } catch (IOException ioe) {
141: /*
142: * RFC: looks like optimal strategy, but we might simply ignore it
143: * (cf. Irbis push_server.c)
144: */
145: throw new ConnectionNotFoundException();
146: }
147:
148: scheduleAlarm(midpApp, time);
149:
150: return oldTime;
151: }
152:
153: /**
154: * Removes alarms for the given suite.
155: *
156: * <p>
157: * NOTE: <code>midletSuiteID</code> must refer to valid installed
158: * <code>MIDlet</code> suite. However, it might refer to the
159: * suite without alarms.
160: * </p>
161: *
162: * @param midletSuiteID ID of the suite to remove alarms for
163: */
164: public synchronized void removeSuiteAlarms(final int midletSuiteID) {
165: for (Iterator it = alarms.entrySet().iterator(); it.hasNext();) {
166: final Map.Entry entry = (Map.Entry) it.next();
167: final MIDPApp midpApp = (MIDPApp) entry.getKey();
168: if (midpApp.midletSuiteID == midletSuiteID) {
169: // No need to care about retval
170: ((AlarmTask) entry.getValue()).cancel();
171: removeAlarmFromStore(midpApp);
172: it.remove();
173: }
174: }
175: }
176:
177: /**
178: * Disposes an alarm controller.
179: *
180: * <p>
181: * NOTE: This method is needed as <code>Timer</code> creates
182: * non daemon thread which would prevent the app from exit.
183: * </p>
184: *
185: * <p>
186: * NOTE: after <code>AlarmController</code> is disposed, attempt to perform
187: * any alarms related activity on it leads to undefined behaviour.
188: * </p>
189: */
190: public synchronized void dispose() {
191: timer.cancel();
192: for (Iterator it = alarms.values().iterator(); it.hasNext();) {
193: final AlarmTask task = (AlarmTask) it.next();
194: task.cancel();
195: }
196: alarms.clear();
197: }
198:
199: /**
200: * Special class that supports guaranteed canceling of TimerTasks.
201: */
202: private class AlarmTask extends TimerTask {
203: /** <code>MIDlet</code> to run. */
204: final MIDPApp midpApp;
205:
206: /** Cancelation status. */
207: boolean cancelled = false;
208:
209: /**
210: * Creates a new instance, originally not cancelled.
211: *
212: * @param midpApp <code>MIDlet</code> to create task for
213: */
214: AlarmTask(final MIDPApp midpApp) {
215: this .midpApp = midpApp;
216: }
217:
218: /** {@inheritDoc} */
219: public void run() {
220: synchronized (AlarmController.this ) {
221: if (cancelled) {
222: return;
223: }
224:
225: try {
226: lifecycleAdapter.launchMidlet(
227: midpApp.midletSuiteID, midpApp.midlet);
228: removeAlarm(midpApp);
229: } catch (Exception ex) {
230: /*
231: * IMPL_NOTE: need to handle _all_ the exceptions
232: * as otherwise Timer thread gets stuck and alarms
233: * cannot be scheduled anymore.
234: */
235: /* TBD: uncomment when logging can be disabled
236: * (not to interfer with unittests)
237: logError(
238: "Failed to launch \"" + midpApp.midlet + "\"" +
239: " (suite ID: " + midpApp.midletSuiteID + "): " +
240: ex);
241: */
242: }
243: }
244: }
245:
246: /** {@inheritDoc} */
247: public boolean cancel() {
248: cancelled = true;
249: return super .cancel();
250: }
251: }
252:
253: /**
254: * Scheduleds an alarm.
255: *
256: * @param midpApp application to register alarm for
257: * @param time alarm time
258: */
259: private void scheduleAlarm(final MIDPApp midpApp, final long time) {
260: final Date date = new Date(time);
261: final AlarmTask newTask = new AlarmTask(midpApp);
262: alarms.put(midpApp, newTask);
263: timer.schedule(newTask, date);
264: /*
265: * RFC: according to <code>Timer</code> spec, <quote>if the time is in
266: * the past, the task is scheduled for immediate execution</quote>.
267: * I hope it's MIDP complaint
268: */
269: }
270:
271: /**
272: * Removes an alarm associated info.
273: *
274: * @param midpApp application to remove alarm for
275: */
276: private void removeAlarm(final MIDPApp midpApp) {
277: alarms.remove(midpApp);
278: removeAlarmFromStore(midpApp);
279: }
280:
281: /**
282: * Removes an alarm from persistent store.
283: *
284: * @param midpApp application to remove alarm for
285: */
286: private void removeAlarmFromStore(final MIDPApp midpApp) {
287: try {
288: store.removeAlarm(midpApp.midletSuiteID, midpApp.midlet);
289: } catch (IOException ioex) {
290: logError("Failed to remove alarm info from the persistent store: "
291: + ioex);
292: }
293: }
294:
295: /**
296: * Logs error message.
297: *
298: * <p>
299: * TBD: common logging
300: * </p>
301: *
302: * @param message message to log
303: */
304: private static void logError(final String message) {
305: System.err.println("ERROR [" + AlarmController.class.getName()
306: + "]: " + message);
307: }
308: }
|