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: package com.sun.midp.jump.push.executive.persistence;
025:
026: import com.sun.jump.module.contentstore.JUMPNode;
027: import com.sun.jump.module.contentstore.JUMPStoreHandle;
028: import com.sun.midp.jump.push.executive.JUMPConnectionInfo;
029: import java.io.IOException;
030: import java.util.Arrays;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.Map;
034: import java.util.Vector;
035:
036: /**
037: * Persistent store class for <code>PushRegistry</code> module.
038: *
039: * <p>
040: * IMPORTANT_NOTE: The clients of this class should ensure that content store
041: * manager passed into the constructor doesn't have exclusive lock when
042: * methods are invoked. Otherwise we'll face deadlocks.
043: * <p>
044: *
045: * <p><strong>NB</strong>: method <code>getConnections</code> guarantees
046: * that <code>MIDlet suite</code> without connections won't be listed,
047: * i.e. <code>MIDletSuiteConnections.connections</code>
048: * array is not empty</p>
049: *
050: * <p><strong>NB</strong>: this class has no intellegence of connection
051: * semantics, e.g. it doesn't handle connection conflicts. It's a simplistic
052: * database.</p>
053: *
054: * <p><strong>Implementation notice</strong>: as for now if <code>MIDlet
055: * suite</code> removes all the connections, the file with suite connections
056: * is not removed and the suite is filtered in {@line getConnections} method.
057: * Another option might be to remove the file.</p>
058: */
059: public final class Store {
060: /** PushRegistry root dir. */
061: private static final String ROOT_DIR = "./push/";
062:
063: /** Dir to store connections. */
064: static final String CONNECTIONS_DIR = ROOT_DIR + "connections";
065:
066: /** Dir to store alarms. */
067: static final String ALARMS_DIR = ROOT_DIR + "alarms";
068:
069: /** PushRegistry connections. */
070: private final AppSuiteDataStore connectionsStore;
071:
072: /** PushRegistry alarms. */
073: private final AppSuiteDataStore alarmsStore;
074:
075: /**
076: * Constructs a Store and reads the data.
077: *
078: * @param storeManager JUMP content store manager to use
079: *
080: * @throws IOException if IO fails
081: */
082: public Store(final StoreOperationManager storeManager)
083: throws IOException {
084: if (storeManager == null) {
085: throw new IllegalArgumentException("storeManager is null");
086: }
087:
088: ensureStoreStructure(storeManager);
089:
090: connectionsStore = new AppSuiteDataStore(storeManager,
091: CONNECTIONS_DIR, CONNECTIONS_CONVERTER);
092:
093: alarmsStore = new AppSuiteDataStore(storeManager, ALARMS_DIR,
094: ALARMS_CONVERTER);
095: }
096:
097: /**
098: * Ensures presence of store layout requested for the
099: * store to function correctly.
100: *
101: * @param storeManager JUMP content store manager to use
102: *
103: * @throws IOException in case of IO errors
104: */
105: private static void ensureStoreStructure(
106: final StoreOperationManager storeManager)
107: throws IOException {
108: storeManager.doOperation(true,
109: new StoreOperationManager.Operation() {
110: public Object perform(
111: final JUMPStoreHandle storeHandle)
112: throws IOException {
113: ensureDir(storeHandle, CONNECTIONS_DIR);
114: ensureDir(storeHandle, ALARMS_DIR);
115: return null;
116: }
117: });
118: }
119:
120: /**
121: * Ensures presence of the given dir.
122: *
123: * @param storeHandle handle to content store to use
124: * @param dir name of directory to check
125: *
126: * @throws IOException in case of IO troubles
127: */
128: private static void ensureDir(final JUMPStoreHandle storeHandle,
129: final String dir) throws IOException {
130: final JUMPNode node = storeHandle.getNode(dir);
131: if (node == null) {
132: logWarning("Directory " + dir + " is missing: recreating");
133: storeHandle.createNode(dir);
134: return;
135: }
136: if (!(node instanceof JUMPNode.List)) {
137: logWarning("Node " + dir
138: + " is a data node: trying to erase and recreate");
139: storeHandle.deleteNode(dir);
140: storeHandle.createNode(dir);
141: return;
142: }
143: }
144:
145: private static void logWarning(final String msg) {
146: // TBD: use common logging
147: System.out.println("[warning] " + Store.class + ": " + msg);
148: }
149:
150: /** Connections consumer. */
151: public static interface ConnectionsConsumer {
152: /**
153: * COnsumes app suite connections.
154: *
155: * @param suiteId app suite ID
156: * @param connections app suite connections
157: */
158: void consume(int suiteId, JUMPConnectionInfo[] connections);
159: }
160:
161: /**
162: * Lists all registered connections.
163: *
164: * @param connectionsLister connection lister
165: */
166: public synchronized void listConnections(
167: final ConnectionsConsumer connectionsLister) {
168: connectionsStore.listData(new AppSuiteDataStore.DataConsumer() {
169: public void consume(final int suiteId,
170: final Object suiteData) {
171: final Vector v = (Vector) suiteData;
172: final JUMPConnectionInfo[] cns = new JUMPConnectionInfo[v
173: .size()];
174: v.toArray(cns);
175: connectionsLister.consume(suiteId, cns);
176: }
177: });
178: }
179:
180: /**
181: * Adds new registered connection.
182: *
183: * <p><strong>Precondition</strong>: <code>connection</code> MUST not be
184: * already registered (the method doesn't check it)</p>
185: *
186: * @param midletSuiteID ID of <code>MIDlet suite</code> to register
187: * connection for
188: *
189: * @param connection Connection to register
190: *
191: * @throws IOException if the content store failed to perform operation
192: */
193: public synchronized void addConnection(final int midletSuiteID,
194: final JUMPConnectionInfo connection) throws IOException {
195: Vector cns = (Vector) connectionsStore
196: .getSuiteData(midletSuiteID);
197: if (cns == null) {
198: cns = new Vector();
199: }
200: cns.add(connection);
201: connectionsStore.updateSuiteData(midletSuiteID, cns);
202: }
203:
204: /**
205: * Adds connections for the suite being installed.
206: *
207: * <p><strong>Preconditin</strong>: <code>connection</code> MUST not be
208: * already registered (the method doesn't check it)</p>
209: *
210: * @param midletSuiteID ID of <code>MIDlet suite</code> to register
211: * connection for
212: *
213: * @param connections Connections to register
214: *
215: * @throws IOException if the content store failed to perform operation
216: */
217: public synchronized void addConnections(final int midletSuiteID,
218: final JUMPConnectionInfo[] connections) throws IOException {
219: final Vector cns = new Vector(Arrays.asList(connections));
220: connectionsStore.updateSuiteData(midletSuiteID, cns);
221: }
222:
223: /**
224: * Removes registered connection.
225: *
226: * <p><strong>Preconditin</strong>: <code>connection</code> MUST have been
227: * already registered (the method doesn't check it)</p>
228: *
229: * <p><strong>NB</strong>: <code>throws IOException</code> was intentionally
230: * removed from the signature: it's resonsibility of <code>Store</code>
231: * to ensure removal of all connections.</p>
232: *
233: * @param midletSuiteID ID of <code>MIDlet suite</code> to remove
234: * connection for
235: *
236: * @param connection Connection to remove
237: *
238: * @throws IOException if the content store failed to perform operation
239: */
240: public synchronized void removeConnection(final int midletSuiteID,
241: final JUMPConnectionInfo connection) throws IOException {
242: Vector cns = (Vector) connectionsStore
243: .getSuiteData(midletSuiteID);
244: // assert cns != null : "cannot be null";
245: cns.remove(connection);
246: if (!cns.isEmpty()) {
247: connectionsStore.updateSuiteData(midletSuiteID, cns);
248: } else {
249: connectionsStore.removeSuiteData(midletSuiteID);
250: }
251: }
252:
253: /**
254: * Removes all registered connections.
255: *
256: * <p><strong>Preconditin</strong>: <code>connection</code> MUST have been
257: * already registered (the method doesn't check it)</p>
258: *
259: * <p><strong>NB</strong>: <code>throws IOException</code> was intentionally
260: * removed from the signature: it's resonsibility of <code>Store</code>
261: * to ensure removal of all connections.</p>
262: *
263: * @param midletSuiteID ID of <code>MIDlet suite</code> to remove
264: * connections for
265: *
266: * @throws IOException if the content store failed
267: */
268: public synchronized void removeConnections(final int midletSuiteID)
269: throws IOException {
270: connectionsStore.removeSuiteData(midletSuiteID);
271: }
272:
273: /** Alarms lister. */
274: public static interface AlarmsConsumer {
275: /**
276: * Lists app suite alarms.
277: *
278: * @param suiteId app suite ID
279: * @param alarms alarms mappings from <code>MIDlet</code> class name
280: * to the scheduled time
281: */
282: void consume(int suiteId, Map alarms);
283: }
284:
285: /**
286: * Lists all alarms.
287: *
288: * @param alarmsLister connection lister
289: */
290: public synchronized void listAlarms(
291: final AlarmsConsumer alarmsLister) {
292: alarmsStore.listData(new AppSuiteDataStore.DataConsumer() {
293: public void consume(final int suiteId,
294: final Object suiteData) {
295: alarmsLister.consume(suiteId, (Map) suiteData);
296: }
297: });
298: }
299:
300: /**
301: * Adds an alarm.
302: *
303: * @param midletSuiteID <code>MIDlet suite</code> to add alarm for
304: * @param midlet <code>MIDlet</code> class name
305: * @param time alarm time
306: *
307: * @throws IOException if the content store failed
308: */
309: public synchronized void addAlarm(final int midletSuiteID,
310: final String midlet, final long time) throws IOException {
311: HashMap as = (HashMap) alarmsStore.getSuiteData(midletSuiteID);
312: if (as == null) {
313: as = new HashMap();
314: }
315: as.put(midlet, new Long(time));
316: alarmsStore.updateSuiteData(midletSuiteID, as);
317: }
318:
319: /**
320: * Removes an alarm.
321: *
322: * @param midletSuiteID <code>MIDlet suite</code> to remove alarm for
323: * @param midlet <code>MIDlet</code> class name
324: *
325: * @throws IOException if the content store failed
326: */
327: public synchronized void removeAlarm(final int midletSuiteID,
328: final String midlet) throws IOException {
329: final HashMap as = (HashMap) alarmsStore
330: .getSuiteData(midletSuiteID);
331: // assert as != null;
332: as.remove(midlet);
333: if (!as.isEmpty()) {
334: alarmsStore.updateSuiteData(midletSuiteID, as);
335: } else {
336: alarmsStore.removeSuiteData(midletSuiteID);
337: }
338: }
339:
340: /** Connections converter. */
341: private static final AppSuiteDataStore.DataConverter CONNECTIONS_CONVERTER = new ConnectionConverter();
342:
343: /** Implements conversion interface for connections. */
344: private static final class ConnectionConverter implements
345: AppSuiteDataStore.DataConverter {
346: /**
347: * Separator to use.
348: *
349: * <p>
350: * <strong>NB</strong>: Separator shouldn't be valid character
351: * for <code>connection</code>, <code>midlet</code> or
352: * <code>filter</code> fields of <code>JUMPConnectionInfo</code>
353: */
354: static final char SEPARATOR = '\n';
355:
356: /**
357: * Number of strings per record.
358: */
359: static final int N_STRINGS_PER_RECORD = 3;
360:
361: /**
362: * Converts a <code>Vector</code> of <code>JUMPConnectionInfo</code>
363: * into a string.
364: *
365: * @param data data to convert
366: * @return string with all connections
367: */
368: public String dataToString(final Object data) {
369: final Vector connections = (Vector) data;
370: if (connections == null) {
371: throw new IllegalArgumentException(
372: "connections vector is null");
373: }
374:
375: final StringBuffer sb = new StringBuffer();
376:
377: for (Iterator it = connections.iterator(); it.hasNext();) {
378: JUMPConnectionInfo connection = (JUMPConnectionInfo) it
379: .next();
380: sb.append(connection.connection);
381: sb.append(SEPARATOR);
382: sb.append(connection.midlet);
383: sb.append(SEPARATOR);
384: sb.append(connection.filter);
385: sb.append(SEPARATOR);
386: }
387:
388: return sb.toString();
389: }
390:
391: /**
392: * Converts a string into a <code>Vector</code> of
393: * <code>JUMPConnectionInfo</code>.
394: *
395: * @param string string to convert
396: * @return <code>Vector</code> of connections
397: */
398: public Object stringToData(final String string) {
399: if (string == null) {
400: throw new IllegalArgumentException("string is null");
401: }
402:
403: Vector split = splitString(string);
404: String[] ss = new String[split.size()];
405: split.toArray(ss);
406:
407: // assert (strings.length % N_STRINGS_PER_RECORD == 0)
408: // : "Broken data";
409:
410: Vector v = new Vector();
411: for (int i = 0; i < ss.length; i += N_STRINGS_PER_RECORD) {
412: v.add(new JUMPConnectionInfo(ss[i], ss[i + 1],
413: ss[i + 2]));
414: }
415: return v;
416: }
417:
418: /**
419: * Splits a string into <code>Vector</code> of strings.
420: *
421: * @param string string to split
422: *
423: * @return <code>Vector</code> of strings splitted by
424: * <code>SEPARATOR</code>
425: */
426: private Vector splitString(final String string) {
427: Vector v = new Vector();
428: int start = 0;
429: while (true) {
430: int i = string.indexOf(SEPARATOR, start);
431: if (i == -1) {
432: // assert start == string.length();
433: return v;
434: }
435: v.add(string.substring(start, i));
436: start = i + 1;
437: }
438: }
439: };
440:
441: /** Alarms converter. */
442: private static final AppSuiteDataStore.DataConverter ALARMS_CONVERTER = new AlarmsConverter();
443:
444: /** Implements conversion interface for alarms. */
445: private static final class AlarmsConverter implements
446: AppSuiteDataStore.DataConverter {
447: /** Char to seprate midlet class name from time. */
448: private static final char FIELD_SEP = ':';
449:
450: /**
451: * Converts data into a string.
452: *
453: * @param data data to convert
454: *
455: * @return string representation
456: */
457: public String dataToString(final Object data) {
458: final Map m = (Map) data;
459:
460: final StringBuffer sb = new StringBuffer();
461: for (Iterator it = m.entrySet().iterator(); it.hasNext();) {
462: final Map.Entry entry = (Map.Entry) it.next();
463: final String midlet = (String) entry.getKey();
464: final Long time = (Long) entry.getValue();
465: sb.append(midlet);
466: sb.append(FIELD_SEP);
467: sb.append(time);
468: sb.append('\n');
469: }
470:
471: return sb.toString();
472: }
473:
474: /**
475: * Converts a string into data.
476: *
477: * @param s string to convert
478: *
479: * @return data
480: */
481: public Object stringToData(final String s) {
482: final HashMap data = new HashMap();
483: int pos = 0;
484: for (;;) {
485: final int p = s.indexOf('\n', pos);
486: if (p == -1) {
487: // assert pos == s.length() - 1 : "unprocessed chars!";
488: return data;
489: }
490: final String record = s.substring(pos, p);
491:
492: // Parse record
493: final int sepPos = record.indexOf(FIELD_SEP);
494: // assert sepPos != -1 : "wrong record";
495: final String midlet = record.substring(0, sepPos);
496: final String timeString = record.substring(sepPos + 1);
497: data.put(midlet, Long.valueOf(timeString));
498:
499: pos = p + 1;
500: }
501: }
502: }
503: }
|