KickJava   Java API By Example, From Geeks To Geeks.

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


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;
20
21 import java.util.*;
22 import java.util.logging.Level JavaDoc;
23
24 import java.sql.Timestamp JavaDoc;
25
26 import sync4j.framework.engine.*;
27 import sync4j.framework.engine.source.SyncSource;
28 import sync4j.framework.security.Sync4jPrincipal;
29 import sync4j.framework.tools.beans.BeanFactory;
30 import sync4j.framework.core.*;
31 import sync4j.framework.database.Database;
32 import sync4j.framework.config.Configuration;
33 import sync4j.framework.config.ConfigurationConstants;
34 import sync4j.framework.server.*;
35 import sync4j.framework.server.store.*;
36 import sync4j.server.engine.Sync4jSource;
37
38 /**
39  * This is the Sync4j implementation of the synchronization engine.
40  *
41  * LOG NAME: sync4j.engine
42  *
43  * @author Stefano Fornari @ Funambol
44  * @version $Id: Sync4jSyncEngine.java,v 1.1 2005/05/16 17:32:56 nichele Exp $
45  */

46 public class Sync4jSyncEngine
47 extends Sync4jEngine
48 implements java.io.Serializable JavaDoc, SyncEngine, ConfigurationConstants {
49
50     // --------------------------------------------------------------- Constants
51

52     // ------------------------------------------------------------ Private data
53

54     /**
55      * The sources this engine deals with
56      */

57     private ArrayList serverSources = new ArrayList();
58     private ArrayList clientSources = new ArrayList();
59
60     /**
61      * The modification objects of the last synchronization
62      */

63     private java.util.Map JavaDoc operations = null;
64
65     /**
66      * The modification objects of the last synchronization
67      */

68     private SyncOperationStatus[] operationStatus = null;
69
70     // ------------------------------------------------------------ Constructors
71

72     /**
73      * To allow deserializazion of subclasses.
74      */

75     protected Sync4jSyncEngine() {
76     }
77
78     /**
79      * Creates a new instance of Sync4jEngine. <p>
80      * NOTE: This is a sample implementation that deals with a single source on
81      * the file system.
82      *
83      * @throws ConfigurationException a runtime exception in case of misconfiguration
84      */

85     public Sync4jSyncEngine(Configuration configuration) {
86         super(configuration);
87
88         //
89
// Set SyncSources
90
//
91
Sync4jSource[] sources = null;
92
93         try {
94             sources = (Sync4jSource[])store.read(Sync4jSource.class);
95         } catch (PersistentStoreException e) {
96             if (log.isLoggable(Level.SEVERE)) {
97                 log.severe("Error reading registered sources: " + e.getMessage());
98             }
99
100             log.throwing(getClass().getName(), "<init>", e);
101         }
102
103         for (int i=0; ((sources != null) && (i<sources.length)); ++i) {
104             if (log.isLoggable(Level.FINE)) {
105                 log.fine("sources[" + i + "]: " + sources[i]);
106             }
107
108             try {
109                 serverSources.add(
110                     BeanFactory.getBeanInstance(configuration.getClassLoader(), sources[i].getConfig())
111                 );
112             } catch (Exception JavaDoc e){
113                 String JavaDoc msg = "Unable to create sync source "
114                            + sources[i]
115                            + ": "
116                            + e.getMessage();
117
118                 log.severe(msg);
119                 log.throwing(getClass().getName(), "<init>", e);
120             }
121         }
122
123
124         //
125
// Set the SyncStrategy object to use for comparisons
126
//
127
SyncStrategy strategy =
128            (SyncStrategy)configuration.getClassInstance(CFG_STRATEGY_CLASS);
129
130         setStrategy(strategy);
131
132         if (log.isLoggable(Level.INFO)) {
133             log.info("Engine configuration:");
134             log.info("store: " + store );
135             log.info("officer: " + officer );
136             log.info("strategy: " + strategy);
137         }
138     }
139
140     // ------------------------------------------------------ Runtime properties
141

142     /**
143      * The underlying strategy.
144      */

145     private SyncStrategy strategy = null;
146
147     /**
148      * Get the underlying strategy
149      *
150      * @return the underlying strategy
151      */

152     public SyncStrategy getStrategy() {
153         return this.strategy;
154     }
155
156     /**
157      * Set the synchronization strategy to be used
158      */

159     public void setStrategy(SyncStrategy strategy) {
160         this.strategy = strategy;
161     }
162
163
164     /**
165      * The database to be synchronized
166      */

167     private java.util.Map JavaDoc dbs = new HashMap();
168
169     public void setDbs(java.util.Map JavaDoc dbs) {
170         if (this.dbs != null) {
171             this.dbs.clear();
172         } else {
173             this.dbs = new HashMap(dbs.size());
174         }
175         this.dbs.putAll(dbs);
176     }
177
178     public void setDbs(Database[] dbs) {
179         if (this.dbs == null) {
180             this.dbs = new HashMap(dbs.length);
181         }
182
183         for (int i=0; ((dbs != null) && (i<dbs.length)); ++i) {
184             this.dbs.put(dbs[i].getName(), dbs[i]);
185         }
186     }
187
188     public Database[] getDbs() {
189         if (this.dbs == null) {
190             return new Database[0];
191         }
192
193         Iterator i = this.dbs.values().iterator();
194
195         Database[] ret = new Database[this.dbs.size()];
196
197         int j = 0;
198         while(i.hasNext()) {
199             ret[j++] = (Database)i.next();
200         }
201
202         return ret;
203     }
204
205     /**
206      * The existing LUID-GUID mapping. It is used and modify by sync()
207      */

208     private java.util.Map JavaDoc clientMappings = new HashMap();
209
210     public void setClientMappings(java.util.Map JavaDoc clientMappings) {
211         this.clientMappings = clientMappings;
212     }
213
214     // ---------------------------------------------------------- Public methods
215

216     /**
217      * Fires and manages the synchronization process.
218      *
219      * @param principal the principal who is requesting the sync
220      *
221      * @throws Sync4jException in case of error
222      */

223     public void sync(Sync4jPrincipal principal)
224     throws Sync4jException {
225         log.info("Starting synchronization ...");
226
227         SyncStrategy syncStrategy = getStrategy();
228
229         SyncSource clientSource = null, serverSource = null;
230         Database db = null;
231
232         ArrayList status = new ArrayList();
233
234         //
235
// Create maps for server sources so that they can be accessed throught
236
// their name
237
//
238
HashMap sourceMap = new HashMap(serverSources.size());
239
240         Iterator s = serverSources.iterator();
241         while(s.hasNext()) {
242             serverSource = (SyncSource)s.next();
243             sourceMap.put(serverSource.getSourceURI(), serverSource);
244         }
245
246         //
247
// Now process clientSources
248
//
249

250         operations = new HashMap();
251
252         String JavaDoc uri = null;
253         Iterator c = clientSources.iterator();
254         while(c.hasNext()) {
255             clientSource = (SyncSource)c.next();
256             uri = clientSource.getSourceURI();
257
258             serverSource = (SyncSource)sourceMap.get(uri);
259             db = (Database)dbs.get(clientSource.getSourceURI());
260
261             SyncSource[] sources = new SyncSource[] {
262                 serverSource, clientSource
263             };
264
265             try {
266                 Sync4jPrincipal p = (Sync4jPrincipal)db.getPrincipal();
267                 p.setId(principal.getId());
268
269                 //
270
// Call beginSync()
271
//
272
serverSource.beginSync(p, db.getMethod());
273
274                 if ( (db.getMethod() == AlertCode.SLOW)
275                     || (db.getMethod() == AlertCode.REFRESH_FROM_SERVER)
276                     || (db.getStatusCode() == StatusCode.REFRESH_REQUIRED)) {
277                     operations.put(uri, syncStrategy.prepareSlowSync(sources, p));
278                 } else {
279                     //
280
// Read the last timestamp from the persistent store, than
281
// prepare for fast synchronization
282
//
283
LastTimestamp last = new LastTimestamp(
284                                              p.getId() ,
285                                              db.getName()
286                                          );
287                     try {
288                         store.read(last);
289                     } catch (PersistentStoreException e) {
290                         throw new SyncException("Error reading last timestamp", e);
291                     }
292                     Timestamp JavaDoc since = new Timestamp JavaDoc(last.start);
293
294                     operations.put(uri, syncStrategy.prepareFastSync(sources, p, since));
295                 }
296
297                 //
298
// After processed the db if status code was "Refresh required"
299
// then change that into StatusCode.OK
300
//
301
if (db.getStatusCode() == StatusCode.REFRESH_REQUIRED) {
302                     db.setStatusCode(StatusCode.OK);
303                 }
304
305                 //
306
// Now synchronize the sources
307
//
308
status.addAll(
309                     Arrays.asList(
310                         syncStrategy.sync((SyncOperation[])operations.get(uri))
311                     )
312                 );
313
314                 operationStatus = (SyncOperationStatus[])status.toArray(new SyncOperationStatus[0]);
315
316                 //
317
// Call endSync()
318
//
319
serverSource.endSync(p);
320
321                 log.info("Ending synchronization ...");
322
323                 syncStrategy.endSync();
324             } catch (SyncException e) {
325                 log.throwing(getClass().getName(), "sync", e);
326                 throw new Sync4jException(e.getMessage(), e);
327             }
328         } // next i (client source)
329
}
330
331     /**
332      * Returns the operations of the last synchronization
333      *
334      * @param uri the URI of the source the operations are applied to
335      *
336      * @return the operations of the last synchronization
337      */

338     public SyncOperation[] getSyncOperations(String JavaDoc uri) {
339         return (SyncOperation[])operations.get(uri);
340     }
341
342     /**
343      * Returns the operation status of the operations executed in the last
344      * synchronization
345      *
346      * @param msgId the id of the SyncML message
347      *
348      * @return the operations status of the operations executed in the last
349                synchronization
350      */

351     public Status[] getModificationsStatusCommands(String JavaDoc msgId) {
352
353         if ((operationStatus == null) || (operationStatus.length == 0)) {
354             return new Status[0];
355         }
356
357         return EngineHelper.generateStatusCommands(
358             operationStatus,
359             msgId,
360             cmdIdGenerator
361         );
362     }
363
364     /**
365      * Adds a new client source to the list of the sources the engine deals with.
366      *
367      * @param source the source to be added
368      */

369     public void addClientSource(SyncSource source) {
370         log.finest("adding " + source);
371
372         clientSources.add(source);
373     }
374
375     /**
376      * Returns the client sources
377      *
378      * @return the client sources
379      */

380     public List getClientSources() {
381         return clientSources;
382     }
383
384     /**
385      * Returns the client source with the given name if there is any.
386      *
387      * @param name the source name
388      *
389      * @return the client source with the given name if there is any, null otherwise
390      */

391     public SyncSource getClientSource(String JavaDoc name) {
392         Iterator i = clientSources.iterator();
393
394         SyncSource s = null;
395         while (i.hasNext()) {
396             s = (SyncSource)i.next();
397
398             if (s.getSourceURI().equals(name)) {
399                 return s;
400             }
401         }
402
403         return null;
404     }
405
406     /**
407      * Returns the server source with the given name if there is any.
408      *
409      * @param name the source name
410      *
411      * @return the server source with the given name if there is any, null otherwise
412      */

413     public SyncSource getServerSource(String JavaDoc name) {
414         Iterator i = serverSources.iterator();
415
416         SyncSource s = null;
417         while (i.hasNext()) {
418             s = (SyncSource)i.next();
419
420             if (s.getSourceURI().equals(name)) {
421                 return s;
422             }
423         }
424
425         return null;
426     }
427
428     /**
429      * Return the <i>DataStore</i> object corresponding to the given
430      * <i>Database</i> object.
431      *
432      * @param database the database to convert
433      *
434      * @return the <i>DataStore</i> object corresponding to the given
435      * <i>Database</i> object.
436      *
437      */

438     public DataStore databaseToDataStore(Database db) {
439         ContentTypeInfo contentTypeInfo
440             = new ContentTypeInfo(db.getType(), "-" /* version */);
441
442         return new DataStore(
443             new SourceRef(db.getSource()) ,
444             null ,
445             -1 ,
446             contentTypeInfo ,
447             new ContentTypeInfo[] { contentTypeInfo } ,
448             contentTypeInfo ,
449             new ContentTypeInfo[] { contentTypeInfo } ,
450             new DSMem(false, -1, -1) ,
451             new SyncCap(new SyncType[] { SyncType.SLOW })
452         );
453     }
454
455     /**
456      * Returns the content type capabilities of this sync engine.
457      *
458      * @return the content type capabilities of this sync engine.
459      */

460     public CTCap[] getContentTypeCapabilities() {
461         return new CTCap[0];
462     }
463
464     /**
465      * Returns the extensions of this sync engine.
466      *
467      * @return the extensions of this sync engine.
468      */

469     public Ext[] getExtensions() {
470         return new Ext[0];
471     }
472
473     /**
474      * Returns the data store capabilities of the sync sources to synchronize.
475      *
476      * @return the data store capabilities of the sync sources to synchronize.
477      */

478     public DataStore[] getDatastores() {
479         DataStore[] ds = null;
480         ArrayList al = new ArrayList();
481
482         Database db = null;
483         String JavaDoc uri = null;
484         Iterator i = dbs.values().iterator();
485         while(i.hasNext()) {
486             db = (Database)i.next();
487             uri = db.getName();
488
489             SyncSource ss = getServerSource(uri);
490
491             if ( ss != null) {
492                 al.add(EngineHelper.toDataStore(uri, ss.getInfo()));
493             }
494         }
495
496         int size = al.size();
497         if (size == 0) {
498             ds = new DataStore[0];
499         } else {
500         ds = (DataStore[])al.toArray(new DataStore[size]);
501         }
502
503
504         return ds;
505     }
506
507
508     /**
509      * Creates and returns a <i>DeviceInfo</i> object with the information
510      * extracted from the configuration object.
511      *
512      * @param verDTD the version of the DTD to use to encode the capabilities
513      *
514      * @return the engine capabilities
515      *
516      * @throws ConfigurationException a runtime exception in case of misconfiguration
517      */

518     public DevInf getServerCapabilities(VerDTD verDTD) {
519         DevInf devInf =
520         new DevInf(
521             verDTD,
522             configuration.getStringValue(CFG_ENGINE_MANIFACTURER, "Sync4j"),
523             configuration.getStringValue(CFG_ENGINE_MODELNAME, "Sync4j"),
524             configuration.getStringValue(CFG_ENGINE_OEM, null ),
525             configuration.getStringValue(CFG_ENGINE_FWVERSION, null ),
526             configuration.getStringValue(CFG_ENGINE_SWVERSION, null ),
527             configuration.getStringValue(CFG_ENGINE_HWVERSION, null ),
528             configuration.getStringValue(CFG_ENGINE_DEVICEID, "Sync4j"),
529             configuration.getStringValue(CFG_ENGINE_DEVICETYPE, "Server"),
530             getDatastores(),
531             getContentTypeCapabilities(),
532             getExtensions(),
533             false,
534             false,
535             false
536         );
537         return devInf;
538     }
539
540     /**
541      * First of all, check the availablity and accessibility of the given
542      * databases. The state of the given database will change as described below
543      * (and in the same order):
544      * <ul>
545      * <li>The database status code is set to <i>StatusCode.OK</i> if the
546      * database is accessible, <i>StatusCode.NOT_FOUND</i> if the database
547      * is not found and <i>StatusCode.FORBIDDEN</i> if the principal is
548      * not allowed to access the database.
549      * <li>If the currently set last anchor does not match the last tag
550      * retrieved from the database (DBMS) and the requested alert code is
551      * not a refresh, the synchronization method is set to
552      * <i>AlertCode.SLOW</i>
553      * <li>The database server sync anchor is set to the server-side sync anchor
554      *
555      * @param principal the principal the is requesting db preparation
556      * @param dbs an array of <i>Database</i> objects - NULL
557      * @param next the sync timestamp of the current synchronization
558      *
559      */

560     public void prepareDatabases(Sync4jPrincipal principal,
561                                  Database[] dbs ,
562                                  SyncTimestamp next ) {
563         for (int i=0; ((dbs != null) && (i < dbs.length)); ++i) {
564             int statusCode = StatusCode.OK;
565
566             if (!checkServerDatabase(dbs[i])) {
567                 statusCode = StatusCode.NOT_FOUND;
568             } else if (!checkDatabasePermissions(dbs[i])) {
569                 statusCode = StatusCode.FORBIDDEN;
570             }
571
572             dbs[i].setStatusCode(statusCode);
573
574             //
575
// Now retrieve the last sync anchor
576
//
577
if (statusCode == StatusCode.OK) {
578                 LastTimestamp last = new LastTimestamp(
579                     principal.getId(),
580                     dbs[i].getName()
581                 );
582
583                 try {
584                     store.read(last);
585                     dbs[i].setServerAnchor(new Anchor(last.tagClient, next.tagClient));
586                 } catch (NotFoundException e) {
587                     //
588
// No prev last sync! Create a new anchor that won't match
589
//
590
last.tagServer = next.tagClient;
591                     dbs[i].setServerAnchor(new Anchor(last.tagServer, next.tagClient));
592                 } catch(PersistentStoreException e) {
593                     log.severe("Unable to retrieve timestamp from store");
594                     log.throwing(getClass().getName(), "prepareDatabases", e);
595                 }
596
597                 if ( !(last.tagServer.equals(dbs[i].getAnchor().getLast()))
598                    && (dbs[i].getMethod() != AlertCode.REFRESH_FROM_SERVER)) {
599                     if (log.isLoggable(Level.FINE)) {
600                         log.fine( "Forcing slow sync for database "
601                                 + dbs[i].getName()
602                                 );
603                         log.fine( "Server last: "
604                                 + last.tagServer
605                                 + "; client last: "
606                                 + dbs[i].getAnchor().getLast()
607                                 );
608                     }
609                     dbs[i].setMethod(AlertCode.SLOW);
610                 }
611             }
612         }
613     }
614
615     /**
616      * Translates an array of <i>SyncOperation</i> objects to an array of
617      * <i>(Add,Delete,Replace)Command</i> objects. Only client side operations
618      * are translated.
619      *
620      * @param clientMapping the associated existing client mapping
621      * @param operations the operations to be translated
622      * @param sourceName the corresponding source name
623      *
624      * @return the sync4j commands corresponding to <i>operations</i>
625      */

626     public ItemizedCommand[] operationsToCommands(ClientMapping clientMapping,
627                                                   SyncOperation[] operations ,
628                                                   String JavaDoc sourceName ) {
629         return EngineHelper.operationsToCommands( clientMapping ,
630                                                   operations ,
631                                                   sourceName ,
632                                                   cmdIdGenerator);
633     }
634
635     /**
636      * Updates the mapping given an array of operations. This is used for
637      * mappings sent by the client.
638      *
639      * @param slowSync is this method called during a slow sync
640      * @param clientMappings the client mappings for current synchronization
641      * @param operations the operation performed
642      */

643     public void updateClientMappings(java.util.Map JavaDoc clientMappings,
644                                      SyncOperation[] operations ,
645                                      boolean slowSync ) {
646         try {
647             EngineHelper.updateClientMappings(clientMappings, operations, slowSync);
648         } catch (Exception JavaDoc e) {
649           log.throwing(getClass().getName(), "updateClientMappings", e);
650         }
651     }
652
653
654     /**
655      * Updates the LUID-GUIDmapping for the client modifications (that is the
656      * items directlry inserted or removed by the server).
657      *
658      * @param clientMappings the client mappings for current synchronization
659      *
660      */

661     public void updateServerMappings(java.util.Map JavaDoc clientMappings, boolean slowSync) {
662         try {
663             EngineHelper.updateServerMappings(clientMappings, operationStatus, slowSync);
664         } catch (Exception JavaDoc e) {
665           log.throwing(getClass().getName(), "updateServerMappings", e);
666         }
667     }
668
669     /**
670      * Converts an array of <i>Item</i> objects belonging to the same
671      * <i>SyncSource</i> into an array of <i>SyncItem</i> objects.
672      * <p>
673      * The <i>Item</i>s created are enriched with an additional property
674      * called as in SyncItemHelper.PROPERTY_COMMAND, which is used to bound the
675      * newly created object with the original command.
676      *
677      * @param syncSource the <i>SyncSource</i> items belong to - NOT NULL
678      * @param items the <i>Item</i> objects
679      * @param state the state of the item as one of the values defined in
680      * <i>SyncItemState</i>
681      * @param the timestamp to assign to the last even on this item
682      *
683      *
684      * @return an array of <i>SyncItem</i> objects
685      */

686     public static SyncItem[] itemsToSyncItems(ClientMapping clientMapping,
687                                               SyncSource syncSource ,
688                                               ModificationCommand cmd ,
689                                               char state ,
690                                               long timestamp ) {
691
692         return EngineHelper.itemsToSyncItems(clientMapping, syncSource, cmd, state, timestamp);
693     }
694
695     // --------------------------------------------------------- Private methods
696

697     /**
698
699     /**
700      * Checks if the given database is managed by this server.
701      *
702      * @param db the database to be checked
703      *
704      * @return true if the database is under control of this server, false otherwise
705      *
706      */

707     private boolean checkServerDatabase(Database db) {
708         if (log.isLoggable(Level.FINEST)) {
709             log.finest( "Checking if the database "
710                       + db
711                       + " is in the server database list "
712                       + serverSources
713                       );
714         }
715
716         SyncSource source = null;
717         Iterator i = serverSources.iterator();
718         while(i.hasNext()) {
719             source = (SyncSource)i.next();
720
721             if (db.getName().equals(source.getSourceURI())) {
722                 log.finest("Yes sir!");
723                 return true;
724             }
725         }
726
727         log.finest("Not found sir");
728
729         //
730
// not found...
731
//
732
return false;
733     }
734
735     /**
736      * Checks if the principal associated to the database is allowed to synchronize.<br>
737      *
738      * @param db the database to be checked
739      *
740      * @return true if the credential is allowed to access the database, false otherwise
741      */

742     private boolean checkDatabasePermissions(Database db) {
743         return authorize(db.getPrincipal(), db.getName());
744     }
745 }
Popular Tags