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.push.gcf.ConnectionReservation;
028: import com.sun.midp.push.gcf.DataAvailableListener;
029: import com.sun.midp.push.gcf.ReservationDescriptor;
030: import com.sun.midp.jump.push.executive.persistence.Store;
031: import com.sun.midp.push.gcf.PermissionCallback;
032: import com.sun.midp.push.gcf.ReservationDescriptorFactory;
033: import java.io.IOException;
034: import java.util.Collection;
035: import java.util.HashMap;
036: import java.util.HashSet;
037: import java.util.Iterator;
038: import java.util.Set;
039: import java.util.Vector;
040: import javax.microedition.io.ConnectionNotFoundException;
041:
042: /** Push connection controller. */
043: final class ConnectionController {
044: /** Store to save connection info. */
045: private final Store store;
046:
047: /**
048: * Reservation descriptor factory.
049: *
050: * IMPL_NOTE: hopefully will go away
051: */
052: private final ReservationDescriptorFactory reservationDescriptorFactory;
053:
054: /** Lifecycle adapter implementation. */
055: private final LifecycleAdapter lifecycleAdapter;
056:
057: /** Current reservations. */
058: private final Reservations reservations;
059:
060: /**
061: * Creates an instance.
062: *
063: * @param store persistent store to save connection info into
064: * (cannot be <code>null</code>)
065: *
066: * @param reservationDescriptorFactory reservation descriptor factory
067: * (cannot be <code>null</code>
068: *
069: * @param lifecycleAdapter adapter to launch <code>MIDlet</code>
070: * (cannot be <code>null</code>)
071: */
072: public ConnectionController(
073: final Store store,
074: final ReservationDescriptorFactory reservationDescriptorFactory,
075: final LifecycleAdapter lifecycleAdapter) {
076: this .store = store;
077: this .reservationDescriptorFactory = reservationDescriptorFactory;
078: this .lifecycleAdapter = lifecycleAdapter;
079: this .reservations = new Reservations();
080:
081: reserveConnectionsFromStore();
082: }
083:
084: /**
085: * Registers the connection.
086: *
087: * <p>
088: * Saves the connection into persistent store and reserves it
089: * for <code>MIDlet</code>.
090: * </p>
091: *
092: * <p>
093: * The connection should be already preverified (see
094: * <code>reservationDescriptor</code> parameter) and all the security
095: * checks should be performed.
096: * </p>
097: *
098: * @param midletSuiteID <code>MIDlet</code> suite ID
099: *
100: * @param midlet <code>MIDlet</code> class name
101: * (cannot be <code>null</code>)
102: *
103: * @param reservationDescriptor reservation descriptor
104: * (cannot be <code>null</code>)
105: *
106: * @throws IOException if the connection is already registered or
107: * if there are insufficient resources to handle the registration request
108: */
109: public synchronized void registerConnection(
110: final int midletSuiteID, final String midlet,
111: final ReservationDescriptor reservationDescriptor)
112: throws IOException {
113: final String connectionName = reservationDescriptor
114: .getConnectionName();
115:
116: /*
117: * IMPL_NOTE: due to ReservationDescriptor#reserve limitations,
118: * need to unregister registered connection first
119: */
120: final ReservationHandler previous = reservations
121: .queryByConnectionName(connectionName);
122: if (previous != null) {
123: if (previous.getSuiteId() != midletSuiteID) {
124: // Already registered for another suite, fail quickly
125: throw new IOException("registered for another suite");
126: }
127: removeRegistration(previous);
128: }
129:
130: final ReservationHandler reservationHandler = new ReservationHandler(
131: midletSuiteID, midlet, reservationDescriptor);
132:
133: final String filter = reservationDescriptor.getFilter();
134:
135: try {
136: // TODO: rethink if we need filter in JUMPConnectionInfo
137: final JUMPConnectionInfo connectionInfo = new JUMPConnectionInfo(
138: connectionName, midlet, filter);
139: store.addConnection(midletSuiteID, connectionInfo);
140: } catch (IOException ioex) {
141: reservationHandler.cancel();
142: throw ioex; // rethrow IOException
143: }
144:
145: reservations.add(reservationHandler);
146: }
147:
148: /**
149: * Unregisters the connection.
150: *
151: * <p>
152: * Removes the connection from persistent store and cancels connection
153: * reservation.
154: * </p>
155: *
156: * @param midletSuiteID <code>MIDlet</code> suite ID
157: *
158: * @param connectionName connection to unregister
159: * (cannot be <code>null</code>)
160: *
161: * @return <code>true</code> if the unregistration was successful,
162: * <code>false</code> if the connection was not registered
163: *
164: * @throws SecurityException if the connection was registered by
165: * another <code>MIDlet</code> suite
166: */
167: public synchronized boolean unregisterConnection(
168: final int midletSuiteID, final String connectionName)
169: throws SecurityException {
170: final ReservationHandler reservationHandler = reservations
171: .queryByConnectionName(connectionName);
172:
173: if (reservationHandler == null) {
174: // Connection hasn't been registered
175: return false;
176: }
177:
178: if (reservationHandler.getSuiteId() != midletSuiteID) {
179: throw new SecurityException(
180: "attempt to unregister connection of another suite");
181: }
182:
183: try {
184: removeRegistration(reservationHandler);
185: } catch (IOException ioex) {
186: return false;
187: }
188:
189: return true;
190: }
191:
192: /**
193: * Transactionally removes the registration.
194: *
195: * @param reservationHandler reservation handler.
196: *
197: * @throws IOException if persistent store fails
198: */
199: private void removeRegistration(
200: final ReservationHandler reservationHandler)
201: throws IOException {
202: final JUMPConnectionInfo info = new JUMPConnectionInfo(
203: reservationHandler.getConnectionName(),
204: reservationHandler.getMidlet(), reservationHandler
205: .getFilter());
206: store.removeConnection(reservationHandler.getSuiteId(), info);
207: reservationHandler.cancel();
208: reservations.remove(reservationHandler);
209: }
210:
211: /**
212: * Returns a list of registered connections for <code>MIDlet</code> suite.
213: *
214: * @param midletSuiteID <code>MIDlet</code> suite ID
215: *
216: * @param available if <code>true</code>, only return
217: * the list of connections with input available, otherwise
218: * return the complete list of registered connections for
219: * <code>MIDlet</code> suite
220: *
221: * @return array of registered connection strings, where each connection
222: * is represented by the generic connection <em>protocol</em>,
223: * <em>host</em> and <em>port</em> number identification
224: */
225: public synchronized String[] listConnections(
226: final int midletSuiteID, final boolean available) {
227: final Vector result = new Vector();
228:
229: final Iterator it = reservations.queryBySuiteID(midletSuiteID)
230: .iterator();
231: while (it.hasNext()) {
232: final ReservationHandler handler = (ReservationHandler) it
233: .next();
234: if ((!available) || handler.hasAvailableData()) {
235: result.add(handler.getConnectionName());
236: }
237: }
238: return (String[]) result.toArray(new String[result.size()]);
239: }
240:
241: /**
242: * Fetches the <code>MIDlet</code> by the connection.
243: *
244: * @param midletSuiteID <code>MIDlet</code> suite ID to query for
245: *
246: * @param connectionName connectionName as passed into
247: * {@link #registerConnection}
248: * (cannot be <code>null</code>)
249: *
250: * @return <code>MIDlet</code> associated with <code>connectionName</code>
251: * or <code>null</code> if there is no appropriate association
252: */
253: public synchronized String getMIDlet(final int midletSuiteID,
254: final String connectionName) {
255: final ReservationHandler reservationHandler = reservations
256: .queryByConnectionName(connectionName);
257:
258: if ((reservationHandler == null)
259: || (reservationHandler.getSuiteId() != midletSuiteID)) {
260: return null;
261: }
262:
263: return reservationHandler.getMidlet();
264: }
265:
266: /**
267: * Fetches the filter by the connection.
268: *
269: * @param midletSuiteID <code>MIDlet</code> suite ID to query for
270: *
271: * @param connectionName connectionName as passed into
272: * {@link #registerConnection}
273: * (cannot be <code>null</code>)
274: *
275: * @return filter associated with <code>connectionName</code> or
276: * <code>null</code> if there is no appropriate association
277: */
278: public synchronized String getFilter(final int midletSuiteID,
279: final String connectionName) {
280: final ReservationHandler reservationHandler = reservations
281: .queryByConnectionName(connectionName);
282:
283: if ((reservationHandler == null)
284: || (reservationHandler.getSuiteId() != midletSuiteID)) {
285: return null;
286: }
287:
288: return reservationHandler.getFilter();
289: }
290:
291: /**
292: * Removes connections for the given suite.
293: *
294: * <p>
295: * NOTE: <code>midletSuiteID</code> must refer to valid installed
296: * <code>MIDlet</code> suite. However, it might refer to the
297: * suite without connections.
298: * </p>
299: *
300: * @param midletSuiteID ID of the suite to remove connections for
301: */
302: public synchronized void removeSuiteConnections(
303: final int midletSuiteID) {
304: /*
305: * IMPL_NOTE: one shouldn't remove and iterate in same time.
306: * The solution is to copy first. It's safe for method is synchronized.
307: */
308: final Collection rs = reservations
309: .queryBySuiteID(midletSuiteID);
310: final ReservationHandler[] handlers = new ReservationHandler[rs
311: .size()];
312: rs.toArray(handlers);
313: for (int i = 0; i < handlers.length; i++) {
314: try {
315: removeRegistration(handlers[i]);
316: } catch (IOException ioex) {
317: logError("failed to remove " + handlers[i] + ": "
318: + ioex);
319: }
320: }
321: }
322:
323: /**
324: * Disposes a connection controller.
325: *
326: * <p>
327: * Cancels all the reservations and callbacks.
328: * </p>
329: *
330: * <p>
331: * The only thing one MUST do with disposed
332: * <code>ConnectionController</code> is to garbage-collect it.
333: * </p>
334: */
335: public synchronized void dispose() {
336: for (Iterator it = reservations.getAllReservations().iterator(); it
337: .hasNext();) {
338: final ReservationHandler rh = (ReservationHandler) it
339: .next();
340: rh.cancel();
341: }
342: reservations.clear();
343: }
344:
345: /**
346: * Noop permission callback for startup connection reservation.
347: *
348: * IMPL_NOTE: hopefully will go away when we'll get rid of
349: * reservationDescriptorFactory
350: */
351: private final PermissionCallback noopPermissionCallback = new PermissionCallback() {
352: public void checkForPermission(final String permissionName,
353: final String resource, final String extraValue)
354: throws SecurityException {
355: }
356: };
357:
358: /**
359: * Reads and reserves connections in persistent store.
360: */
361: private void reserveConnectionsFromStore() {
362: /*
363: * IMPL_NOTE: currently connection info is stored as plain data.
364: * However, as reservation descriptors should be serializable
365: * for jump, it might be a good idea to store descriptors
366: * directly and thus get rid of reservation factory in
367: * ConnectionController altogether.
368: */
369: store.listConnections(new Store.ConnectionsConsumer() {
370: public void consume(final int suiteId,
371: final JUMPConnectionInfo[] connections) {
372: for (int i = 0; i < connections.length; i++) {
373: final JUMPConnectionInfo info = connections[i];
374: try {
375: registerConnection(suiteId, info.midlet,
376: reservationDescriptorFactory
377: .getDescriptor(info.connection,
378: info.filter,
379: noopPermissionCallback));
380: } catch (ConnectionNotFoundException cnfe) {
381: logError("failed to register " + info + ": "
382: + cnfe);
383: } catch (IOException ioex) {
384: logError("failed to register " + info + ": "
385: + ioex);
386: }
387: }
388: }
389: });
390: }
391:
392: /**
393: * Reservation listening handler.
394: *
395: * <p>
396: * IMPL_NOTE: invariant is: one instance of a handler per registration
397: * and thus default Object <code>equals</code> and <code>hashCode</code>
398: * should be enough
399: * </p>
400: *
401: * <p>
402: * TODO: think if common code with AlarmRegistry.AlarmTask can be factored
403: * </p>
404: */
405: final class ReservationHandler implements DataAvailableListener {
406: /** Reservation's app. */
407: private final MIDPApp midpApp;
408:
409: /** Connection name. */
410: private final String connectionName;
411:
412: /** Connection filter. */
413: private final String filter;
414:
415: /** Connection reservation. */
416: private final ConnectionReservation connectionReservation;
417:
418: /** Cancelation status. */
419: private boolean cancelled = false;
420:
421: /**
422: * Creates a handler and reserves the connection.
423: *
424: * @param midletSuiteID <code>MIDlet</code> suite ID of reservation
425: *
426: * @param midlet <code>MIDlet</code> suite ID of reservation.
427: * (cannot be <code>null</code>)
428: *
429: * @param reservationDescriptor reservation descriptor
430: * (cannot be <code>null</code>)
431: *
432: * @throws IOException if reservation fails
433: */
434: ReservationHandler(final int midletSuiteID,
435: final String midlet,
436: final ReservationDescriptor reservationDescriptor)
437: throws IOException {
438: this .midpApp = new MIDPApp(midletSuiteID, midlet);
439:
440: this .connectionName = reservationDescriptor
441: .getConnectionName();
442: this .filter = reservationDescriptor.getFilter();
443:
444: this .connectionReservation = reservationDescriptor.reserve(
445: midletSuiteID, midlet, this );
446: }
447:
448: /**
449: * Gets <code>MIDlet</code> suite ID.
450: *
451: * @return <code>MIDlet</code> suite ID
452: */
453: int getSuiteId() {
454: return midpApp.midletSuiteID;
455: }
456:
457: /**
458: * Gets <code>MIDlet</code> class name.
459: *
460: * @return <code>MIDlet</code> class name
461: */
462: String getMidlet() {
463: return midpApp.midlet;
464: }
465:
466: /**
467: * Gets connection name.
468: *
469: * @return connection name
470: */
471: String getConnectionName() {
472: return connectionName;
473: }
474:
475: /**
476: * Gets connection filter.
477: *
478: * @return connection filter
479: */
480: String getFilter() {
481: return filter;
482: }
483:
484: /**
485: * Cancels reservation.
486: */
487: void cancel() {
488: cancelled = true;
489: connectionReservation.cancel();
490: }
491:
492: /**
493: * See {@link ConnectionReservation#hasAvailableData}.
494: *
495: * @return <code>true</code> iff reservation has available data
496: */
497: boolean hasAvailableData() {
498: return connectionReservation.hasAvailableData();
499: }
500:
501: /** {@inheritDoc} */
502: public void dataAvailable() {
503: synchronized (ConnectionController.this ) {
504: if (cancelled) {
505: return;
506: }
507:
508: try {
509: lifecycleAdapter.launchMidlet(
510: midpApp.midletSuiteID, midpApp.midlet);
511: } catch (Exception ex) {
512: /* IMPL_NOTE: need to handle _all_ the exceptions. */
513: /* TBD: uncomment when logging can be disabled
514: * (not to interfer with unittests)
515: logError(
516: "Failed to launch \"" + midpApp.midlet + "\"" +
517: " (suite ID: " + midpApp.midletSuiteID + "): " +
518: ex);
519: */
520: }
521: }
522: }
523: }
524:
525: /** Internal structure that manages needed mappings. */
526: static final class Reservations {
527: /** Mappings from connection to reservations. */
528: private final HashMap connection2data = new HashMap();
529:
530: /** Mappings from suite id to set of reservations. */
531: private final HashMap suiteId2data = new HashMap();
532:
533: /**
534: * Adds a reservation into reservations.
535: *
536: * @param reservationHandler reservation to add
537: */
538: void add(final ReservationHandler reservationHandler) {
539: connection2data.put(reservationHandler.getConnectionName(),
540: reservationHandler);
541:
542: final Set data = getData(reservationHandler.getSuiteId());
543: if (data != null) {
544: data.add(reservationHandler);
545: } else {
546: final Set d = new HashSet();
547: d.add(reservationHandler);
548: suiteId2data.put(new Integer(reservationHandler
549: .getSuiteId()), d);
550: }
551: }
552:
553: /**
554: * Removes a reservation from reservations.
555: *
556: * @param reservationHandler reservation to remove
557: */
558: void remove(final ReservationHandler reservationHandler) {
559: connection2data.remove(reservationHandler
560: .getConnectionName());
561:
562: getData(reservationHandler.getSuiteId()).remove(
563: reservationHandler);
564: }
565:
566: /**
567: * Queries the reservations by the connection.
568: *
569: * @param connectionName name of connection to query by
570: * (cannot be <code>null</code>)
571: *
572: * @return reservation (<code>null</code> if absent)
573: */
574: ReservationHandler queryByConnectionName(
575: final String connectionName) {
576: return (ReservationHandler) connection2data
577: .get(connectionName);
578: }
579:
580: /**
581: * Queries the reservations by the suite id.
582: *
583: * @param midletSuiteID <code>MIDlet</code> suite ID to query by
584: * @return collection of <code>ReservationHandler</code>s
585: * (cannot be <code>null</code>)
586: */
587: Collection queryBySuiteID(final int midletSuiteID) {
588: final Set data = getData(midletSuiteID);
589: return (data == null) ? new HashSet() : data;
590: }
591:
592: /**
593: * Gets all the reservations.
594: *
595: * @return collection of reservations
596: */
597: Collection getAllReservations() {
598: return connection2data.values();
599: }
600:
601: /**
602: * Clears all the reservations.
603: */
604: void clear() {
605: connection2data.clear();
606: suiteId2data.clear();
607: }
608:
609: /**
610: * Gets reservation set by suite id.
611: *
612: * @param midletSuiteID <code>MIDlet</code> suite ID
613: *
614: * @return set of reservations or <code>null</code> if absent
615: */
616: private Set getData(final int midletSuiteID) {
617: return (Set) suiteId2data.get(new Integer(midletSuiteID));
618: }
619: }
620:
621: /**
622: * Logs error message.
623: *
624: * <p>
625: * TBD: common logging
626: * </p>
627: *
628: * @param message message to log
629: */
630: private static void logError(final String message) {
631: System.err.println("ERROR ["
632: + ConnectionController.class.getName() + "]: "
633: + message);
634: }
635: }
|