KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > sync4j > server > engine > dm > ManagementEngine


1 /**
2  * Copyright (C) 2003-2005 Funambol
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17  */

18
19 package sync4j.server.engine.dm;
20
21 import java.net.URI JavaDoc;
22 import java.net.URISyntaxException JavaDoc;
23 import java.util.Date JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.logging.Level JavaDoc;
27 import java.util.logging.Logger JavaDoc;
28
29 import org.apache.commons.lang.StringUtils;
30
31 import sync4j.framework.config.Configuration;
32 import sync4j.framework.config.ConfigurationConstants;
33 import sync4j.framework.core.bootstrap.BootStrap;
34 import sync4j.framework.core.dm.ddf.DMAcc;
35 import sync4j.framework.core.dm.ddf.DMAccount;
36 import sync4j.framework.core.dm.ddf.SyncMLDM;
37 import sync4j.framework.engine.dm.DeviceDMState;
38 import sync4j.framework.notification.NotificationConstants;
39 import sync4j.framework.notification.NotificationEngine;
40 import sync4j.framework.notification.NotificationException;
41 import sync4j.framework.security.Officer;
42 import sync4j.framework.security.Sync4jPrincipal;
43 import sync4j.framework.server.Sync4jDevice;
44 import sync4j.framework.server.SyncUser;
45 import sync4j.framework.server.store.*;
46 import sync4j.framework.tools.Base64;
47 import sync4j.framework.tools.MD5;
48 import sync4j.framework.tools.SecurityTools;
49 import sync4j.framework.tools.beans.BeanFactory;
50
51 import sync4j.server.admin.DBUserManager;
52 import sync4j.server.engine.Sync4jEngine;
53 import sync4j.server.notification.NotificationEngineImpl;
54
55
56
57
58 /**
59  * This class implements the Management Engine component.
60  *
61  * @author Stefano Nichele @ Funambol
62  *
63  * @version $Id: ManagementEngine.java,v 1.1 2005/05/16 17:32:56 nichele Exp $
64  */

65 public class ManagementEngine extends Sync4jEngine {
66
67     // --------------------------------------------------------------- Constants
68

69     /**
70      * HTTP: 1
71      * WSP: 2
72      * OBEX: 3
73      */

74     private static final int DEFAULT_ADDRESS_TYPE = 1;
75     private static final int DEFAULT_PORT_NUMBER = 80;
76
77     /**
78      * The server bean to use to create a bootstrap object
79      */

80     public static final String JavaDoc SYNCML_DM_BOOTSTRAP_MESSAGE =
81         "sync4j/server/engine/dm/SyncMLDMbootstrapMessage.xml";
82
83     // Spaceid used in getNextNotificationSessionIds
84
private static final String JavaDoc NS_MSSID = "mssid" ;
85
86     private static final String JavaDoc OPERATION_AFTER_BOOTSTRAP = "GetDeviceDetail";
87
88     // ------------------------------------------------------------ Private Data
89
private Configuration config = null;
90     private Logger JavaDoc log = null;
91     private DBUserManager dbUserManager = null;
92     private Officer officer = null;
93
94     // ------------------------------------------------------------- Constructor
95

96     /**
97      * Disable default constructor
98      */

99     private ManagementEngine() {}
100
101
102     /**
103      * Creates a new instance of ManagementEngine given a Configuration object.
104      *
105      * @param config the configuration object
106      */

107     public ManagementEngine(Configuration config) {
108         super(config);
109         this.config = config;
110         log = Logger.getLogger("sync4j.dm");
111
112         dbUserManager = (DBUserManager)config.getBeanInstance(CFG_USER_MANAGER);
113         officer = (Officer)config.getBeanInstance(CFG_SECURITY_OFFICER);
114     }
115
116
117     /**
118      * Performs a bootstrap with the given parameter
119      *
120      * @param messageType the type of the bootstrap message as define in <code>NotificationConstants<code>
121      * @param transportType the type of the transport as define in <code>NotificationConstants<code>
122      * @param deviceUri the device id
123      * @param phoneNumber the phone number of the device
124      * @param username the user name with which the device will do the next device management
125      * @param password the password with which the device will do the next device management
126      * @param info application specific info
127      * @param updateDeviceInfo if the device already exist and this parameter is true,
128      * the client nonce, server nonce and server password are regenerated. If this parameter
129      * is false, no new info are generated and the previous data are used.
130      * @throws NotificationException if a error occurs
131      */

132     public void bootstrap(int messageType,
133                           int transportType,
134                           String JavaDoc deviceUri,
135                           String JavaDoc phoneNumber,
136                           String JavaDoc username,
137                           String JavaDoc password,
138                           String JavaDoc info,
139                           boolean updateDeviceInfo) throws NotificationException {
140
141         if (log.isLoggable(Level.INFO)) {
142             log.info("Start bootstrap process in ManagementEngine for: " + phoneNumber);
143         }
144
145         String JavaDoc serverUri = config.getStringValue(ConfigurationConstants.CFG_SERVER_URI);
146
147         SyncMLDM syncMLDMBootstrapMessage = preBootstrapOperation(deviceUri,
148                                                                   phoneNumber,
149                                                                   username,
150                                                                   password,
151                                                                   info,
152                                                                   updateDeviceInfo);
153
154         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
155
156         notificationEngine.sendBootstrapMessage(messageType, transportType, serverUri, deviceUri,
157                                                 phoneNumber, syncMLDMBootstrapMessage, info);
158     }
159
160
161     /**
162      * Bootstrap devices with digest
163      * @param info the session id
164      * @param type the type of the message
165      * @param bootstrap the bootstrap info
166      * @param updateDeviceInfo if the device already exist and this parameter is true,
167      * the client nonce, server nonce and server password are regenerated. If this parameter
168      * is false, no new info are generated and the previous data are used.
169      */

170     public void bootstrapWithDigest(int messageType,
171                                     int transportType,
172                                     String JavaDoc info,
173                                     BootStrap[] bootstrap,
174                                     boolean updateDeviceInfo) throws NotificationException {
175
176         if (log.isLoggable(Level.INFO)) {
177             log.info("Start bootstrap process in ManagementEngine for " + bootstrap.length +
178                      " devices");
179         }
180
181         String JavaDoc serverUri = config.getStringValue(ConfigurationConstants.CFG_SERVER_URI);
182
183         int numDevices = bootstrap.length;
184         String JavaDoc phoneNumber = null;
185         String JavaDoc username = null;
186         String JavaDoc password = null;
187         String JavaDoc deviceId = null;
188
189         SyncMLDM[] syncMLDMMessages = new SyncMLDM[numDevices];
190         String JavaDoc applicationInfo = null;
191
192         for (int i = 0; i < numDevices; i++) {
193             verifyBootStrap(bootstrap[i]);
194
195             phoneNumber = bootstrap[i].getMsisdn();
196             username = bootstrap[i].getUsername();
197             password = bootstrap[i].getPassword();
198             deviceId = bootstrap[i].getDeviceId(); // imei
199

200             if (numDevices == 1) {
201                 applicationInfo = info;
202             } else {
203                 applicationInfo = info + ":" + phoneNumber;
204             }
205
206             // creates device and syncMLDM message
207
syncMLDMMessages[i] = preBootstrapOperation(deviceId,
208                                                         phoneNumber,
209                                                         username,
210                                                         password,
211                                                         applicationInfo,
212                                                         updateDeviceInfo);
213         }
214
215         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
216
217         notificationEngine.sendBootstrapMessages(messageType,
218                                                  transportType,
219                                                  bootstrap,
220                                                  serverUri,
221                                                  syncMLDMMessages,
222                                                  info);
223
224     }
225
226     /**
227      * Bootstrap device with mac based on IMSI
228      * @param messageType the message type
229      * @param transportType the transport type
230      * @param info the application info
231      * @param bootstrap the bootstrap info
232      * @param updateDeviceInfo if the device already exist and this parameter is true,
233      * the client nonce, server nonce and server password are regenerated. If this parameter
234      * is false, no new info are generated and the previous data are used.
235      */

236     public void bootstrapWithIMSI(int messageType,
237                                   int transportType,
238                                   String JavaDoc info,
239                                   BootStrap[] bootstrap,
240                                   boolean updateDeviceInfo) throws NotificationException {
241
242         if (log.isLoggable(Level.INFO)) {
243             log.info("Start bootstrap process in ManagementEngine for " + bootstrap.length + " devices");
244         }
245
246         String JavaDoc serverUri = config.getStringValue(ConfigurationConstants.CFG_SERVER_URI);
247
248         int numDevices = bootstrap.length;
249         String JavaDoc phoneNumber = null;
250         String JavaDoc username = null;
251         String JavaDoc password = null;
252         String JavaDoc deviceId = null;
253
254         SyncMLDM[] syncMLDMMessage = new SyncMLDM[numDevices];
255         String JavaDoc applicationInfo = null;
256         for (int i = 0; i < numDevices; i++) {
257             verifyBootStrap(bootstrap[i]);
258
259             phoneNumber = bootstrap[i].getMsisdn();
260             username = bootstrap[i].getUsername();
261             password = bootstrap[i].getPassword();
262             deviceId = bootstrap[i].getDeviceId(); // imei
263

264             if (numDevices == 1) {
265                 applicationInfo = info;
266             } else {
267                 applicationInfo = info + ":" + phoneNumber;
268             }
269
270             // creates device and syncMLDM message
271
syncMLDMMessage[i] = preBootstrapOperation(deviceId,
272                                                        phoneNumber,
273                                                        username,
274                                                        password,
275                                                        applicationInfo,
276                                                        updateDeviceInfo);
277         }
278
279         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
280
281         notificationEngine.sendBootstrapMessages(messageType,
282                                                  transportType,
283                                                  bootstrap,
284                                                  serverUri,
285                                                  syncMLDMMessage,
286                                                  info);
287
288     }
289
290     /**
291      * Prepare bootstrap message
292      * @param messageType the message type
293      * @param transportType the transport type
294      * @param info the application info
295      * @param bootstrap the bootstrap list
296      * @param updateDeviceInfo if the device already exist and this parameter is true,
297      * the client nonce, server nonce and server password are regenerated. If this parameter
298      * is false, no new info are generated and the previous data are used.
299      * @return Bootstrap the bootstrap messages
300      */

301     public BootStrap[] prepareBootstrap(int messageType,
302                                         int transportType,
303                                         String JavaDoc info,
304                                         BootStrap[] bootstrap,
305                                         boolean updateDeviceInfo) throws NotificationException {
306
307         if (log.isLoggable(Level.INFO)) {
308             log.info("Generate bootstrap message in ManagementEngine for " + bootstrap.length + " devices");
309         }
310
311         String JavaDoc serverUri = config.getStringValue(ConfigurationConstants.CFG_SERVER_URI);
312
313         int numDevices = bootstrap.length;
314         String JavaDoc phoneNumber = null;
315         String JavaDoc username = null;
316         String JavaDoc password = null;
317         String JavaDoc deviceId = null;
318
319         SyncMLDM[] syncMLDMMessage = new SyncMLDM[numDevices];
320         String JavaDoc applicationInfo = null;
321         for (int i = 0; i < numDevices; i++) {
322             verifyBootStrap(bootstrap[i]);
323
324             phoneNumber = bootstrap[i].getMsisdn();
325             username = bootstrap[i].getUsername();
326             password = bootstrap[i].getPassword();
327             deviceId = bootstrap[i].getDeviceId(); // imei
328

329             if (numDevices == 1) {
330                 applicationInfo = info;
331             } else {
332                 applicationInfo = info + ":" + phoneNumber;
333             }
334             // creates device and syncMLDM message
335
syncMLDMMessage[i] = preBootstrapOperation(deviceId,
336                                                        phoneNumber,
337                                                        username,
338                                                        password,
339                                                        applicationInfo,
340                                                        updateDeviceInfo);
341         }
342
343         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
344
345         BootStrap[] bootstrapToReturn =
346         notificationEngine.prepareBootstrap(messageType,
347                                                     bootstrap,
348                                                     serverUri,
349                                                     syncMLDMMessage,
350                                                     info);
351
352         return bootstrapToReturn;
353     }
354
355
356     /**
357      * Bootstrap devices with digest
358      * @param info the session id
359      * @param type the type of the message
360      * @param bootstrap the bootstrap info
361      */

362     public void sendBootstrapWithDigest(int messageType,
363                                         int transportType,
364                                         String JavaDoc info,
365                                         BootStrap[] bootstrap) throws NotificationException {
366
367         if (log.isLoggable(Level.INFO)) {
368             log.info("Send bootstrap message in ManagementEngine for " + bootstrap.length +
369                      " devices");
370         }
371
372         String JavaDoc serverUri = config.getStringValue(ConfigurationConstants.CFG_SERVER_URI);
373
374         int numDevices = bootstrap.length;
375         String JavaDoc[] phoneNumbers = new String JavaDoc[numDevices];
376         String JavaDoc[] macs = new String JavaDoc[numDevices];
377         int[] authMethods = new int[numDevices];
378         byte[][] messages = new byte[numDevices][];
379
380         for (int i = 0; i < numDevices; i++) {
381             verifyBootStrap(bootstrap[i]);
382
383             phoneNumbers[i] = bootstrap[i].getMsisdn();
384             messages[i] = bootstrap[i].getBootstrapMessage();
385             macs[i] = bootstrap[i].getDigest();
386             authMethods[i] = bootstrap[i].getAuthMethod();
387         }
388
389         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
390
391         notificationEngine.sendBootstrapMessages(messageType,
392                                                  transportType,
393                                                  phoneNumbers,
394                                                  macs,
395                                                  authMethods,
396                                                  messages,
397                                                  info);
398
399     }
400
401     /**
402      * Sends a notification message to the device with the given phoneNumber
403      *
404      * @param messageType the type of the notification message as define in <code>NotificationConstants<code>
405      * @param transportType the type of the transport as define in <code>NotificationConstants<code>
406      * @param phoneNumber the phone number
407      * @param operation application specific management operation
408      * @param info application specific info
409      *
410      * @throws NotificationException if an error occurs
411      */

412     public void sendNotification( int messageType ,
413                                   int transportType,
414                                   String JavaDoc phoneNumber ,
415                                   String JavaDoc operation ,
416                                   String JavaDoc info )
417         throws NotificationException {
418
419         if (log.isLoggable(Level.INFO)) {
420             log.info("Executing sendNotification with: " +
421                      "\n\tmessageType: " + messageType +
422                      "\n\ttransportType: " + transportType +
423                      "\n\tphoneNumber: " + phoneNumber +
424                      "\n\toperation: " + operation +
425                      "\n\tinfo: " + info
426                      );
427         }
428
429         int sessionId = getNextNotificationSessionIds(1)[0];
430
431         String JavaDoc serverId = config.getStringValue(ConfigurationConstants.CFG_SERVER_ID);
432
433         Sync4jDevice device = getSync4jDevice(phoneNumber);
434
435         if (device == null) {
436             throw new NotificationException("Device with phone number '" + phoneNumber + "' not found");
437         }
438
439         String JavaDoc serverPassword = device.getServerPassword();
440         byte[] serverNonce = device.getServerNonce();
441
442         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
443
444         notificationEngine.sendNotificationMessage(
445             messageType, transportType, sessionId, phoneNumber,
446             info, serverId, serverPassword, serverNonce
447         );
448
449         //
450
// After notification, we have to initialize the device managemnent
451
// state for this session. We do not have the device id, therefore we
452
// set the session id as identifier. This will be matched by the
453
// engine in resolveDMState(). See "Sync4j Device Management Architecture"
454
// for details.
455
//
456
DeviceDMState dms = new DeviceDMState();
457
458         dms.mssid = String.valueOf(sessionId) ;
459         dms.state = DeviceDMState.STATE_NOTIFIED ;
460         dms.start = new Date JavaDoc(System.currentTimeMillis());
461         dms.operation = operation ;
462         dms.info = info ;
463         dms.deviceId = device.getDeviceId() ;
464
465         storeDeviceDMState(dms);
466     }
467
468     /**
469      * Sends a notification message to the devices with the given phoneNumbers
470      *
471      * @param messageType the type of the notification message as define in <code>NotificationConstants<code>
472      * @param transportType the type of the transport as define in <code>NotificationConstants<code>
473      * @param phoneNumbers the phone numbers list
474      * @param operation application specific management operation
475      * @param info application specific info
476      *
477      * @throws NotificationException if an error occurs
478      */

479     public void sendNotification(int messageType ,
480                                  int transportType,
481                                  String JavaDoc[] phoneNumbers ,
482                                  String JavaDoc operation ,
483                                  String JavaDoc info )
484         throws NotificationException {
485
486         if (log.isLoggable(Level.INFO)) {
487             log.info("Executing sendNotification to " + phoneNumbers.length + " devices with: " +
488                      "\n\tmessageType: " + messageType +
489                      "\n\ttransportType: " + transportType +
490                      "\n\toperation: " + operation +
491                      "\n\tinfo: " + info
492                      );
493         }
494
495         int numDevices = phoneNumbers.length;
496         int[] sessionIds = getNextNotificationSessionIds(numDevices);
497
498         String JavaDoc serverId = config.getStringValue(ConfigurationConstants.CFG_SERVER_ID);
499
500         Sync4jDevice device = null;
501         DeviceDMState dms = null;
502
503         String JavaDoc[] serverPasswords = new String JavaDoc[numDevices];
504         byte[][] serverNonces = new byte[numDevices][];
505         for (int i=0; i<numDevices; i++) {
506
507             device = getSync4jDevice(phoneNumbers[i]);
508
509             if (device == null) {
510                 throw new NotificationException("Device with phone number '" + phoneNumbers[i] + "' not found");
511             }
512
513             serverPasswords[i] = device.getServerPassword();
514             serverNonces[i] = device.getServerNonce();
515
516             //
517
// Before notification, we have to initialize the device managemnent
518
// state for this session. We do not have the device id, therefore we
519
// set the session id as identifier. This will be matched by the
520
// engine in resolveDMState(). See "Sync4j Device Management Architecture"
521
// for details.
522
//
523
dms = new DeviceDMState();
524
525             dms.mssid = String.valueOf(sessionIds[i]);
526             dms.state = DeviceDMState.STATE_NOTIFIED;
527             dms.start = new Date JavaDoc(System.currentTimeMillis());
528             dms.operation = operation;
529             dms.info = info + ":" + phoneNumbers[i];
530             dms.deviceId = device.getDeviceId();
531             storeDeviceDMState(dms);
532         }
533
534         NotificationEngine notificationEngine = new NotificationEngineImpl(config);
535         notificationEngine.sendNotificationMessages(messageType,
536                                                     transportType,
537                                                     sessionIds,
538                                                     phoneNumbers,
539                                                     info,
540                                                     serverId,
541                                                     serverPasswords,
542                                                     serverNonces);
543     }
544
545
546     /**
547      * Stores the given DeviceDMState.
548      *
549      * @param d the DeviceDMState
550      *
551      */

552     public void storeDeviceDMState(DeviceDMState d) {
553         try{
554             getStore().store(d);
555         } catch (PersistentStoreException e) {
556             if (log.isLoggable(Level.SEVERE)) {
557                 log.severe( "Error storing the device DM state "
558                             + d.toString()
559                             + ": "
560                             + e.getMessage()
561                             );
562                 log.throwing(getClass().getName(), "storeDeviceDMState", e);
563             }
564         }
565     }
566
567     /**
568      * Reads the given DeviceDMState.
569      *
570      * @param d the DeviceDMState
571      *
572      * @throws NotFoundException if the DeviceDMState is not found
573      */

574     public void readDeviceDMState(DeviceDMState d)
575         throws NotFoundException {
576         WhereClause where = new WhereClause(
577             DeviceDMState.PROPERTY_DEVICE_ID,
578             new String JavaDoc[] { d.deviceId },
579             WhereClause.OPT_EQ,
580             true
581         );
582
583         try{
584             DeviceDMState[] rows = (DeviceDMState[])getStore().read(d, where);
585
586             if (rows.length == 0) {
587                 throw new NotFoundException("No device DM state found with deviceId " + d.deviceId );
588             }
589
590             d.copyFrom(rows[0]);
591         } catch (NotFoundException e) {
592             throw e;
593         } catch (PersistentStoreException e) {
594             if (log.isLoggable(Level.SEVERE)) {
595                 log.severe( "Error reading the device DM state "
596                             + d.toString()
597                             + ": "
598                             + e.getMessage()
599                             );
600                 log.throwing(getClass().getName(), "storeDeviceDMState", e);
601             }
602         }
603     }
604
605     /**
606      * Deletes the given DeviceDMState.
607      *
608      * @param d the DeviceDMState
609      *
610      */

611     public void deleteDeviceDMState(DeviceDMState d) {
612         try {
613             getStore().delete(d);
614         } catch (PersistentStoreException e) {
615             if (log.isLoggable(Level.SEVERE)) {
616                 log.severe( "Error deleting the device DM state "
617                             + d.toString()
618                             + ": "
619                             + e.getMessage()
620                             );
621                 log.throwing(getClass().getName(), "storeDeviceDMState", e);
622             }
623         }
624     }
625
626     /**
627      * This method tries to associate the given DeviceDMState instance to an
628      * existing device management state. The algorithm is as follows:
629      * <ul>
630      * <li>look for a <i>notified</i> device with session id equal to dms.mssid</li>
631      * <li>IF a device management session is found THEN fill dms with the
632      * retrieved values and return true</li>
633      * <li>ELSE look for any other state whose associated device is equal to dms.deviceId</li>
634      * <li>IF a device management session is found THEN fill dms with the
635      * retrieved values and return true.
636      * <li>return false in any other case
637      * </ul>
638      *
639      * @param dms the DM session state
640      * @param deviceConnected the device connected
641      * @return true if the device management session was resolved, false otherwise.
642      */

643     public boolean resolveDMState(DeviceDMState dms, Sync4jDevice deviceConnected) {
644         //
645
// 1. Search for notified devices first
646
//
647
WhereClause wc1 = new WhereClause(
648             DeviceDMState.PROPERTY_STATE,
649             new String JavaDoc[] { String.valueOf((char)DeviceDMState.STATE_NOTIFIED) },
650             WhereClause.OPT_EQ,
651             true
652         );
653
654         WhereClause wc2 = new WhereClause(
655             DeviceDMState.PROPERTY_SESSION_ID,
656             new String JavaDoc[] {dms.mssid},
657             WhereClause.OPT_EQ,
658             true
659         );
660
661         LogicalClause select = new LogicalClause(
662             LogicalClause.OPT_AND,
663             new Clause[] {wc1, wc2}
664         );
665
666         dms.state = DeviceDMState.STATE_NOTIFIED;
667
668         try {
669             DeviceDMState[] rows = (DeviceDMState[])getStore().read(dms, select);
670
671             if (rows.length > 0) {
672                 //
673
// The device id is null in the database, therefore we have to
674
// overwrite it after copy
675
//
676
String JavaDoc deviceConnectedId = dms.deviceId;
677                 dms.copyFrom(rows[0]);
678
679                 if (!dms.deviceId.equalsIgnoreCase(deviceConnectedId)) {
680                     //
681
// The device connected is different from the device notified
682
// So, we swap the device id (SIM Swap)
683
//
684
Sync4jDevice deviceNotified = new Sync4jDevice(dms.deviceId);
685
686                     store.read(deviceNotified);
687                     store.read(deviceConnected);
688
689                     String JavaDoc descriptionDeviceNotified = deviceNotified.getDescription();
690                     String JavaDoc descriptionDeviceConnected = deviceConnected.getDescription();
691
692                     deviceNotified.setDescription(descriptionDeviceConnected);
693                     deviceConnected.setDescription(descriptionDeviceNotified);
694
695                     store.store(deviceNotified);
696                     store.store(deviceConnected);
697                 }
698                 dms.deviceId = deviceConnectedId;
699
700                 return true;
701             }
702         } catch (PersistentStoreException e) {
703             if (log.isLoggable(Level.SEVERE)) {
704                 log.severe("Error reading the device DM state "
705                            + dms.toString()
706                            + ": "
707                            + e.getMessage()
708                            );
709                 log.throwing(getClass().getName(), "resolveDMState", e);
710             }
711
712             return false;
713         }
714
715         //
716
// 2. Search for any other association
717
//
718
wc1 = new WhereClause(
719             DeviceDMState.PROPERTY_STATE,
720             new String JavaDoc[] {String.valueOf((char)DeviceDMState.STATE_IN_PROGRESS)},
721             WhereClause.OPT_EQ,
722             true
723         );
724
725         wc2 = new WhereClause(
726             DeviceDMState.PROPERTY_DEVICE_ID,
727             new String JavaDoc[] {dms.deviceId},
728             WhereClause.OPT_EQ,
729             true
730         );
731
732         select = new LogicalClause(
733             LogicalClause.OPT_AND,
734             new Clause[] { wc1, wc2}
735         );
736
737         try {
738             DeviceDMState[] rows = (DeviceDMState[])getStore().read(dms, select);
739
740             if (rows.length > 0) {
741                 dms.copyFrom(rows[0]);
742
743                 return true;
744             }
745         } catch (PersistentStoreException e) {
746             if (log.isLoggable(Level.SEVERE)) {
747                 log.severe( "Error reading the device DM state "
748                           + dms.toString()
749                           + ": "
750                           + e.getMessage()
751                           );
752                 log.throwing(getClass().getName(), "resolveDMState", e);
753             }
754         }
755
756         return false;
757     }
758
759     // --------------------------------------------------------- Private Methods
760

761     /**
762      * Completes the SyncMLDM bootstrap message with the dinamic information as username, password.
763      * @param bootstrapMessage the bootstrap message to complete
764      * @param serverUri the server uri to set in the bootstrap message as address
765      * @param serverId the server id to set in the bootstrap message
766      * @param serverPwd the server password to set in the bootstrap message
767      * @param serverPort the server port
768      * @param username the user name to set in the bootstrap message
769      * @param password the password to set in the bootstrap message
770      * @param serverNonce the nonce that the server will use to authenticate itself to the client
771      * @param clienteNonce the nonce that the client will use to authenticate itself to the server
772      */

773     private void completeSyncMLDMBootstrapMessage(SyncMLDM bootstrapMessage,
774                                                   URI JavaDoc serverUri,
775                                                   String JavaDoc serverId,
776                                                   String JavaDoc serverPwd, int serverPort,
777                                                   String JavaDoc username, String JavaDoc password,
778                                                   byte[] serverNonce, byte[] clienteNonce) {
779
780         String JavaDoc host = serverUri.getHost();
781         String JavaDoc path = serverUri.getPath();
782         String JavaDoc query = serverUri.getQuery();
783
784         DMAcc dmAcc = bootstrapMessage.getDmAcc();
785         Map JavaDoc dmAccounts = dmAcc.getDMAccounts();
786
787         Iterator JavaDoc keys = dmAccounts.keySet().iterator();
788         String JavaDoc accountName = (String JavaDoc)keys.next();
789
790         DMAccount dmAccount = dmAcc.getDMAccount(accountName);
791
792         // set the dmAccount in the dmAcc with the correct username
793
// (the xml bean contains a temporary account name)
794

795         dmAcc.renameDMAccount(accountName, username);
796
797         dmAccount.setServerId(serverId);
798         dmAccount.setServerPassword(serverPwd);
799         dmAccount.setServerNonce(serverNonce);
800
801         dmAccount.setName(username);
802         dmAccount.setUserName(username);
803         dmAccount.setClientPassword(password);
804         dmAccount.setClientNonce(clienteNonce);
805
806         dmAccount.setAddress("http://" + host + path + (query != null ? ("?" + query) : "") );
807         dmAccount.setAddressType(DEFAULT_ADDRESS_TYPE);
808
809         if (serverPort == -1) {
810             dmAccount.setPortNumber(DEFAULT_PORT_NUMBER);
811         } else {
812             dmAccount.setPortNumber(serverPort);
813         }
814
815         dmAccount.setAuthPref(officer.getAuthType());
816     }
817
818     /**
819      * Create the sync user, sync4j device and sync4j principal with the given data.
820      * The username of the user is calculate as:<p/>b64(md5(user':'password))
821      *
822      * @param deviceId String
823      * @param username String
824      * @param password String
825      * @param phoneNumber String
826      * @param updateDeviceInfo if the device already exist and this parameter is true,
827      * the client nonce, server nonce and server password are regenerated. If this parameter
828      * is false, no new info are generated and the previous data are used.
829      * @throws PersistentStoreException
830      * @return Sync4jDevice
831      */

832     private Sync4jDevice createSync4jUserDevicePrincipal(String JavaDoc deviceId,
833                                                          String JavaDoc username,
834                                                          String JavaDoc password,
835                                                          String JavaDoc phoneNumber,
836                                                          boolean updateDeviceInfo)
837         throws PersistentStoreException {
838
839         // b64(md5(user':'password))
840
String JavaDoc userDigest = calculateDigest(username, password);
841
842         Sync4jDevice device = new Sync4jDevice(deviceId);
843         boolean deviceFound = false;
844
845         try {
846             readDevice(device);
847             deviceFound = true;
848         } catch (NotFoundException ex) {
849             deviceFound = false;
850         }
851
852         if (!deviceFound) {
853             //
854
// Checks if there is already a device with this phone number
855
//
856
Sync4jDevice[] devices = getSync4jDevices(phoneNumber);
857             if (devices.length > 0) {
858                 //
859
// We are in this case:
860
//
861
// 1) Bootstrap device A, with SIM 1
862
// 2) Put SIM 1 in another non-Bootstrapped device B.
863
// 3) try to Bootstrap device B, SIM 1
864
//
865
// For all devices with this phone number,
866
// sets the phone number to empty string (so only one device
867
// has this phone number)
868
//
869
for (int i=0; i<devices.length; i++) {
870                     getStore().read(devices[i]);
871                     devices[i].setDescription("");
872                     getStore().store(devices[i]);
873                 }
874             }
875
876
877             //
878
// create and store new user
879
//
880
SyncUser user = new SyncUser();
881             user.setUsername(userDigest);
882             user.setPassword(" ");
883             user.setFirstname("");
884             user.setLastname(phoneNumber);
885             String JavaDoc[] role = {"sync_user"};
886             user.setRoles(role);
887             dbUserManager.insertUser(user);
888             //
889
// create and store new device
890
//
891
device = new Sync4jDevice();
892             device.setDeviceId(deviceId);
893             device.setType("phone");
894             device.setDescription(phoneNumber);
895             device.setDigest(userDigest);
896
897             byte[] serverNonce = MD5.getNextNonce();
898             byte[] clientNonce = MD5.getNextNonce();
899             device.setServerNonce(serverNonce);
900             device.setClientNonce(clientNonce);
901
902             String JavaDoc serverPassword = SecurityTools.getRandomPassword();
903             device.setServerPassword(serverPassword);
904
905             storeDevice(device);
906             //
907
// create and store new principal
908
Sync4jPrincipal principal = new Sync4jPrincipal(userDigest, deviceId);
909             getStore().store(principal);
910             return device;
911         }
912
913         //
914
// Device found
915
//
916

917         //
918
// Checks if there is already a device with this phone number
919
//
920
Sync4jDevice[] devices = getSync4jDevices(phoneNumber);
921         if (devices.length == 1) {
922             //
923
// We are in this case:
924
// 1) Bootstrap device A, with SIM 1
925
// 2) Bootstrap device B, with SIM 2
926
// 3) Put SIM 1 in device B
927
// 4) Try to re-Bootstrap device B, SIM 1
928
//
929
// Swap the phone number
930
// (so there isn't a phone number associated a more devices)
931
// device A <--> SIM 2
932
// device B <--> SIM 1
933
//
934
Sync4jDevice deviceA = null;
935             Sync4jDevice deviceB = device;
936
937             getStore().read(devices[0]);
938
939             deviceA = devices[0];
940
941             // Sets the phone number of the SIM 2 (device.getDescription())
942
// (device == device B)
943
deviceA.setDescription(deviceB.getDescription());
944             getStore().store(deviceA);
945
946         } else if (devices.length > 1) {
947             //
948
// This condition indicates a error because not more that one device
949
// can have the same phone number
950
//
951
throw new IllegalStateException JavaDoc("There are more devices with phone number '" +
952                                             phoneNumber + "' then the swap of this is not possible");
953         }
954
955
956         if (updateDeviceInfo) {
957             byte[] serverNonce = MD5.getNextNonce();
958             byte[] clientNonce = MD5.getNextNonce();
959             device.setServerNonce(serverNonce);
960             device.setClientNonce(clientNonce);
961
962             String JavaDoc serverPassword = SecurityTools.getRandomPassword();
963             device.setServerPassword(serverPassword);
964         }
965
966         if (!phoneNumber.equals(device.getDescription()) || updateDeviceInfo) {
967             //
968
// The device is already in the db but has a different phone number
969
// or we have updated device info. However we set the phone number because
970
// or is the same number or it must be updated.
971
//
972
device.setDescription(phoneNumber);
973             storeDevice(device);
974         }
975
976
977         String JavaDoc deviceDigest = device.getDigest();
978
979         //
980
// Checks if the device has been bootstrapped with other username and password (digest)
981
//
982
if (StringUtils.equals(deviceDigest, userDigest)) {
983             return device;
984         }
985
986         //
987
// The device has been bootstrapped with other username and password (digest).
988
// Then:
989
// 1. update the device digest (and store the device)
990
// 2. create new user with the new digest
991
// 3. create new principal with the new user and the current device
992
// 4. delete the previous principal
993
// 5. delete the previous user (if not principal is associated to)
994
//
995

996         //
997
// 1. Update the digest of the device with new digest
998
//
999
device.setDigest(userDigest);
1000        storeDevice(device);
1001
1002        //
1003
// 2. Create new user with the new digest
1004
//
1005
SyncUser user = new SyncUser();
1006        user.setUsername(userDigest);
1007        user.setPassword(" ");
1008        user.setFirstname("");
1009        user.setLastname(phoneNumber);
1010        String JavaDoc[] role = {"sync_user"};
1011        user.setRoles(role);
1012        dbUserManager.insertUser(user);
1013
1014        //
1015
// 3. Create new principal with the new user and the current device
1016
//
1017
Sync4jPrincipal principal = new Sync4jPrincipal(userDigest, deviceId);
1018        getStore().store(principal);
1019
1020        //
1021
// 4. Delete the previous principal
1022
//
1023
Sync4jPrincipal previousPrincipal = new Sync4jPrincipal(deviceDigest, deviceId);
1024        getStore().read(previousPrincipal);
1025        getStore().delete(previousPrincipal);
1026
1027        //
1028
// Checks if the previous user is associated to other principal
1029
//
1030
WhereClause wc1 = new WhereClause(
1031                             Sync4jPrincipal.PROPERTY_USERNAME,
1032                             new String JavaDoc[] { deviceDigest },
1033                             WhereClause.OPT_EQ,
1034                             true
1035                             );
1036
1037        Sync4jPrincipal[] principals = (Sync4jPrincipal[])(getStore().read(principal, wc1));
1038
1039        if (principals.length == 0) {
1040            //
1041
// 5. Delete the previous user (no principal associated)
1042
//
1043
SyncUser previousUser = new SyncUser();
1044            previousUser.setUsername(deviceDigest);
1045            dbUserManager.deleteUser(previousUser);
1046        }
1047        return device;
1048    }
1049
1050    /**
1051     * Returns a digest for the given username and password.<br/>
1052     * The digest is calculated as b64(md5(user':'password))
1053     *
1054     * @param username String
1055     * @param password String
1056     * @return String the digest
1057     */

1058    private String JavaDoc calculateDigest(String JavaDoc username, String JavaDoc password) {
1059        String JavaDoc cred = username + ":" + password;
1060
1061        byte[] md5 = null;
1062
1063        md5 = MD5.digest(cred.getBytes()); // md5(user:password)
1064

1065        byte[] digest = Base64.encode(md5);
1066
1067        return new String JavaDoc(digest);
1068    }
1069
1070    /**
1071     * Reads the device with the given phoneNumber
1072     * @param phoneNumber String
1073     * @throws NotificationException if there are more devices with the same phoneNumber
1074     * @return Sync4jDevice the device with the given phoneNumber
1075     */

1076    private Sync4jDevice getSync4jDevice(String JavaDoc phoneNumber) throws NotificationException {
1077
1078        // search device with description=phoneNumber (case insensitive)
1079
Clause clause = new WhereClause(Sync4jDevice.PROPERTY_DESCRIPTION,
1080                                         new String JavaDoc[] {phoneNumber},
1081                                         WhereClause.OPT_EQ,
1082                                         true);
1083
1084        Sync4jDevice[] devices = null;
1085
1086
1087        try {
1088            // returns a Sync4jDevice[] with only id,description and type
1089
devices = (Sync4jDevice[]) getStore().read(new Sync4jDevice(), clause);
1090
1091        } catch (PersistentStoreException e) {
1092            throw new NotificationException("Unable to get device information", e);
1093        }
1094
1095        if (devices == null) {
1096            return null;
1097        } else if (devices.length == 0) {
1098            throw new NotificationException("Device with phone number '" + phoneNumber + "' not found");
1099        } else if (devices.length > 1) {
1100            throw new NotificationException("There are more devices with the same phone number [" + phoneNumber + "]");
1101        }
1102
1103        try {
1104            // get all device info
1105
readDevice(devices[0]);
1106
1107        } catch (PersistentStoreException e) {
1108            throw new NotificationException("Unable to get device information", e);
1109        }
1110
1111        return devices[0];
1112    }
1113
1114    /**
1115     * Reads the devices with the given phoneNumber
1116     * @param phoneNumber String
1117     * @return Sync4jDevice[] the devices with the given phoneNumber
1118     */

1119    private Sync4jDevice[] getSync4jDevices(String JavaDoc phoneNumber) throws PersistentStoreException {
1120
1121        // search device with description=phoneNumber (case insensitive)
1122
Clause clause = new WhereClause(Sync4jDevice.PROPERTY_DESCRIPTION,
1123                                         new String JavaDoc[] {phoneNumber},
1124                                         WhereClause.OPT_EQ,
1125                                         true);
1126
1127        Sync4jDevice[] devices = null;
1128
1129        // returns a Sync4jDevice[] with only id,description and type
1130
devices = (Sync4jDevice[]) getStore().read(new Sync4jDevice(), clause);
1131
1132        return devices;
1133    }
1134
1135    /**
1136     * Performs the operations necessary to bootstrap a device:
1137     * <ui>
1138     * <li>Creates a new device with the given parameters
1139     * <li>Creates and returns the <code>SyncMLDM</code> with the bootstrap info
1140     * </ui>
1141     * @param deviceId String
1142     * @param phoneNumber String
1143     * @param username String
1144     * @param password String
1145     * @param info String
1146     * @param updateDeviceInfo if the device already exist and this parameter is true,
1147     * the client nonce, server nonce and server password are regenerated. If this parameter
1148     * is false, no new info are generated and the previous data are used.
1149     * @throws NotificationException
1150     * @return SyncMLDM
1151     */

1152    private SyncMLDM preBootstrapOperation(String JavaDoc deviceId,
1153                                           String JavaDoc phoneNumber,
1154                                           String JavaDoc username,
1155                                           String JavaDoc password,
1156                                           String JavaDoc info,
1157                                           boolean updateDeviceInfo) throws NotificationException {
1158
1159        // read the SyncMLDM object from the config path
1160
ClassLoader JavaDoc classLoader = config.getClassLoader();
1161
1162        SyncMLDM syncMLDMBootstrapMessage = null;
1163
1164        try {
1165            syncMLDMBootstrapMessage =
1166                (SyncMLDM)BeanFactory.getBeanInstance(classLoader, SYNCML_DM_BOOTSTRAP_MESSAGE);
1167        } catch (Exception JavaDoc ex) {
1168            log.throwing(getClass().getName(), "bootstrap", ex);
1169            throw new NotificationException("Error reading bootstrap message", ex);
1170        }
1171
1172        // verify syncMLDM. Each bootstrap message MUST only contain one dm account
1173
//if (syncMLDMBootstrapMessage.getDmAcc().getDMAccounts().size() != 1) {
1174
// throw new NotificationException("Bootstrap message MUST only contain one dm account");
1175
//}
1176

1177        // generate user, device, principal
1178
Sync4jDevice device = null;
1179
1180        try {
1181            device = createSync4jUserDevicePrincipal(deviceId,
1182                                                     username,
1183                                                     password,
1184                                                     phoneNumber,
1185                                                     updateDeviceInfo);
1186        } catch (PersistentStoreException e) {
1187            log.throwing("ManagementEngine", "bootstrap", e);
1188            throw new NotificationException("Error during store sync object", e);
1189        }
1190
1191        try {
1192            createDMStateToExecuteAfterBootstrap(deviceId, info);
1193        } catch (PersistentStoreException e) {
1194            log.throwing("ManagementEngine", "bootstrap", e);
1195            throw new NotificationException("Error during create dmstate", e);
1196        }
1197
1198        byte[] serverNonce = device.getServerNonce();
1199        byte[] clientNonce = device.getClientNonce();
1200
1201        String JavaDoc serverUri = config.getStringValue(ConfigurationConstants.CFG_SERVER_URI);
1202
1203        int serverPort = -1;
1204
1205        URI JavaDoc uri = null;
1206
1207        try {
1208            uri = new URI JavaDoc(serverUri);
1209            serverPort = uri.getPort();
1210        } catch (URISyntaxException JavaDoc e) {
1211            throw new NotificationException("The server uri isn't a valid uri", e);
1212        }
1213
1214        String JavaDoc serverId = config.getStringValue(ConfigurationConstants.CFG_SERVER_ID);
1215        String JavaDoc serverPassword = device.getServerPassword();
1216
1217        completeSyncMLDMBootstrapMessage(syncMLDMBootstrapMessage,
1218                                         uri,
1219                                         serverId,
1220                                         serverPassword,
1221                                         serverPort,
1222                                         username,
1223                                         password,
1224                                         serverNonce,
1225                                         clientNonce);
1226
1227        return syncMLDMBootstrapMessage;
1228    }
1229
1230    /**
1231     * Returns an array of the next session identifiers to use in notification message
1232     *
1233     * @throws NotificationException if error occurs reading the counter from
1234     * the persistent store
1235     * @return int[]
1236     */

1237    private synchronized int[] getNextNotificationSessionIds(int numOfSessionsId)
1238        throws NotificationException {
1239
1240        int counter = 0;
1241        try {
1242            counter = getStore().readCounter(NS_MSSID, numOfSessionsId);
1243        } catch (PersistentStoreException ex) {
1244            throw new NotificationException("Error reading session ids");
1245        }
1246
1247        int[] sessionIds = new int[numOfSessionsId];
1248        for (int i = 0; i<numOfSessionsId; i++) {
1249            sessionIds[i] = (counter + i) & 0xffff; // 16 bit integer
1250
}
1251        return sessionIds;
1252    }
1253
1254    /**
1255     * Verify if the given bootstrap is correct (from the semantic point of view)
1256     * @param bootstrap BootStrap
1257     * @throws NotificationException if the bootstrap is not correct
1258     */

1259    private void verifyBootStrap(BootStrap bootstrap) throws NotificationException {
1260
1261        if (bootstrap == null) {
1262            throw new NotificationException(
1263                    "Unable to send the bootstrap (is null)");
1264        }
1265
1266        String JavaDoc imsi = bootstrap.getImsi();
1267        String JavaDoc userPin = bootstrap.getUserPin();
1268        int authMethod = bootstrap.getAuthMethod();
1269        String JavaDoc digest = bootstrap.getDigest();
1270
1271        if (StringUtils.isNotEmpty(digest)) {
1272            return;
1273        }
1274
1275        if (authMethod == NotificationConstants.AUTH_METHOD_NETWPIN) {
1276            if (StringUtils.isEmpty(imsi)) {
1277                throw new NotificationException(
1278                    "Imsi can't be null using NETWPIN authentication method");
1279            }
1280        } else if (authMethod == NotificationConstants.AUTH_METHOD_USERPIN) {
1281            if (StringUtils.isEmpty(userPin) && StringUtils.isEmpty(imsi)) {
1282                throw new NotificationException(
1283                    "Userpin or imsi must be not null using USERPIN authentication method");
1284            }
1285        } else if (authMethod == NotificationConstants.AUTH_METHOD_USERNETWPIN) {
1286            if (StringUtils.isEmpty(imsi)) {
1287                throw new NotificationException(
1288                    "Imsi can't be null using USERNETWPIN authentication method");
1289            }
1290
1291            if (StringUtils.isEmpty(userPin)) {
1292                throw new NotificationException(
1293                    "Userpin can't be null using USERNETWPIN authentication method");
1294            }
1295        }
1296    }
1297
1298    /**
1299     * Creates a new DMState with the operation to execute after a bootstrap
1300     * @param deviceId the device id
1301     */

1302    private void createDMStateToExecuteAfterBootstrap(String JavaDoc deviceId, String JavaDoc info) throws
1303        PersistentStoreException {
1304
1305        DeviceDMState dmState = new DeviceDMState();
1306        dmState.deviceId = deviceId;
1307        dmState.state = DeviceDMState.STATE_IN_PROGRESS;
1308        dmState.operation = OPERATION_AFTER_BOOTSTRAP;
1309        dmState.info = info;
1310        store.store(dmState);
1311
1312    }
1313
1314}
Popular Tags