KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > sync4j > server > engine > Sync4jEngine


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 package sync4j.server.engine;
19
20 import java.security.Principal JavaDoc;
21 import java.sql.Timestamp JavaDoc;
22 import java.util.*;
23 import java.util.Date JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.logging.Level JavaDoc;
26 import java.util.logging.Logger JavaDoc;
27
28 import sync4j.framework.config.ConfigurationConstants;
29 import sync4j.framework.core.*;
30 import sync4j.framework.database.Database;
31 import sync4j.framework.engine.*;
32 import sync4j.framework.engine.source.CommittableSyncSource;
33 import sync4j.framework.engine.source.MemorySyncSource;
34 import sync4j.framework.engine.source.SyncSource;
35 import sync4j.framework.engine.source.SyncSourceException;
36 import sync4j.framework.logging.Sync4jLogger;
37 import sync4j.framework.protocol.CommandIdGenerator;
38 import sync4j.framework.security.Officer;
39 import sync4j.framework.security.Sync4jPrincipal;
40 import sync4j.framework.server.*;
41 import sync4j.framework.server.session.SyncSourceState;
42 import sync4j.framework.server.store.NotFoundException;
43 import sync4j.framework.server.store.PersistentStore;
44 import sync4j.framework.server.store.PersistentStoreException;
45 import sync4j.framework.tools.Base64;
46 import sync4j.framework.tools.MD5;
47
48 import sync4j.server.config.Configuration;
49
50
51 /**
52  * This is the Sync4j implementation of the synchronization engine.
53  *
54  * LOG NAME: sync4j.engine
55  *
56  * @author Stefano Fornari @ Funambol
57  * @version $Id: Sync4jEngine.java,v 1.68 2005/05/09 15:13:28 luigiafassina Exp $
58  */

59 public class Sync4jEngine
60 extends AbstractSyncEngine
61 implements java.io.Serializable JavaDoc, ConfigurationConstants {
62
63     // --------------------------------------------------------------- Constants
64

65     public static final String JavaDoc LOG_NAME = "engine";
66
67     // ------------------------------------------------------------ Private data
68

69     /**
70      * The configuration properties
71      */

72     private Configuration configuration = null;
73
74     public Configuration getConfiguration() {
75         return configuration;
76     }
77
78     /**
79      * The security officer
80      */

81     private Officer officer = null;
82
83     public Officer getOfficer() {
84         return this.officer;
85     }
86
87     /**
88      * The underlying persistent store
89      */

90     private PersistentStore store = null;
91
92     /** Getter for property store.
93      * @return Value of property store.
94      *
95      */

96     public PersistentStore getStore() {
97         return store;
98     }
99
100     /** Setter for property store.
101      * @param store New value of property store.
102      *
103      */

104     public void setStore(PersistentStore store) {
105         this.store = store;
106     }
107
108     /**
109      * The sources this engine deals with
110      */

111     private ArrayList clientSources = new ArrayList();
112     private HashMap serverSources = new HashMap();
113
114     /**
115      * The modification objects of the last synchronization
116      */

117     private HashMap operations = null;
118
119     /**
120      * The status of the last synchronization modifications
121      */

122     private ArrayList status = null;
123
124     /**
125      * Logger
126      */

127     protected transient Logger JavaDoc log = null;
128
129     /**
130      * Sets the SyncML Protocol version. It is used to create the server
131      * credential with MD5 authentication.
132      */

133     private String JavaDoc syncMLVerProto = null;
134     public void setSyncMLVerProto(String JavaDoc syncMLVerProto) {
135         this.syncMLVerProto = syncMLVerProto;
136     }
137
138     private boolean isLastMessage = false;
139     public void setLastMessage(final boolean isLastMessage) {
140         this.isLastMessage = isLastMessage;
141     }
142
143     public boolean isLastMessage() {
144         return this.isLastMessage;
145     }
146
147     /**
148      * The SyncSoure states
149      */

150     private SyncSourceState syncSourceState;
151
152     /**
153      * The existing LUID-GUID mapping. It is used and modify by sync()
154      */

155     private Map JavaDoc clientMappings;
156
157     // ------------------------------------------------------------ Constructors
158

159     /**
160      * To allow deserializazion of subclasses.
161      */

162     protected Sync4jEngine() {
163         syncSourceState = new SyncSourceState();
164     }
165
166     /**
167      * Creates a new instance of Sync4jEngine. <p>
168      * NOTE: This is a sample implementation that deals with a single source on
169      * the file system.
170      *
171      * @throws ConfigurationException a runtime exception in case of misconfiguration
172      */

173     public Sync4jEngine(Configuration configuration) {
174         this();
175
176         log = Sync4jLogger.getLogger(LOG_NAME);
177
178         this.configuration = configuration;
179
180         //
181
// Set the underlying persistent store
182
//
183
store = configuration.getStore();
184
185         //
186
// Set the security officer
187
//
188
officer = configuration.getOfficer();
189
190         //
191
// Set the SyncStrategy object to use for comparisons
192
//
193
SyncStrategy strategy = configuration.getStrategy();
194
195         setStrategy(strategy);
196     }
197
198     // ------------------------------------------------ Configuration properties
199

200      /**
201      * The id generator
202      */

203     private CommandIdGenerator cmdIdGenerator = null;
204
205     /**
206      * Set the id generator for commands
207      *
208      * @param cmdIdGenerator the id generator
209      */

210     public void setCommandIdGenerator(CommandIdGenerator cmdIdGenerator) {
211         this.cmdIdGenerator = cmdIdGenerator;
212     }
213
214
215     /**
216      * Return the id generator
217      *
218      * @return the id generator
219      */

220     public CommandIdGenerator getCommandIdGenerator() {
221         return cmdIdGenerator;
222     }
223
224     // ------------------------------------------------------ Runtime properties
225

226     /**
227      * The database to be synchronized
228      */

229     private HashMap dbs = new HashMap();
230
231     public void setDbs(HashMap dbs) {
232         if (this.dbs != null) {
233             this.dbs.clear();
234         } else {
235             this.dbs = new HashMap(dbs.size());
236         }
237         this.dbs.putAll(dbs);
238     }
239
240     public void setDbs(Database[] dbs) {
241         if (this.dbs == null) {
242             this.dbs = new HashMap(dbs.length);
243         }
244
245         for (int i=0; ((dbs != null) && (i<dbs.length)); ++i) {
246             this.dbs.put(dbs[i].getName(), dbs[i]);
247         }
248     }
249
250     public Database[] getDbs() {
251         if (this.dbs == null) {
252             return new Database[0];
253         }
254
255         Iterator i = this.dbs.values().iterator();
256
257         Database[] ret = new Database[this.dbs.size()];
258
259         int j = 0;
260         while(i.hasNext()) {
261             ret[j++] = (Database)i.next();
262         }
263
264         return ret;
265     }
266
267     /**
268      * Timestamp of the beginning of this Sync
269      */

270     private Timestamp JavaDoc syncTimestamp;
271
272     /**
273      * Setter for syncTimestamp
274      *
275      * @param syncTimestamp new value
276      */

277     public void setSyncTimestamp(Date JavaDoc syncTimestamp) {
278         //
279
// Just for now: we have to change everything to Date
280
//
281
this.syncTimestamp = new Timestamp JavaDoc(syncTimestamp.getTime());
282     }
283
284
285     // ---------------------------------------------------------- Public methods
286

287     /**
288      * Fires and manages the synchronization process.
289      *
290      * @param principal the principal who is requesting the sync
291      *
292      * @throws Sync4jException in case of error
293      */

294     public void sync(final Sync4jPrincipal principal)
295     throws Sync4jException {
296         if (log.isLoggable(Level.INFO)) {
297             log.info("Starting synchronization ...");
298         }
299
300         SyncStrategy syncStrategy = getStrategy();
301
302         //
303
// Being this a concrete implementation of the Sync4j engine
304
// we know already that client sources are of type MemorySyncSource
305
//
306
MemorySyncSource clientSource = null;
307         SyncSource serverSource = null;
308         Database db = null;
309
310         //
311
// Now process clientSources
312
//
313

314         operations = new HashMap();
315         status = new ArrayList();
316
317         String JavaDoc uri = null;
318         ListIterator c = clientSources.listIterator();
319         while(c.hasNext()) {
320             clientSource = (MemorySyncSource)c.next();
321             uri = clientSource.getSourceURI();
322
323             if (log.isLoggable(Level.FINEST)) {
324                 log.finest( "SyncSource state of '"
325                           + uri
326                           + "' is "
327                           + syncSourceState.getStateName(uri)
328                           );
329             }
330
331             //
332
// If the source state is the error state, skip it
333
//
334
if (syncSourceState.isError(uri)) {
335                 continue;
336             }
337
338             serverSource = getServerSource(uri);
339             db = (Database)dbs.get(uri);
340
341             SyncSource[] sources = new SyncSource[] {
342                 serverSource, clientSource
343             };
344
345             //
346
// I can't remember why this is done. If somebody reads this and
347
// knows the reason, please UPDATE this comment!
348
//
349
Sync4jPrincipal p = (Sync4jPrincipal)db.getPrincipal();
350             p.setId(principal.getId());
351
352             //
353
// if the source is in the CONFIGURED state, call beginSync(). If
354
// beginSync() fails, skip
355
//
356
if (syncSourceState.isConfigured(uri)) {
357                 try {
358                     serverSource.beginSync(p, db.getMethod());
359                     syncSourceState.moveToSyncing(uri);
360                 } catch (SyncSourceException e) {
361                     syncSourceState.moveToError(uri);
362                     setDBError(db, e);
363                     continue;
364                 }
365             }
366
367             try {
368                 clientSource.commitSync();
369             } catch (SyncSourceException e) {
370                 //
371
// Hey, this should never happen!
372
//
373
syncSourceState.moveToError(uri);
374                 setDBError(db, e);
375                 continue;
376             }
377
378             //
379
// Note: in the case of fast sync, we do prepareSync and sync as
380
// soon as we can. If instead the slow sync is the case, we do the
381
// sync preparation and the sync only at the end of the process,
382
// when all items from the client have been sent.
383
// Plus, in the case of a slow sync, the status commands in response
384
// of a modification command is usually 200, since no real handling
385
// is done.
386
//
387
if ( (db.getMethod() == AlertCode.SLOW)
388                 || (db.getMethod() == AlertCode.REFRESH_FROM_SERVER)
389                 || (db.getStatusCode() == StatusCode.REFRESH_REQUIRED)) {
390
391                 try {
392                     operations.put(uri, syncStrategy.prepareSlowSync(sources, p, syncTimestamp, isLastMessage));
393
394                     status.addAll(
395                         Arrays.asList(
396                             syncStrategy.sync((SyncOperation[])operations.get(uri))
397                         )
398                     );
399                 } catch (SyncException e) {
400                     syncSourceState.moveToError(uri);
401                     setDBError(db, e);
402                     continue;
403                 }
404             } else {
405                 //
406
// Read the last timestamp from the persistent store, than
407
// prepare for fast synchronization
408
//
409
LastTimestamp last = new LastTimestamp(
410                                          p.getId() ,
411                                          db.getName()
412                                      );
413                 try {
414                     store.read(last);
415                 } catch (PersistentStoreException e) {
416                     throw new Sync4jException("Error reading last timestamp", e);
417                 }
418
419                 Timestamp JavaDoc since = new Timestamp JavaDoc(last.start);
420
421                 try {
422                     operations.put(uri, syncStrategy.prepareFastSync(sources, p, since, syncTimestamp, isLastMessage));
423                     //
424
// Now synchronize the sources
425
//
426
status.addAll(
427                         Arrays.asList(
428                             syncStrategy.sync((SyncOperation[])operations.get(uri))
429                         )
430                     );
431                 } catch (SyncException e) {
432                     syncSourceState.moveToError(uri);
433                     setDBError(db, e);
434                     continue;
435                 }
436             }
437
438             //
439
// Let's try to commit the sync now!
440
//
441
if (serverSource instanceof CommittableSyncSource) {
442                 try {
443                     ((CommittableSyncSource)serverSource).commitSync();
444                     syncSourceState.moveToCommitted(uri);
445                 } catch (SyncSourceException e) {
446                     syncSourceState.moveToError(uri);
447                     setDBError(db, e);
448                     continue;
449                 }
450             } else {
451                 //
452
// If the sync source is not committable, just make it move to
453
// the committed state since commit is implicit.
454
//
455
syncSourceState.moveToCommitted(uri);
456             }
457
458             //
459
// After processing, the database status code is set to OK
460
//
461
db.setStatusCode(StatusCode.OK);
462
463         } // next i (client source)
464

465         //
466
// Call endSync()
467
//
468
if (isLastMessage) {
469             while (c.hasPrevious()) {
470                 clientSource = (MemorySyncSource)c.previous();
471                 uri = clientSource.getSourceURI();
472                 serverSource = getServerSource(uri);
473
474                 try {
475                     serverSource.endSync(principal);
476                 } catch (SyncException e) {
477                     log.throwing(getClass().getName(), "sync", e);
478                     if (log.isLoggable(Level.SEVERE)) {
479                         log.severe(e.getMessage());
480                     }
481                 }
482             }
483         }
484
485         log.info("Ending synchronization ...");
486
487         try {
488             syncStrategy.endSync();
489         } catch (SyncException e) {
490             log.throwing(getClass().getName(), "sync", e);
491             if (log.isLoggable(Level.SEVERE)) {
492                 log.severe(e.getMessage());
493             }
494         }
495
496     }
497
498     /**
499      * Returns the operations of the last synchronization
500      *
501      * @param uri the URI of the source the operations are applied to
502      *
503      * @return the operations of the last synchronization
504      */

505     public SyncOperation[] getSyncOperations(String JavaDoc uri) {
506         return (SyncOperation[])operations.get(uri);
507     }
508
509     /**
510      * Resets the operations for the given source.
511      *
512      * @param uri source uri
513      */

514     public void resetSyncOperations(String JavaDoc uri) {
515         operations.put(uri, new SyncOperation[0]);
516     }
517
518     /**
519      * Returns the operation status of the operations executed in the last
520      * synchronization
521      *
522      * @param msgId the id of the SyncML message
523      *
524      * @return the operations status of the operations executed in the last
525                synchronization
526      */

527     public Status[] getModificationsStatusCommands(String JavaDoc msgId) {
528
529         if ((status == null) || (status.size() == 0)) {
530             return new Status[0];
531         }
532
533         SyncOperationStatus[] operationStatus =
534             (SyncOperationStatus[])status.toArray(new SyncOperationStatus[status.size()]);
535
536         return EngineHelper.generateStatusCommands(
537             operationStatus,
538             msgId,
539             cmdIdGenerator
540         );
541     }
542
543     /**
544      * Adds a new client source to the list of the sources the engine deals with.
545      *
546      * @param source the source to be added
547      */

548     public void addClientSource(SyncSource source) {
549         if (log.isLoggable(Level.FINEST)) {
550             log.finest("adding " + source);
551         }
552
553         clientSources.add(source);
554
555         syncSourceState.moveToConfigured(source.getSourceURI());
556     }
557
558     /**
559      * Returns the client sources
560      *
561      * @return the client sources
562      */

563     public List getClientSources() {
564         return clientSources;
565     }
566
567     /**
568      * Returns the client source with the given name if there is any.
569      *
570      * @param name the source name
571      *
572      * @return the client source with the given name if there is any, null otherwise
573      */

574     public SyncSource getClientSource(String JavaDoc name) {
575         Iterator i = clientSources.iterator();
576
577         SyncSource s = null;
578         while (i.hasNext()) {
579             s = (SyncSource)i.next();
580
581             if (s.getSourceURI().equals(name)) {
582                 return s;
583             }
584         }
585
586         return null;
587     }
588
589     /**
590      * Returns the status of the give client source (as stored in the
591      * corresponding database object).
592      *
593      * @param uri the source uri
594      *
595      * @return the status of the client source (as stored in the corresponding
596      * database object)
597      */

598     public int getClientSourceStatus(String JavaDoc uri) {
599         Database db = (Database)dbs.get(uri);
600
601         return db.getStatusCode();
602     }
603
604     /**
605      * Returns the status message of the given client source (as stored in the
606      * corresponding database object).
607      *
608      * @param uri the source uri
609      *
610      * @return the status message of the client source (as stored in the corresponding
611      * database object)
612      */

613     public String JavaDoc getClientStatusMessage(String JavaDoc uri) {
614         Database db = (Database)dbs.get(uri);
615
616         return db.getStatusMessage();
617     }
618
619     /**
620      * Returns the server source with the given name if there is any.
621      *
622      * @param name the source name
623      *
624      * @return the server source with the given name if there is any, null otherwise
625      */

626     public SyncSource getServerSource(String JavaDoc name) {
627         if (serverSources.containsKey(name)) {
628             return (SyncSource)serverSources.get(name);
629         }
630
631         Sync4jSource s = new Sync4jSource(name);
632
633         try {
634             store.read(s);
635         } catch (PersistentStoreException e) {
636             if (log.isLoggable(Level.SEVERE)) {
637                 log.severe( "Error reading source configuration for source "
638                           + name
639                           + ": "
640                           + e.getMessage()
641                           );
642             }
643
644             log.throwing(getClass().getName(), "getServerSource", e);
645         }
646
647         try {
648             SyncSource syncSource = (SyncSource)configuration.getBeanInstanceByName(s.getConfig());
649             serverSources.put(name, syncSource);
650             return syncSource;
651
652         } catch (Exception JavaDoc e){
653             String JavaDoc msg = "Unable to create sync source "
654                        + s
655                        + ": "
656                        + e.getMessage();
657
658             log.severe(msg);
659             log.throwing(getClass().getName(), "getServerSource", e);
660         }
661
662         return null;
663     }
664
665     /**
666      * Return the <i>DataStore</i> object corresponding to the given
667      * <i>Database</i> object.
668      *
669      * @param database the database to convert
670      *
671      * @return the <i>DataStore</i> object corresponding to the given
672      * <i>Database</i> object.
673      *
674      */

675     public DataStore databaseToDataStore(Database db) {
676         ContentTypeInfo contentTypeInfo
677                          = new ContentTypeInfo(db.getType(), "-" /* version */);
678
679         return new DataStore(
680                     new SourceRef(db.getSource()) ,
681                     null ,
682                     -1 ,
683                     contentTypeInfo ,
684                     new ContentTypeInfo[] { contentTypeInfo } ,
685                     contentTypeInfo ,
686                     new ContentTypeInfo[] { contentTypeInfo } ,
687                     new DSMem(false, -1, -1) ,
688                     new SyncCap(new SyncType[] { SyncType.SLOW })
689         );
690     }
691
692     /**
693      * Returns the content type capabilities of this sync engine.
694      *
695      * @return the content type capabilities of this sync engine.
696      */

697     public CTCap[] getContentTypeCapabilities() {
698         return new CTCap[0];
699     }
700
701     /**
702      * Returns the extensions of this sync engine.
703      *
704      * @return the extensions of this sync engine.
705      */

706     public Ext[] getExtensions() {
707         return new Ext[0];
708     }
709
710     /**
711      * Returns the data store capabilities of the sync sources to synchronize.
712      *
713      * @return the data store capabilities of the sync sources to synchronize.
714      */

715     public DataStore[] getDatastores() {
716         DataStore[] ds = null;
717         ArrayList al = new ArrayList();
718
719         Database db = null;
720         String JavaDoc uri = null;
721         Iterator i = dbs.values().iterator();
722         while(i.hasNext()) {
723             db = (Database)i.next();
724             uri = db.getName();
725
726             if (!db.isOkStatusCode()) {
727                 continue;
728             }
729
730             SyncSource ss = getServerSource(uri);
731
732             if ( ss != null) {
733                 al.add(EngineHelper.toDataStore(uri, ss.getInfo()));
734             }
735         }
736
737         int size = al.size();
738         if (size == 0) {
739             ds = new DataStore[0];
740         } else {
741         ds = (DataStore[])al.toArray(new DataStore[size]);
742         }
743
744
745         return ds;
746     }
747
748
749     /**
750      * Creates and returns a <i>DeviceInfo</i> object with the information
751      * extracted from the configuration object.
752      *
753      * @param verDTD the version of the DTD to use to encode the capabilities
754      *
755      * @return the engine capabilities
756      *
757      * @throws ConfigurationException a runtime exception in case of misconfiguration
758      */

759     public DevInf getServerCapabilities(VerDTD verDTD) {
760         DevInf devInf = configuration.getServerConfig().getServerInfo();
761         devInf.setDataStore(this.getDatastores());
762         devInf.setVerDTD(verDTD);
763         return devInf;
764     }
765
766     /**
767      * First of all, check the availablity and accessibility of the given
768      * databases. The state of the given database will change as described below
769      * (and in the same order):
770      * <ul>
771      * <li>The database status code is set to <i>StatusCode.OK</i> if the
772      * database is accessible, <i>StatusCode.NOT_FOUND</i> if the database
773      * is not found and <i>StatusCode.FORBIDDEN</i> if the principal is
774      * not allowed to access the database.
775      * <li>If the currently set last anchor does not match the last tag
776      * retrieved from the database (DBMS) and the requested alert code is
777      * not a refresh, the synchronization method is set to
778      * <i>AlertCode.SLOW</i>
779      * <li>The database server sync anchor is set to the server-side sync anchor
780      *
781      * @param principal the principal the is requesting db preparation
782      * @param dbs an array of <i>Database</i> objects - NULL
783      * @param next the sync timestamp of the current synchronization
784      *
785      */

786     public void prepareDatabases(Sync4jPrincipal principal,
787                                  Database[] dbs ,
788                                  SyncTimestamp next ) {
789         for (int i=0; ((dbs != null) && (i < dbs.length)); ++i) {
790             int statusCode = StatusCode.OK;
791
792             if (!checkServerDatabase(dbs[i])) {
793                 statusCode = StatusCode.NOT_FOUND;
794             } else if (!checkDatabasePermissions(dbs[i])) {
795                 statusCode = StatusCode.FORBIDDEN;
796             }
797
798             dbs[i].setStatusCode(statusCode);
799
800             //
801
// Now retrieve the last sync anchor
802
//
803
if (statusCode == StatusCode.OK) {
804                 LastTimestamp last = new LastTimestamp(
805                     principal.getId(),
806                     dbs[i].getName()
807                 );
808
809                 //
810
// Into tagServer there is the Next anchor sent by client: now
811
// this become a Last anchor of the server.
812
// Into tagClient there is the Next anchor sent by server: now
813
// this become a Last anchor of the client.
814
// The server anchor has like Last the Next sent from the client
815
// and like Next the Next generated at the start of sync.
816
//
817
try {
818                     store.read(last);
819                     dbs[i].setServerAnchor(new Anchor(last.tagClient, next.tagClient));
820                 } catch (NotFoundException e) {
821                     //
822
// No prev last sync! Create a new anchor that won't match
823
//
824
last.tagServer = next.tagClient;
825                     dbs[i].setServerAnchor(new Anchor(last.tagServer, next.tagClient));
826                 } catch(PersistentStoreException e) {
827                     log.severe("Unable to retrieve timestamp from store");
828                     log.throwing(getClass().getName(), "prepareDatabases", e);
829                 }
830
831                 if ( !(last.tagServer.equals(dbs[i].getAnchor().getLast()))
832                    && (dbs[i].getMethod() != AlertCode.REFRESH_FROM_SERVER)) {
833                     if (log.isLoggable(Level.FINEST)) {
834                         log.finest( "Forcing slow sync for database "
835                                 + dbs[i].getName()
836                                 );
837                         log.finest( "Server last: "
838                                 + last.tagServer
839                                 + "; client last: "
840                                 + dbs[i].getAnchor().getLast()
841                                 );
842                     }
843                     if (dbs[i].getMethod() != AlertCode.SLOW) {
844                         dbs[i].setStatusCode(StatusCode.REFRESH_REQUIRED);
845                     }
846                     dbs[i].setMethod(AlertCode.SLOW);
847                 }
848             }
849         }
850     }
851
852     /**
853      * Authenticates the given credential using <i>officer</i>.
854      *
855      * @param credential the credential to be authenticated
856      *
857      * @return true if the credential is authenticated, false otherwise
858      */

859     public boolean login(Cred credential) {
860         return officer.authenticate(credential);
861     }
862
863     /**
864      * Logs out the given credential using <i>officer</i>.
865      *
866      * @param credential the credential to be authenticated
867      *
868      * @return true if the credential is authenticated, false otherwise
869      */

870     public void logout(Cred credential) {
871         officer.unAuthenticate(credential);
872     }
873
874     /**
875      * Authorizes the given principal to access the given resource using
876      * <i>officer</i>.
877      *
878      * @param principal the entity to be authorized
879      * @param resource the name of the resource
880      *
881      * @return true if the principal is authorized, false otherwise
882      */

883     public boolean authorize(Principal JavaDoc principal, String JavaDoc resource) {
884         return officer.authorize(principal, resource);
885     }
886
887     /**
888      * Translates an array of <i>SyncOperation</i> objects to an array of
889      * <i>(Add,Delete,Replace)Command</i> objects. Only client side operations
890      * are translated.
891      *
892      * @param operations the operations to be translated
893      * @param uri the source uri
894      * @param slow is a slow sync?
895      *
896      * @return the sync4j commands corresponding to <i>operations</i>
897      */

898     public ItemizedCommand[] operationsToCommands(SyncOperation[] operations,
899                                                   String JavaDoc uri ,
900                                                   boolean slow ) {
901         ItemizedCommand[] commands =
902             EngineHelper.operationsToCommands( getMapping(uri),
903                                                operations ,
904                                                uri ,
905                                                cmdIdGenerator );
906         try {
907             EngineHelper.updateClientMappings(clientMappings, operations, slow);
908         } catch (Exception JavaDoc e) {
909             log.severe(e.getMessage());
910             log.throwing(getClass().getName(), "updateClientMappings", e);
911         }
912
913         return commands;
914     }
915
916
917     /**
918      * Updates the LUID-GUIDmapping for the client modifications (that is the
919      * items directlry inserted or removed by the server).
920      *
921      * @param slowSync is a slow sync in progress?
922      *
923      */

924     public void updateServerMappings(boolean slowSync) {
925         SyncOperationStatus[] operationStatus =
926             (SyncOperationStatus[])status.toArray(new SyncOperationStatus[status.size()]);
927
928         try {
929             EngineHelper.updateServerMappings(clientMappings, operationStatus, slowSync);
930         } catch (Exception JavaDoc e) {
931             log.throwing(getClass().getName(), "updateServerMappings", e);
932
933             if (log.isLoggable(Level.SEVERE)) {
934                 log.severe(e.getMessage());
935             }
936         }
937     }
938
939     /**
940      * Converts an array of <i>Item</i> objects belonging to the same
941      * <i>SyncSource</i> into an array of <i>SyncItem</i> objects.
942      * <p>
943      * The <i>Item</i>s created are enriched with an additional property
944      * called as in SyncItemHelper.PROPERTY_COMMAND, which is used to bound the
945      * newly created object with the original command.
946      *
947      * @param syncSource the <i>SyncSource</i> items belong to - NOT NULL
948      * @param items the <i>Item</i> objects
949      * @param state the state of the item as one of the values defined in
950      * <i>SyncItemState</i>
951      * @param the timestamp to assign to the last even on this item
952      *
953      *
954      * @return an array of <i>SyncItem</i> objects
955      */

956     public SyncItem[] itemsToSyncItems(SyncSource syncSource ,
957                                        ModificationCommand cmd ,
958                                        char state ,
959                                        long timestamp ) {
960
961         ClientMapping m = getMapping(syncSource.getSourceURI());
962         return EngineHelper.itemsToSyncItems(m, syncSource, cmd, state, timestamp);
963     }
964
965
966         /**
967      * Returns the server credentials in the format specified by the given Chal.
968      *
969      * @param chal the client Chal
970      * @param device the device
971      *
972      * @return the server credentials
973      */

974     public Cred getServerCredentials(Chal chal, Sync4jDevice device) {
975         String JavaDoc login = configuration.getServerConfig().getServerInfo().getDevID();
976
977         if (login == null || login.equals("")) {
978             return null;
979         }
980
981         String JavaDoc pwd = device.getServerPassword();
982
983         String JavaDoc type = chal.getType();
984         String JavaDoc data = login + ':' + pwd;
985
986         if (Cred.AUTH_TYPE_BASIC.equals(type)) {
987             data = new String JavaDoc(Base64.encode(data.getBytes()));
988         } else if (Cred.AUTH_TYPE_MD5.equals(type)) {
989
990             if (this.syncMLVerProto.indexOf("1.0") != -1) {
991                 //
992
// Steps to follow:
993
// 1) decode nonce sent by client
994
// 2) b64(H(username:password:nonce))
995
//
996
byte[] serverNonce = chal.getNextNonce().getValue();
997                 byte[] serverNonceDecode = null;
998                 if ((serverNonce == null) || (serverNonce.length == 0)) {
999                     serverNonceDecode = new byte[0];
1000                } else {
1001                    serverNonceDecode = Constants.FORMAT_B64.equals(chal.getFormat())
1002                                        ? Base64.decode(serverNonce)
1003                                        : serverNonce
1004                                        ;
1005                }
1006                byte[] dataBytes = data.getBytes();
1007                byte[] buf = new byte[dataBytes.length + 1 + serverNonceDecode.length];
1008                System.arraycopy(dataBytes, 0, buf, 0, dataBytes.length);
1009                buf[dataBytes.length] = (byte)':';
1010                System.arraycopy(serverNonceDecode, 0, buf, dataBytes.length+1, serverNonceDecode.length);
1011
1012                byte[] digestNonce = MD5.digest(buf);
1013                data = new String JavaDoc(Base64.encode(digestNonce));
1014
1015            } else {
1016                //
1017
//Steps to follow:
1018
//1) H(username:password)
1019
//2) b64(H(username:password))
1020
//3) decode nonce sent by client
1021
//4) b64(H(username:password)):nonce
1022
//5) H(b64(H(username:password)):nonce)
1023
//6) b64(H(b64(H(username:password)):nonce))
1024
//
1025
byte[] serverNonce = chal.getNextNonce().getValue();
1026                byte[] serverNonceDecode = null;
1027                if ((serverNonce == null) || (serverNonce.length == 0)) {
1028                    serverNonceDecode = new byte[0];
1029                } else {
1030                    serverNonceDecode = Constants.FORMAT_B64.equals(chal.getFormat())
1031                                        ? Base64.decode(serverNonce)
1032                                        : serverNonce
1033                                        ;
1034                }
1035                byte[] digest = MD5.digest(data.getBytes()); // data = username:password
1036
byte[] b64 = Base64.encode(digest);
1037                byte[] buf = new byte[b64.length + 1 + serverNonceDecode.length];
1038
1039                System.arraycopy(b64, 0, buf, 0, b64.length);
1040                buf[b64.length] = (byte)':';
1041                System.arraycopy(serverNonceDecode, 0, buf, b64.length+1, serverNonceDecode.length);
1042
1043                data = new String JavaDoc(Base64.encode(MD5.digest(buf)));
1044            }
1045        }
1046
1047        Meta m = new Meta();
1048        m.setType(type);
1049        m.setNextNonce(null);
1050        return new Cred(new Authentication(m, data));
1051    }
1052
1053    /**
1054     * Read the given principal from the store. The principal must be
1055     * already configured with username and device. The current implementation
1056     * reads the following additional informatioin:
1057     * <ul>
1058     * <li>principal id
1059     * </ul>
1060     *
1061     * @param principal the principal to be read
1062     *
1063     * @throws sync4j.framework.server.store.PersistentStoreException
1064     */

1065    public void readPrincipal(Sync4jPrincipal principal)
1066    throws PersistentStoreException {
1067        assert(principal != null);
1068        assert(principal.getUsername() != null);
1069        assert(principal.getDeviceId() != null);
1070
1071        getStore().read(principal);
1072    }
1073
1074    /**
1075     * Read the Sync4jDevice identified by the given id from the store.
1076     *
1077     * @param deviceId the device of which reading the information
1078     *
1079     * @throws sync4j.framework.server.store.PersistentStoreException
1080     */

1081    public void readDevice(Sync4jDevice device)
1082    throws PersistentStoreException {
1083        assert(device != null);
1084        assert(device.getDeviceId() != null);
1085
1086        getStore().read(device);
1087    }
1088
1089    /**
1090     * Stores the given Sync4jDevice.
1091     *
1092     * @param device the device
1093     *
1094     */

1095    public void storeDevice(Sync4jDevice device) {
1096        try{
1097            getStore().store(device);
1098        } catch (PersistentStoreException e) {
1099            if (log.isLoggable(Level.SEVERE)) {
1100                log.severe("Error storing the device: " + e.getMessage());
1101                log.throwing(getClass().getName(), "storeDevice", e);
1102            }
1103        }
1104    }
1105
1106    /**
1107     * Updates the given mapping for the given source
1108     *
1109     * @param uri source uri
1110     * @param GUID the GUID
1111     * @param LUID the LUID
1112     *
1113     */

1114    public void updateMapping(String JavaDoc uri, String JavaDoc guid, String JavaDoc luid) {
1115        getMapping(uri).updateMapping(guid, luid);
1116    }
1117
1118
1119
1120    /**
1121     * Returns the GUID-LUID mappings associated to the given source URI.
1122     * Mappings are cached internally so that they are read from the db only
1123     * the first time they are requested.
1124     * Note that in the case of a slow sync, the first time a mapping is
1125     * requested the mapping must be reset so that old values will be removed.
1126     *
1127     * @param principal principal - this is used only when the mapping was not
1128     * cached yet; after caching it can be null
1129     * @param uri database URI
1130     * @param slow is a slow sync being performed?
1131     *
1132     * @return the LUID-GUID mapping associated to the give source uri
1133     */

1134    public ClientMapping getMapping(Sync4jPrincipal principal,
1135                                    String JavaDoc uri ,
1136                                    boolean slow ) {
1137        if (clientMappings == null) {
1138            clientMappings = new HashMap();
1139        }
1140
1141        ClientMapping ret = (ClientMapping)clientMappings.get(uri);
1142
1143        if (ret == null) {
1144            try {
1145                PersistentStore ps = getStore();
1146
1147                ret = new ClientMapping(principal, uri);
1148
1149                ps.read(ret);
1150
1151                if (slow) {
1152                    ret.clearMappings();
1153                    ps.store(ret);
1154                }
1155
1156                clientMappings.put(uri, ret);
1157            } catch (PersistentStoreException e) {
1158                log.severe("Unable to read clientMappings from the persistent store");
1159                log.throwing(getClass().getName(), "getMapping", e);
1160            }
1161        }
1162
1163        return ret;
1164    }
1165
1166    /**
1167     * This is a shortcut for getMapping(null, uri, false). It can be used when
1168     * it is sure that a mapping was already retrieved from the db.
1169     *
1170     * @param uri the source uri
1171     *
1172     * @return the LUID-GUID mapping associated to the give source uri
1173     */

1174    public ClientMapping getMapping(String JavaDoc uri) {
1175        return getMapping(null, uri, false);
1176    }
1177
1178    /**
1179     * Stores the LUIG-GUID mappings into the database
1180     *
1181     * @throws Sync4jException in case of database error
1182     */

1183    public void storeMappings() {
1184        if (clientMappings == null) {
1185            return;
1186        }
1187
1188        try {
1189            PersistentStore ps = getStore();
1190
1191            ClientMapping cm = null;
1192            Iterator i = clientMappings.keySet().iterator();
1193            while (i.hasNext()) {
1194                cm = (ClientMapping)clientMappings.get(i.next());
1195                if (log.isLoggable(Level.FINEST)) {
1196                    log.finest("Saving client mapping: " + cm);
1197                }
1198                ps.store(cm);
1199
1200                cm.resetModifiedKeys();
1201            }
1202        } catch (PersistentStoreException e) {
1203            log.severe("Unable to save clientMappings to the persistent store");
1204            log.throwing(getClass().getName(), "storeMappings", e);
1205        }
1206    }
1207
1208
1209    // --------------------------------------------------------- Private methods
1210

1211    /**
1212
1213    /**
1214     * Checks if the given database is managed by this server.
1215     *
1216     * @param db the database to be checked
1217     *
1218     * @return true if the database is under control of this server, false otherwise
1219     *
1220     */

1221    private boolean checkServerDatabase(Database db) {
1222        if (log.isLoggable(Level.FINEST)) {
1223            log.finest( "Checking if the database "
1224                      + db
1225                      + " is in the server database list."
1226                      );
1227        }
1228
1229        Sync4jSource s = new Sync4jSource(db.getName());
1230
1231        try {
1232            store.read(s); log.finest("Yes sir!");
1233
1234            return true;
1235        } catch (NotFoundException e) {
1236            log.finest("Not found sir");
1237        } catch (Exception JavaDoc e) {
1238            String JavaDoc msg = "Unable to access the perstistent store "
1239                       + s
1240                       + ": "
1241                       + e.getMessage();
1242
1243            log.severe(msg);
1244            log.throwing(getClass().getName(), "checkServerDatabase", e);
1245        }
1246
1247        //
1248
// not found...
1249
//
1250
return false;
1251    }
1252
1253    /**
1254     * Checks if the principal associated to the database is allowed to synchronize.<br>
1255     *
1256     * @param db the database to be checked
1257     *
1258     * @return true if the credential is allowed to access the database, false otherwise
1259     */

1260    private boolean checkDatabasePermissions(Database db) {
1261        return authorize(db.getPrincipal(), db.getName());
1262    }
1263
1264    /**
1265     * Sets the database error accordingly to the given exception.
1266     *
1267     * @param db the database
1268     * @param cause the throwable cause
1269     *
1270     */

1271    private void setDBError(Database db, SyncException cause) {
1272        db.setStatusCode (cause.getStatusCode());
1273        db.setStatusMessage(cause.getMessage() );
1274    }
1275
1276
1277
1278
1279
1280
1281
1282
1283}
1284
1285
1286
1287
Popular Tags