KickJava   Java API By Example, From Geeks To Geeks.

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


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.logging.Level JavaDoc;
24 import java.util.logging.Logger JavaDoc;
25
26 import org.apache.commons.collections.ListUtils;
27
28 import sync4j.framework.core.Add;
29 import sync4j.framework.core.ModificationCommand;
30 import sync4j.framework.core.StatusCode;
31 import sync4j.framework.engine.*;
32 import sync4j.framework.engine.Util;
33 import sync4j.framework.engine.source.MemorySyncSource;
34 import sync4j.framework.engine.source.ObjectSizeMismatchException;
35 import sync4j.framework.engine.source.SyncSource;
36 import sync4j.framework.engine.source.SyncSourceException;
37
38 /**
39  * This class represents a synchronization process.
40  *
41  * The base concrete implementation of a synchronization strategy. It implements
42  * the <i>ConcreteStrategy</i> partecipant of the Strategy Pattern.
43  * <p>
44  * The synchronization process is implemented in this class as follows:
45  * <p>
46  * Given a set of sources A, B, C, D, etc, the synchronization process takes place
47  * between two sources at a time: A is first synchronzed with B, then AB with
48  * C, then ABC with D and so on. <br>
49  * The synchronization process is divided in three phases: preparation, synchronization,
50  * finalization. These phases correspond to the methdos prepareSync, sync and
51  * endSync; they are call-back methods called sequentially by a driver object,
52  * usually a SyncMethod object. In this way the SyncMethod object has a chance
53  * to communicate with the external world before carry on the process.
54  * For example a SyncMethod object can show to the user the items that are going
55  * to be synchronized, so that the user can let the process carry on or stop it. <br>
56  * prepareSync returns an array of SyncOperation, in which each element represents
57  * a particular synchronization action, ie. create an item in the source A,
58  * delete the item X from the source B, etc. Sometime it is not possible decide
59  * what to do, thus a SyncConflict operation is used. A conflict must be solved
60  * by something external the synchronization process, for instance by a user
61  * action. Below is a table of all possible situations.
62  * <pre>
63  *
64  * | -------- | --- | --- | --- | --- | --- |
65  * | Source A | | | | | |
66  * | / | N | D | U | S | X | N : item new
67  * | Source B | | | | | | D : item deleted
68  * | -------- | --- | --- | --- | --- | --- | U : item updated
69  * | N | O | O | O | O | B | S : item synchronized/unchanged
70  * | -------- | --- | --- | --- | --- | --- | X : item not existing
71  * | D | O | X | O | X | X | O : conflict
72  * | -------- | --- | --- | --- | --- | --- | A : item A replaces item B
73  * | U | O | O | O | B | B | B : item B replaces item A
74  * | -------- | --- | --- | --- | --- | --- |
75  * | S | O | X | A | = | B |
76  * | -------- | --- | --- | --- | --- | --- |
77  * | X | A | X | A | A | X |
78  * | -------- | --- | --- | --- | --- | --- |
79  *
80  * </pre>
81  *
82  * @author Stefano Fornari @ Funambol
83  *
84  * @version $Id: Sync4jStrategy.java,v 1.43 2005/07/13 19:26:51 luigiafassina Exp $
85  */

86 public class Sync4jStrategy implements SyncStrategy, java.io.Serializable JavaDoc {
87
88     // --------------------------------------------------------------- Constants
89

90     public static String JavaDoc LOG_NAME = "sync4j.framework.engine";
91
92     private transient static Logger JavaDoc log = Logger.getLogger(LOG_NAME);
93
94     // -------------------------------------------------------------- Properties
95

96     /**
97      * The synchronization source
98      */

99     private transient SyncSource[] sources = null;
100     public SyncSource[] getSources(){ return sources; }
101
102     public void setSources(SyncSource[] sources){ this.sources = sources; }
103
104     /**
105      * The process' name
106      */

107     public String JavaDoc getName(){ return name; }
108
109     private String JavaDoc name;
110     public void setName(String JavaDoc name){ this.name = name; }
111
112     // ------------------------------------------------------------ Private data
113

114     //
115
// NOTE: server modification MUST be detected and stored at the beginning of
116
// the sync process (i.e. in the first modification message), otherwise in
117
// the case of multi message, likely the server will detect items modified
118
// by the current sync as server modifications.
119
// Reading server modifications at the synchronization start does not
120
// loose items since. Let's suppose we have the following events axis:
121
//
122
// s1 e1 s2 e2 s3 e3
123
// |-------------|....|---------------|...............|-----------|
124
//
125
// where s<x> is the beginning of a sync and e<x> represents its end.
126
// In s2 we collect all modified items since s1; if any other process
127
// modifies an item after s2, it won't be detected during the second
128
// synchronization. However, it will be detected at s3, where we collect all
129
// modified items since s2.
130
//
131
private HashMap newBAll;
132     private HashMap updatedBAll;
133     private HashMap deletedBAll;
134
135     // ------------------------------------------------------------ Constructors
136

137     public Sync4jStrategy() {
138         newBAll = new HashMap();
139         updatedBAll = new HashMap();
140         deletedBAll = new HashMap();
141     }
142
143     public Sync4jStrategy(SyncSource[] syncSources) {
144         this();
145         sources = syncSources;
146     }
147
148     // ---------------------------------------------------------- Public methods
149

150     /**
151      * Preparation for the synchronization. If <i>sources</i> is not null,
152      * the preparation works on the given sources. Otherwise it works on the
153      * sources as they were set in the constructor.
154      * Note that in the case of slow sync, the sync analysis to determine the
155      * new items on the server that must be returned to the client, we need to
156      * wait for the last call to this method. This is used for protocols that
157      * supports a multi message session. At the contrary, client side
158      * modifications can be processed immediately.
159      *
160      *
161      * @param sources the sources to be synchronized
162      * @param nextSync the timestamp of the beginning of this synchronization
163      * @param principal the entity for which the synchronization is required
164      * @param last is this the last call to prepareSlowSync ?
165      *
166      * @return an array of SyncOperation, one for each SyncItem that must be
167      * created/updated/deleted or in conflict.
168      *
169      * @see sync4j.framework.engine.SyncStrategy
170      */

171     public SyncOperation[] prepareSlowSync(SyncSource[] sources ,
172                                            Principal JavaDoc principal,
173                                            Timestamp JavaDoc nextSync ,
174                                            boolean last )
175     throws SyncException {
176
177         if (sources != null) {
178             this.sources = sources;
179         }
180
181         SyncItem[] Am, Bm;
182         List syncOperations = new ArrayList();
183
184         Logger JavaDoc log = Logger.getLogger(LOG_NAME);
185
186         if (log.isLoggable(Level.INFO)) {
187             log.info( "Preparing slow synchronization of source '"
188                     + sources[1].getSourceURI()
189                     + "' for "
190                     + principal
191                     + " ..."
192                     );
193             if (last) {
194                 log.info("Last call");
195             } else {
196                 log.info("Not the last call");
197             }
198         }
199
200         //
201
// First process the items provided by the client
202
//
203
Am = sources[1].getUpdatedSyncItems(principal, null);
204         Bm = sources[0].getAllSyncItems(principal);
205
206         //
207
// Just in case...
208
//
209
if (Am == null) {
210             Am = new SyncItem[0];
211         }
212
213         if (Bm == null) {
214             Bm = new SyncItem[0];
215         }
216
217         //
218
// If any item from the client has not a corresponding mapping, the
219
// server source must be queried for the item, since the client item
220
// could be the same of an existing server item. In this case, the old
221
// unmapped item is replaced in Map by the newly mapped item.
222
//
223
ArrayList newlyMappedItems = new ArrayList();
224         fixMappedItems(newlyMappedItems, Am, sources[0], principal);
225
226         //
227
// Because it is a slow sync, items state must be reset to S
228
//
229
EngineHelper.resetState(Am);
230         EngineHelper.resetState(Bm);
231
232         for (int i=0; i<Am.length; ++i) {
233             if (!Am[i].isMapped()) {
234                 syncOperations.add(checkSyncOperation(principal, nextSync, Am[i], null));
235             }
236         }
237
238         Iterator j = newlyMappedItems.iterator();
239         while (j.hasNext()) {
240             SyncItemMapping m = (SyncItemMapping)j.next();
241             m.getSyncItemA().setState(SyncItemState.SYNCHRONIZED);
242             m.getSyncItemB().setState(SyncItemState.SYNCHRONIZED);
243             syncOperations.add(checkSyncOperation(principal, nextSync, m.getSyncItemA(), m.getSyncItemB()));
244         }
245
246         //
247
// If it is the last call, we have also to consider what is in source
248
// B but not in A
249
//
250
if (last) {
251             //
252
// In this case getAllSyncItems() returns the items in the datastore
253
// reconstructed from the GUID-LUID mapping. In addition to these
254
// items we have the ones sent in the last message (retrieved
255
// calling getUpdatedSyncItems().
256
//
257
EngineHelper.resetState(Am);
258
259             List all = new ArrayList(Arrays.asList(sources[1].getAllSyncItems(principal)));
260             all.addAll(Arrays.asList(Am));
261
262             for (int i=0; i<Bm.length; ++i) {
263                 if (!(all.contains(Bm[i]))) {
264                     syncOperations.add(checkSyncOperation(principal, nextSync, null, Bm[i]));
265                 }
266             }
267         }
268
269         if (log.isLoggable(Level.FINEST)) {
270             log.finest("operations: " + syncOperations);
271         }
272
273         if (log.isLoggable(Level.INFO)) {
274             log.info("Preparation completed.");
275         }
276
277         return (SyncOperation[])syncOperations.toArray(new SyncOperationImpl[] {});
278     }
279
280     /**
281      * Preparation for faset synchronization. If <i>sources</i> is not null,
282      * the preparation operates on the given sources. Otherwise it works on the
283      * sources as they were set in the constructor.
284      * <p>
285      * Refer to the <a HREF="http://sync4j.sourceforge.net/web/project/architecture/sync4j-architecture.html">
286      * architecture document</a> for details about the algoritm applied.
287      *
288      * @param sources the sources to be synchronized
289      * @param principal the entity for which the synchronization is required
290      * @param lastSync timestamp of the last synchronization
291      * @param nextSync timestamp of the current synchronization
292      * @param last is this the last call to prepareFastSync ?
293      *
294      * @return an array of SyncOperation, one for each SyncItem that must be
295      * created/updated/deleted or in conflict.
296      *
297      * @see sync4j.framework.engine.SyncStrategy
298      */

299     public SyncOperation[] prepareFastSync(SyncSource[] sources ,
300                                            Principal JavaDoc principal,
301                                            Timestamp JavaDoc lastSync ,
302                                            Timestamp JavaDoc nextSync ,
303                                            boolean last )
304     throws SyncException {
305         if (sources != null) {
306             this.sources = sources;
307         }
308
309         // ---------------------------------------------------------------------
310

311         List Am , // items modified in A
312
Bm , // items modified in B
313
AmBm , // Am intersect Bm
314
AAmBm , // items unmodified in A, but modified in B
315
AmBBm ; // items unmodified in B, but modified in A
316

317         ArrayList syncOperations = null;
318
319         SyncItem[] newA = null, newB = null,
320                    updatedA = null, updatedB = null,
321                    deletedA = null, deletedB = null;
322
323         if (log.isLoggable(Level.INFO)) {
324             log.info( "Preparing fast synchronization of source '"
325                     + sources[1].getSourceURI()
326                     + "' for "
327                     + principal
328                     + " since "
329                     + lastSync
330                     + "..."
331                     );
332
333             if (last) {
334                 log.info("Last call");
335             } else {
336                 log.info("Not the last call");
337             }
338         }
339
340         // ---------------------------------------------------------------------
341

342         //
343
// NOTE: simplified version - only two sources, the first one of which
344
// is the client
345
//
346
newA = sources[1].getNewSyncItems (principal, lastSync);
347         updatedA = sources[1].getUpdatedSyncItems (principal, lastSync);
348         deletedA = sources[1].getDeletedSyncItems (principal, lastSync);
349
350         if (log.isLoggable(Level.FINEST)) {
351             log.finest("newA: " + Util.arrayToString(newA));
352             log.finest("updatedA: " + Util.arrayToString(updatedA));
353             log.finest("deletedA: " + Util.arrayToString(deletedA));
354         }
355
356         String JavaDoc uri = sources[0].getSourceURI();
357         if (newBAll.get(uri) == null) {
358             if (log.isLoggable(Level.FINEST)) {
359                 log.finest("Detecting server changes...");
360             }
361             newB = sources[0].getNewSyncItems (principal, lastSync);
362             updatedB = sources[0].getUpdatedSyncItems (principal, lastSync);
363             deletedB = sources[0].getDeletedSyncItems (principal, lastSync);
364
365             //
366
// The SyncSource should return empty arrays... but just in case...
367
//
368
if (newB == null) {
369                 newB = new SyncItemImpl[0];
370             }
371             if (updatedB == null) {
372                 updatedB = new SyncItemImpl[0];
373             }
374             if (deletedB == null) {
375                 deletedB = new SyncItemImpl[0];
376             }
377
378             if (log.isLoggable(Level.FINEST)) {
379                 log.finest("newB: " + Util.arrayToString(newB));
380                 log.finest("updatedB: " + Util.arrayToString(updatedB));
381                 log.finest("deletedB: " + Util.arrayToString(deletedB));
382             }
383             
384             newBAll .put(uri, newB);
385             updatedBAll.put(uri, updatedB);
386             deletedBAll.put(uri, deletedB);
387         } else {
388             newB = (SyncItem[])newBAll .get(uri);
389             updatedB = (SyncItem[])updatedBAll.get(uri);
390             deletedB = (SyncItem[])deletedBAll.get(uri);
391         }
392
393         //
394
// If any item from the client has not a corresponding mapping, the
395
// server source must be queried for the item, since the client item
396
// could be the same of an existing server item. In this case, the old
397
// unmapped item is replaced by the newly mapped item.
398
//
399
ArrayList newlyMappedItems = new ArrayList();
400
401         fixMappedItems(newlyMappedItems, newA, sources[0], principal);
402         fixMappedItems(newlyMappedItems, updatedA, sources[0], principal);
403         fixMappedItems(newlyMappedItems, deletedA, sources[0], principal);
404
405         if (log.isLoggable(Level.FINEST)) {
406             log.finest("Newly mapped items: " + newlyMappedItems);
407         }
408
409         Am = new ArrayList(); Bm = new ArrayList();
410         Am.addAll(Arrays.asList(newA ));
411         Am.addAll(Arrays.asList(updatedA ));
412         Am.addAll(Arrays.asList(deletedA ));
413         Bm.addAll(Arrays.asList(newB ));
414         Bm.addAll(Arrays.asList(updatedB ));
415         Bm.addAll(Arrays.asList(deletedB ));
416
417         if (log.isLoggable(Level.FINEST)) {
418             log.finest("Am: " + Am);
419             log.finest("Bm: " + Bm);
420             log.finest("Am-Bm: " + ListUtils.subtract(Am, Bm));
421             log.finest("Bm-Am: " + ListUtils.subtract(Bm, Am));
422         }
423
424         //
425
// Now calculate subsets: AmBm, AmBBm, AAmBm.
426
// Note that Bm and AAmBm must be calculated only when we got the
427
// last message. For the others, it must be empty. At the contrary,
428
//
429
AmBm = EngineHelper.intersect(Am, Bm);
430         AmBBm = EngineHelper.buildAmBBm(ListUtils.subtract(Am, Bm), sources[0], principal);
431
432         AAmBm = new ArrayList();
433         if (last) {
434             //
435
// NOte: being this class a concrete SyncStrategy implementation for
436
// Sync4j we know already that sources[1] is a MemorySyncSource.
437
//
438
newA = ((MemorySyncSource)sources[1]).getAllNewSyncItems (principal, lastSync);
439             updatedA = ((MemorySyncSource)sources[1]).getAllUpdatedSyncItems (principal, lastSync);
440             deletedA = ((MemorySyncSource)sources[1]).getAllDeletedSyncItems (principal, lastSync);
441
442             AAmBm = EngineHelper.buildAAmBm(ListUtils.subtract(Bm, Am), sources[1], principal);
443         } else {
444             Bm.clear();
445         }
446
447         if (log.isLoggable(Level.FINEST)) {
448             log.finest("AmBm: " + AmBm );
449             log.finest("AmBBm: " + AmBBm);
450             log.finest("AAmBm: " + AAmBm);
451         }
452
453         //
454
// Ready for conflict detection!
455
//
456
syncOperations =
457             checkSyncOperations(principal, nextSync, Am, Bm, AmBm, AmBBm, AAmBm);
458
459         if (log.isLoggable(Level.FINEST)) {
460             log.finest("operations: " + syncOperations);
461         }
462
463         if (log.isLoggable(Level.INFO)) {
464             log.info("Preparation completed.");
465         }
466
467         return (SyncOperation[])syncOperations.toArray(new SyncOperationImpl[] {});
468     }
469
470     /**
471      * Implements Synchronizable.sync
472      *
473      *
474      */

475     public SyncOperationStatus[] sync(SyncOperation[] syncOperations) {
476         Logger JavaDoc log = Logger.getLogger(LOG_NAME);
477
478         if (log.isLoggable(Level.INFO)) {
479             log.info("Synchronizing...");
480         }
481
482         if ((syncOperations == null) || (syncOperations.length == 0)) {
483             return new SyncOperationStatus[0];
484         }
485
486         ArrayList status = new ArrayList();
487
488         SyncOperationStatus[] operationStatus = null;
489         for(int i=0; i<syncOperations.length; ++i) {
490             if (log.isLoggable(Level.FINEST)) {
491                 log.finest("Executing " + syncOperations[i]);
492             }
493
494             //
495
// execSyncOperation can return more than one status for one
496
// operation when more than one source are involved
497
//
498
operationStatus = execSyncOperation((SyncOperationImpl)syncOperations[i]);
499
500             for (int j=0; j<operationStatus.length; ++j) {
501                 status.add(operationStatus[j]);
502             } // next j
503
} // next i
504

505         if (log.isLoggable(Level.FINEST)) {
506             log.finest("status: " + status);
507         }
508
509         return (SyncOperationStatus[])status.toArray(new SyncOperationStatus[0]);
510     }
511
512     /**
513      * Implements Synchronizable.endSync
514      *
515      * @see beanblue.sync.event.Synchronizable
516      */

517     public void endSync() throws SyncException {
518         Logger JavaDoc log = Logger.getLogger(LOG_NAME);
519
520         if (log.isLoggable(Level.INFO)) {
521             log.info("Synchronization completed.");
522         }
523     }
524
525     // -------------------------------------------------------- Protected methds
526

527     /**
528      * Checks the given SyncItem lists and creates the needed SyncOperations
529      * following the rules described in the class description and in the
530      * architecture document.
531      *
532      * @param principal who has requested the synchronization
533      * @param nextSync timestamp of the current synchronization
534      * @param Am the Am set
535      * @param Bm the Bm set
536      * @param AmBm the AmBm
537      * @param AmBBm the AmBBm
538      * @param AAmBm the AAmBm
539      *
540      * @return an ArrayList containing all the collected sync operations
541      */

542     protected ArrayList checkSyncOperations(Principal JavaDoc principal,
543                                             Timestamp JavaDoc nextSync ,
544                                             List Am ,
545                                             List Bm ,
546                                             List AmBm ,
547                                             List AmBBm ,
548                                             List AAmBm ) {
549         SyncItemMapping mapping = null;
550         SyncItem syncItemA = null, syncItemB = null;
551
552         ArrayList all = new ArrayList();
553         ArrayList operations = new ArrayList();
554
555         // ---------------------------------------------------------------------
556

557         all.addAll(AmBm );
558         all.addAll(AmBBm);
559         all.addAll(AAmBm);
560
561         //
562
// 1st check: items in both sources
563
//
564
Iterator i = all.iterator();
565         while (i.hasNext()) {
566             mapping = (SyncItemMapping)i.next();
567
568             syncItemA = mapping.getSyncItemA();
569             syncItemB = mapping.getSyncItemB();
570
571             operations.add(
572                 checkSyncOperation(
573                     principal,
574                     nextSync ,
575                     syncItemA,
576                     syncItemB
577                 )
578             );
579             Am.remove(syncItemA);
580             Bm.remove(syncItemB);
581         }
582
583         //
584
// 2nd check: items in source A and not in source B
585
//
586
i = Am.iterator();
587         while (i.hasNext()) {
588            syncItemA = (SyncItem)i.next();
589
590            operations.add(checkSyncOperation(principal, nextSync, syncItemA, null));
591         } // next i
592

593         //
594
// 3rd check: items in source B and not in source A
595
//
596
i = Bm.iterator();
597         while (i.hasNext()) {
598            syncItemB = (SyncItem)i.next();
599
600            operations.add(checkSyncOperation(principal, nextSync, null, syncItemB));
601         } // next i
602

603         return operations;
604     }
605
606     /**
607      * Create a SyncOperation based on the state of the given SyncItem couple.
608      *
609      * @param principal the entity that wnats to do the operation
610      * @param nextSync timestamp of the current synchronization (used for "B" operation)
611      * @param syncItemA the SyncItem of the source A - NULL means <i>not existing</i/
612      * @param syncItemB the SyncItem of the source B - NULL means <i>not existing</i/
613      *
614      * @return the SyncOperation object
615      */

616     protected SyncOperation checkSyncOperation(Principal JavaDoc principal,
617                                                Timestamp JavaDoc nextSync ,
618                                                SyncItem syncItemA,
619                                                SyncItem syncItemB) {
620         if (log.isLoggable(Level.FINEST)) {
621             log.finest( "check: syncItemA: "
622                     + syncItemA
623                     + " syncItemB: "
624                     + syncItemB
625                     );
626         }
627
628         if (syncItemA == null) {
629             syncItemA = SyncItemImpl.getNotExistingSyncItem(null);
630         }
631         if (syncItemB == null) {
632             syncItemB = SyncItemImpl.getNotExistingSyncItem(null);
633         }
634
635         switch (syncItemA.getState()) {
636             //
637
// NEW
638
//
639
case SyncItemState.NEW:
640                 switch (syncItemB.getState()) {
641                     case SyncItemState.NEW:
642                         return new SyncConflict(syncItemA, syncItemB,
643                                                 String.valueOf(SyncItemState.NEW) +
644                                                 String.valueOf(SyncItemState.NEW) );
645                     case SyncItemState.UPDATED:
646                         return new SyncConflict(syncItemA, syncItemB,
647                                                 String.valueOf(SyncItemState.NEW) +
648                                                 String.valueOf(SyncItemState.UPDATED) );
649                    case SyncItemState.DELETED:
650                        return new SyncConflict(syncItemA, syncItemB,
651                                                 String.valueOf(SyncItemState.NEW) +
652                                                 String.valueOf(SyncItemState.DELETED) );
653                    case SyncItemState.SYNCHRONIZED:
654                        return new SyncConflict(syncItemA, syncItemB,
655                                                 String.valueOf(SyncItemState.NEW) +
656                                                 String.valueOf(SyncItemState.SYNCHRONIZED) );
657                    case SyncItemState.NOT_EXISTING:
658                        syncItemA.setPropertyValue(syncItemB.PROPERTY_TIMESTAMP, nextSync);
659                        return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NEW, false, true);
660                 } // end inner switch
661

662             //
663
// DELETED
664
//
665
case SyncItemState.DELETED:
666                 switch (syncItemB.getState()) {
667                     case SyncItemState.NEW:
668                         return new SyncConflict(syncItemA, syncItemB,
669                                                 String.valueOf(SyncItemState.DELETED) +
670                                                 String.valueOf(SyncItemState.NEW) );
671                     case SyncItemState.UPDATED:
672                         syncItemB.setState(SyncItemState.NEW);
673                         return new SyncConflict(syncItemA, syncItemB,
674                                                 String.valueOf(SyncItemState.DELETED) +
675                                                 String.valueOf(SyncItemState.NEW) );
676                    case SyncItemState.SYNCHRONIZED:
677                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.DELETE, false, true);
678
679                    case SyncItemState.DELETED:
680                    case SyncItemState.NOT_EXISTING:
681                         return new SyncOperationImpl(principal, syncItemB, syncItemA, SyncOperation.NOP, false, false);
682                 } // end inner switch
683

684             //
685
// UPDATED
686
//
687
case SyncItemState.UPDATED:
688                 switch (syncItemB.getState()) {
689                     case SyncItemState.NEW:
690                         return new SyncConflict(syncItemA, syncItemB,
691                                                 String.valueOf(SyncItemState.UPDATED) +
692                                                 String.valueOf(SyncItemState.NEW) );
693                     case SyncItemState.UPDATED:
694                         return new SyncConflict(syncItemA, syncItemB,
695                                                 String.valueOf(SyncItemState.UPDATED) +
696                                                 String.valueOf(SyncItemState.UPDATED) );
697                     case SyncItemState.DELETED:
698                         return new SyncConflict(syncItemA, syncItemB,
699                                                 String.valueOf(SyncItemState.UPDATED) +
700                                                 String.valueOf(SyncItemState.DELETED) );
701                     case SyncItemState.SYNCHRONIZED:
702                         syncItemA.setPropertyValue(syncItemB.PROPERTY_TIMESTAMP, nextSync);
703                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.UPDATE, false, true);
704                     case SyncItemState.NOT_EXISTING:
705                         syncItemA.setPropertyValue(syncItemB.PROPERTY_TIMESTAMP, nextSync);
706                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NEW, false, true);
707                 } // end inner switch
708

709            //
710
// SYNCHRONIZED
711
//
712
case SyncItemState.SYNCHRONIZED:
713                 switch (syncItemB.getState()) {
714                     case SyncItemState.NEW:
715                         return new SyncConflict(syncItemA, syncItemB,
716                                                 String.valueOf(SyncItemState.SYNCHRONIZED) +
717                                                 String.valueOf(SyncItemState.NEW) );
718                     case SyncItemState.UPDATED:
719                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.UPDATE, true, false);
720                     case SyncItemState.DELETED:
721                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.DELETE, true, false);
722                     case SyncItemState.SYNCHRONIZED:
723                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NOP, false, false);
724                     case SyncItemState.NOT_EXISTING:
725                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NEW, false, true);
726                 } // end inner switch
727

728            //
729
// NOTEXISITNG
730
//
731
case SyncItemState.NOT_EXISTING:
732                 switch (syncItemB.getState()) {
733                     case SyncItemState.NEW:
734                     case SyncItemState.UPDATED:
735                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NEW, true, false);
736                     case SyncItemState.SYNCHRONIZED:
737                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NEW, true, false);
738                     case SyncItemState.NOT_EXISTING:
739                     case SyncItemState.DELETED:
740                         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NOP, false, false);
741                 } // end inner switch
742

743            //
744
// PARTIAL
745
//
746
case SyncItemState.PARTIAL:
747                 switch (syncItemB.getState()) {
748                     case SyncItemState.NEW:
749                         return new SyncConflict(syncItemA, syncItemB,
750                                                 String.valueOf(SyncItemState.UPDATED) +
751                                                 String.valueOf(SyncItemState.NEW) );
752                     case SyncItemState.UPDATED:
753                         return new SyncConflict(syncItemA, syncItemB,
754                                                 String.valueOf(SyncItemState.UPDATED) +
755                                                 String.valueOf(SyncItemState.UPDATED) );
756                     case SyncItemState.DELETED:
757                         return new SyncConflict(syncItemA, syncItemB,
758                                                 String.valueOf(SyncItemState.UPDATED) +
759                                                 String.valueOf(SyncItemState.DELETED) );
760                     case SyncItemState.SYNCHRONIZED:
761                         return new SyncOperationImpl(principal, syncItemA, null, SyncOperation.ACCEPT_CHUNK, false, true);
762                     case SyncItemState.NOT_EXISTING:
763                         return new SyncOperationImpl(principal, syncItemA, null, SyncOperation.ACCEPT_CHUNK, false, true);
764                 } // end inner switch
765

766             //
767
// CONFLICT
768
// In this case itemA has a mapped twin and itemB not existing
769
//
770
case SyncItemState.CONFLICT:
771                 return new SyncConflict(syncItemA, syncItemB, "CX");
772         } // end switch
773

774         return new SyncOperationImpl(principal, syncItemA, syncItemB, SyncOperation.NOP, false, false);
775     }
776
777     /**
778      * Executes the given SyncOperation. Note that conflicts are ignored!
779      * <p>
780      * Note also that the number of status returned is equal to the number of
781      * sources affected by the operation (0 for NOP, 1 for NEW and UPDATE and
782      * 1 or 2 for DELETE).
783      *
784      * @param operation the SyncOperation to execute
785      *
786      * @return an array of <i>SyncOperationStatus</i> objects representing the
787      * status of the executed operation. For instance, in case of error,
788      * <i>SyncOperation.error</i> will be set to the catched exception.
789      */

790     protected SyncOperationStatus[] execSyncOperation(SyncOperationImpl operation) {
791         SyncItem syncItemA = operation.getSyncItemA(),
792                  syncItemB = operation.getSyncItemB();
793
794         Principal JavaDoc owner = operation.getOwner();
795         SyncOperationStatus[] status = null;
796         ModificationCommand cmd = null;
797
798         int size = 0, s = 0;
799
800         switch (operation.getOperation()) {
801             case SyncOperation.NEW:
802                 status = new SyncOperationStatus[1];
803                 if (operation.isAOperation()) {
804                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
805                     try {
806                         syncItemA = sources[1].setSyncItem(owner, syncItemB);
807                         operation.setSyncItemA(syncItemA);
808                         status[0] = new Sync4jOperationStatusOK(operation, sources[1], cmd, StatusCode.ITEM_ADDED);
809                     } catch (SyncException e) {
810                         log.severe("Error executing sync operation: " + e.getMessage());
811                         log.throwing(getClass().getName(), "execSyncOperation", e);
812                         status[0] = new Sync4jOperationStatusError(operation, sources[1], cmd, e);
813                         operation.setAOperation(false);
814                         operation.setBOperation(false);
815                     }
816                 } else if (operation.isBOperation()) {
817                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
818                     syncItemB.setProperty(syncItemA.getProperty(SyncItem.PROPERTY_TIMESTAMP)); // this contains the
819
// current sync
820
try {
821                         checkSize(syncItemA);
822                         syncItemB = sources[0].setSyncItem(owner, syncItemA);
823                         operation.setSyncItemB(syncItemB);
824                         status[0] = new Sync4jOperationStatusOK(operation, sources[1], cmd, StatusCode.ITEM_ADDED);
825                     } catch (ObjectSizeMismatchException e) {
826                         log.info(e.getMessage());
827                         status[0] = new Sync4jOperationStatusError(operation, sources[0], cmd, e);
828                         operation.setAOperation(false);
829                         operation.setBOperation(false);
830                     } catch (SyncException e) {
831                         log.severe("Error executing sync operation: " + e.getMessage());
832                         log.throwing(getClass().getName(), "execSyncOperation", e);
833                         status[0] = new Sync4jOperationStatusError(operation, sources[0], cmd, e);
834                         operation.setAOperation(false);
835                         operation.setBOperation(false);
836                     }
837                 }
838                 break;
839
840             case SyncOperation.UPDATE:
841                 status = new SyncOperationStatus[1];
842                 if (operation.isAOperation()) {
843                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
844                     try {
845                         syncItemA = sources[1].setSyncItem(owner, syncItemB);
846                         operation.setSyncItemA(syncItemA);
847                         status[0] = new Sync4jOperationStatusOK(operation, sources[1], cmd);
848                     } catch (SyncException e) {
849                         log.severe("Error executing sync operation: " + e.getMessage());
850                         log.throwing(getClass().getName(), "execSyncOperation", e);
851                         status[0] = new Sync4jOperationStatusError(operation, sources[1], cmd, e);
852                         operation.setAOperation(false);
853                         operation.setBOperation(false);
854                     }
855                 } else if (operation.isBOperation()) {
856                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
857                     syncItemB.setProperty(syncItemA.getProperty(SyncItem.PROPERTY_TIMESTAMP)); // this contains the
858
// current sync
859
try {
860                         checkSize(syncItemA);
861                         syncItemB = sources[0].setSyncItem(owner, syncItemA);
862                         operation.setSyncItemB(syncItemB);
863                         status[0] = new Sync4jOperationStatusOK(operation, sources[0], cmd);
864                     } catch (ObjectSizeMismatchException e) {
865                         log.info(e.getMessage());
866                         status[0] = new Sync4jOperationStatusError(operation, sources[0], cmd, e);
867                         operation.setAOperation(false);
868                         operation.setBOperation(false);
869                     } catch (SyncException e) {
870                         log.severe("Error executing sync operation: " + e.getMessage());
871                         log.throwing(getClass().getName(), "execSyncOperation", e);
872                         status[0] = new Sync4jOperationStatusError(operation, sources[0], cmd, e);
873                         operation.setAOperation(false);
874                         operation.setBOperation(false);
875                     }
876                 }
877                 break;
878
879             case SyncOperation.DELETE:
880                 //
881
// How many status we need? One if we have to delete the item
882
// from only one source, two if we have to delete it from both
883
// sources
884
//
885
size = 1;
886                 if (operation.isAOperation() && operation.isBOperation()) {
887                     size = 2;
888                 }
889                 status = new SyncOperationStatus[size];
890
891                 s = 0;
892                 if (operation.isBOperation()) {
893                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
894                     syncItemB.setProperty(syncItemA.getProperty(SyncItem.PROPERTY_TIMESTAMP)); // this contains the
895
// timestamp of the
896
// current sync
897
try {
898                         sources[0].removeSyncItem(owner, syncItemB);
899                         status[s++] = new Sync4jOperationStatusOK(operation, sources[0], cmd);
900                     } catch (SyncException e) {
901                         log.severe("Error executing sync operation: " + e.getMessage());
902                         log.throwing(getClass().getName(), "execSyncOperation", e);
903                         status[s++] = new Sync4jOperationStatusError(operation, sources[0], cmd, e);
904                         operation.setAOperation(false);
905                         operation.setBOperation(false);
906                     }
907                 }
908                 if (operation.isAOperation()) {
909                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
910
911                     try {
912                         sources[1].removeSyncItem(owner, syncItemA);
913                         status[s++] = new Sync4jOperationStatusOK(operation, sources[1], cmd);
914                     } catch (SyncException e) {
915                         log.severe("Error executing sync operation: " + e.getMessage());
916                         log.throwing(getClass().getName(), "execSyncOperation", e);
917                         status[s++] = new Sync4jOperationStatusError(operation, sources[1], cmd, e);
918                         operation.setAOperation(false);
919                         operation.setBOperation(false);
920                     }
921                 }
922                 break;
923
924             case SyncOperation.NOP:
925                 //
926
// A NOP operation can be due by one of the following conditions
927
// (see checkOperation(...)):
928
//
929
// 1. both items are flagged as "Deleted"
930
// 2. both items are flagged "Synchronized"
931
// 3. itemA is "Not existing" and itemB is "Not existing" or "Deleted"
932
//
933
// In case 1. we want a status is returned, thought no real
934
// action is performed.
935
//
936
s = 0;
937                 if (operation.isAOperation()) ++size;
938                 if (operation.isBOperation()) ++size;
939
940                 status = new SyncOperationStatus[size];
941
942                 if (operation.isBOperation()) {
943                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
944                     status[s++] = new Sync4jOperationStatusOK(operation, sources[0], cmd);
945                 }
946
947                 if (operation.isAOperation()) {
948                     cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
949                     status[s++] = new Sync4jOperationStatusOK(operation, sources[1], cmd);
950                 }
951
952                 if (!operation.isAOperation() && !operation.isBOperation()) {
953                     char stateItemA = syncItemA.getState();
954                     char stateItemB = syncItemB.getState();
955
956                     switch (stateItemA) {
957                         case SyncItemState.SYNCHRONIZED:
958                             if (stateItemB == SyncItemState.SYNCHRONIZED) {
959                                 status = new SyncOperationStatus[1];
960                                 cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
961
962                                 if (cmd instanceof Add) {
963                                     status[0] = new Sync4jOperationStatusOK(operation, sources[1], cmd, StatusCode.ITEM_ADDED);
964                                 } else {
965                                     status[0] = new Sync4jOperationStatusOK(operation, sources[1], cmd);
966                                 }
967                                 break;
968                             }
969                         case SyncItemState.NOT_EXISTING:
970                             if (stateItemB == SyncItemState.DELETED) {
971                                 //
972
//client sent Delete item that not exist on server
973
//
974
try {
975                                     cmd = (ModificationCommand)syncItemB.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
976                                     syncItemA = sources[1].setSyncItem(owner, syncItemB);
977                                     operation.setSyncItemA(syncItemA);
978
979                                     status = new SyncOperationStatus[1];
980                                     status[0] = new Sync4jOperationStatusOK(
981                                                 operation,
982                                                 sources[1],
983                                                 cmd,
984                                                 StatusCode.ITEM_NOT_DELETED
985                                                 );
986                                     break;
987                                 } catch (SyncException e) {
988                                     log.severe("Error executing sync operation: " + e.getMessage());
989                                     log.throwing(getClass().getName(), "execSyncOperation", e);
990                                     status[0] = new Sync4jOperationStatusError(operation, sources[1], cmd, e);
991                                     operation.setAOperation(false);
992                                     operation.setBOperation(false);
993                                 }
994                             }
995                         case SyncItemState.DELETED:
996                             if (stateItemB == SyncItemState.DELETED) {
997                                 status = new SyncOperationStatus[1];
998                                 cmd = (ModificationCommand)syncItemB.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
999                                 status[0] = new Sync4jOperationStatusOK(operation, sources[1], cmd);
1000                                break;
1001                            }
1002                    }
1003                }
1004
1005                break;
1006
1007            case SyncOperation.ACCEPT_CHUNK:
1008                status = new SyncOperationStatus[1];
1009                cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
1010                status[0] = new Sync4jOperationStatusOK(
1011                                operation, sources[0], cmd, StatusCode.CHUNKED_ITEM_ACCEPTED
1012                            );
1013                break;
1014
1015            case SyncOperation.CONFLICT:
1016                //
1017
// The conflic is solved with data from server
1018
//
1019
try {
1020                    status = new SyncOperationStatus[1];
1021                    SyncConflict sc = (SyncConflict)operation;
1022                    cmd = (ModificationCommand)syncItemA.getPropertyValue(SyncItemHelper.PROPERTY_COMMAND);
1023
1024                    //
1025
//Type CX means that itemA is in state CONFLICT and itemB is in state NOT_EXISTING
1026
//This situation happens when itemA has already a twin mapped
1027
//
1028
SyncItem resolvingItem = null;
1029                    if (sc.getType().equals(sc.STATE_CONFLICT_NONE)) {
1030                        resolvingItem = new SyncItemImpl(syncItemA.getSyncSource(), syncItemA.getMappedKey());
1031                        resolvingItem.setProperties(syncItemA.getProperties());
1032                        sources[1].removeSyncItem(owner, resolvingItem);
1033                    } else if (sc.getType().equals(sc.STATE_NEW_NEW)) {
1034                        //
1035
// In this case, the items won't be mapped, therefore
1036
// because server wins, the server item is returned
1037
//
1038
resolvingItem = new SyncItemImpl(
1039                                            syncItemB.getSyncSource(),
1040                                            syncItemB.getKey().getKeyAsString(),
1041                                            syncItemB.getKey().getKeyAsString(),
1042                                            SyncItemState.UNKNOWN
1043                        );
1044                        resolvingItem.setProperties(syncItemB.getProperties());
1045
1046                        syncItemA = sources[1].setSyncItem(owner, resolvingItem);
1047                        operation.setSyncItemA(syncItemA);
1048                    } else {
1049                        SyncItemKey luid = syncItemA.getMappedKey();
1050                        if (luid == null) {
1051                            throw new SyncException( "Item A with key "
1052                                                   + syncItemA.getKey()
1053                                                   + " should have a mapping, but the mapped key is null"
1054                                                   );
1055                        }
1056                        resolvingItem = new SyncItemImpl(
1057                                            syncItemB.getSyncSource(),
1058                                            syncItemA.getMappedKey().getKeyAsString()
1059                        );
1060                        resolvingItem.setProperties(syncItemB.getProperties());
1061
1062                        syncItemA = sources[1].setSyncItem(owner, resolvingItem);
1063                        operation.setSyncItemA(syncItemA);
1064                    }
1065                    operation.setAOperation(true);
1066
1067                    if (syncItemA.getState() == SyncItemState.NEW &&
1068                        syncItemB.getState() == SyncItemState.SYNCHRONIZED
1069                       ) {
1070                        //
1071
// This happens when the Client sent an item that have a twin
1072
// synchronized on server.
1073
//
1074
status[0] = new Sync4jOperationStatusConflict(
1075                                    operation,
1076                                    sources[1],
1077                                    cmd,
1078                                    StatusCode.ALREADY_EXISTS
1079                                    );
1080                        break;
1081                    }
1082                    //
1083
// in all the others cases...
1084
//
1085
status[0] = new Sync4jOperationStatusConflict(
1086                                    operation,
1087                                    sources[1],
1088                                    cmd,
1089                                    StatusCode.CONFLICT_RESOLVED_WITH_SERVER_DATA
1090                                );
1091                } catch (SyncException e) {
1092                    log.severe("Error executing sync operation: " + e.getMessage());
1093                    log.throwing(getClass().getName(), "execSyncOperation", e);
1094                    status[0] = new Sync4jOperationStatusError(operation, sources[1], cmd, e);
1095                    operation.setAOperation(false);
1096                    operation.setBOperation(false);
1097                }
1098
1099                break;
1100        } // end switch
1101

1102        return status;
1103    }
1104
1105    // --------------------------------------------------------- Private methods
1106

1107    /**
1108     * If any item from the client has not a corresponding mapping, the
1109     * server source must be queried for the item, since the client item
1110     * could be the same of an existing server item. In this case, the old
1111     * unmapped item is replaced in Am by the newly mapped item.
1112     *
1113     * @param newlyMappedItems the collection that will contain the newly mapped
1114     * items. If null, it won't be used.
1115     * @param syncItems the items to be searched if not mapped
1116     * @param source the source that has to be queried for twins
1117     * @param principal the principal items are belonging to
1118     *
1119     */

1120    private void fixMappedItems(Collection newlyMappedItems,
1121                                SyncItem[] syncItems ,
1122                                SyncSource source ,
1123                                Principal JavaDoc principal ) {
1124        SyncItem itemB = null;
1125
1126        for (int i = 0; ((syncItems != null) && (i<syncItems.length)); ++i) {
1127            itemB = null;
1128            if (!syncItems[i].isMapped() && (SyncItemState.PARTIAL != syncItems[i].getState())) {
1129                try {
1130                    itemB = source.getSyncItemFromTwin(principal, syncItems[i]);
1131                } catch (SyncSourceException e) {
1132                      String JavaDoc msg = "Error retrieving the twin item of "
1133                                 + syncItems[i].getKey()
1134                                 + " from source "
1135                                 + source
1136                                 + ": "
1137                                 + e.getMessage();
1138                      log.severe(msg);
1139                      log.throwing(getClass().getName(), "fixSyncMapping", e);
1140                }
1141                if (itemB != null) {
1142                    if (log.isLoggable(Level.FINEST)) {
1143                        log.finest( "Hey, client item "
1144                                  + syncItems[i].getKey().getKeyAsString()
1145                                  + " is the same of server item "
1146                                  + itemB.getKey().getKeyAsString()
1147                                  + '!'
1148                                  );
1149                    }
1150                    SyncItemMapping mapping = new SyncItemMapping(itemB.getKey());
1151                    mapping.setMapping(syncItems[i], itemB);
1152                    if (newlyMappedItems != null) {
1153                        newlyMappedItems.add(mapping);
1154                    }
1155                    syncItems[i] = SyncItemHelper.newMappedSyncItem(itemB.getKey(), syncItems[i]);
1156                }
1157            }
1158        } // next i
1159
}
1160
1161    /**
1162     * Verifies that the given item the size of its content metches the
1163     * specified size, if specified. In the case of a mismatch, a
1164     * <code>SyncSourceException</code> is thrown with status code
1165     * OBJECT_SIZE_MISMATCH (424).
1166     *
1167     * @param item item to check
1168     *
1169     * @throws ObjectSizeMismatchException if the content size does not match
1170               the declared size (if declared)
1171     */

1172    private void checkSize(SyncItem item)
1173    throws ObjectSizeMismatchException {
1174        Long JavaDoc size = (Long JavaDoc)item.getPropertyValue(item.PROPERTY_SIZE);
1175
1176        if (size == null) {
1177            return;
1178        }
1179
1180        byte[] data = (byte[])item.getPropertyValue(item.PROPERTY_BINARY_CONTENT);
1181
1182        if (data.length != size.intValue()) {
1183            throw new ObjectSizeMismatchException("The size of the received object does not match the given size");
1184        }
1185    }
1186}
1187
1188
Popular Tags