source: contrib/psync/src/main/java/br/com/prognus/psync/engine/source/PIMContactSyncSource.java @ 1009

Revision 1009, 17.3 KB checked in by wmerlotto, 15 years ago (diff)

Ticket #554 - Commit da versão inicial do psync.

Line 
1/**
2 *
3 * @author Diorgenes Felipe Grzesiuk <diorgenes@prognus.com.br>
4 * @copyright Copyright 2007-2008 Prognus
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as published by
8 * the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Foobar; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19package br.com.prognus.psync.engine.source;
20
21import java.io.ByteArrayInputStream;
22import java.sql.Timestamp;
23import java.util.List;
24
25import br.com.prognus.psync.exception.EntityException;
26import br.com.prognus.psync.items.manager.PIMContactManager;
27
28import com.funambol.common.pim.contact.Contact;
29import com.funambol.common.pim.converter.ContactToSIFC;
30import com.funambol.common.pim.converter.ContactToVcard;
31import com.funambol.common.pim.sif.SIFCParser;
32import com.funambol.common.pim.vcard.VcardParser;
33import com.funambol.framework.engine.SyncItem;
34import com.funambol.framework.engine.SyncItemImpl;
35import com.funambol.framework.engine.SyncItemKey;
36import com.funambol.framework.engine.SyncItemState;
37import com.funambol.framework.engine.source.SyncContext;
38import com.funambol.framework.engine.source.SyncSourceException;
39import com.funambol.framework.tools.beans.BeanInitializationException;
40
41public class PIMContactSyncSource extends PIMSyncSource {
42
43        // --------------------------------------------------------------Private
44        // data
45
46        private transient PIMContactManager manager;
47
48        // ------------------------------------------------------------Public
49        // methods
50
51        @Override
52        public void beginSync(SyncContext context) {
53
54                this.manager = new PIMContactManager(JNDI_DATA_SOURCE_NAME, context
55                                .getPrincipal());
56                super.manager = this.manager;
57                super.beginSync(context);
58        }
59
60        /**
61         * Makes an array of SyncItemKey objects representing the ID(s) of the
62         * twin(s) of a given contact.
63         *
64         * @param syncItem
65         *            the SyncItem representing the contact whose twin(s) are looked
66         *            for
67         * @throws SyncSourceException
68         * @return possibly, just one or no key should be in the array, but it can't
69         *         be ruled out a priori that several keys get returned by this
70         *         method
71         */
72        @Override
73        public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem syncItem)
74                        throws SyncSourceException {
75
76                try {
77                        List idList = this.manager.getTwins(convert(syncItem));
78                        SyncItemKey[] keyList = new SyncItemKey[idList.size()];
79                        for (int i = 0; i < idList.size(); i++) {
80                                keyList[i] = new SyncItemKey(idList.get(i));
81                        }
82                        return keyList;
83                } catch (EntityException e) {
84                        throw new SyncSourceException("Error retrieving twin item keys.", e);
85                }
86        }
87
88        /**
89         * Adds a SyncItem object (representing a contact).
90         *
91         * @param syncItem
92         *            the SyncItem representing the contact
93         *
94         * @return a newly created syncItem based on the input object but with its
95         *         status set at SyncItemState.NEW and the GUID retrieved by the
96         *         back-end
97         */
98        @Override
99        public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException {
100
101                if (log.isTraceEnabled()) {
102                        log.trace("PIMContactSyncSource addSyncItem begin");
103                }
104
105                Contact c = null;
106                String content = null;
107
108                try {
109
110                        content = getContentFromSyncItem(syncItem);
111                        String contentType = syncItem.getType();
112
113                        c = convert(content, contentType);
114                        Timestamp ts = syncItem.getTimestamp();
115
116                        // Adds the contact, wraps it in sync information and uses it to
117                        // create a new SyncItem which is the return value of this method
118                        SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
119                                        this.manager.addItem(c, ts), // key
120                                        null, // mappedKey
121                                        SyncItemState.NEW, // state
122                                        content.getBytes(), // content
123                                        null, // format
124                                        contentType, // type
125                                        ts // timestamp
126                        );
127
128                        return newSyncItem;
129
130                } catch (Exception e) {
131                        log.error("SyncSource error adding a new synchronization item", e);
132                        throw new SyncSourceException("Error adding the item " + syncItem,
133                                        e);
134                }
135        }
136
137        /**
138         * Updates a SyncItem object (representing a contact).
139         *
140         * @param syncItem
141         *            the SyncItem representing the contact
142         *
143         * @return a newly created syncItem based on the input object but with its
144         *         status set at SyncItemState.UPDATED and the GUID retrieved by the
145         *         back-end
146         */
147        @Override
148        public SyncItem updateSyncItem(SyncItem syncItem)
149                        throws SyncSourceException {
150
151                if (log.isTraceEnabled()) {
152                        log.trace("updateSyncItem from " + this.sourceURI);
153                }
154
155                Contact c = null;
156                String content = null;
157
158                try {
159
160                        String id = syncItem.getKey().getKeyAsString();
161                        content = getContentFromSyncItem(syncItem);
162                        String contentType = syncItem.getType();
163
164                        c = convert(content, contentType);
165
166                        // Modifies the contact, wraps it in sync information and uses it to
167                        // create a new SyncItem which is the return value of this method
168                        SyncItemImpl newSyncItem = new SyncItemImpl(this, // syncSource
169                                        this.manager.updateItem(id, c, syncItem.getTimestamp()), // key
170                                        null, // mappedKey
171                                        SyncItemState.UPDATED, // state
172                                        content.getBytes(), // content
173                                        null, // format
174                                        contentType, // type
175                                        null // timestamp
176                        );
177
178                        return newSyncItem;
179
180                } catch (Exception e) {
181                        log
182                                        .error(
183                                                        "SyncSource error updating a new synchronization item",
184                                                        e);
185                        throw new SyncSourceException(
186                                        "Error updating the item " + syncItem, e);
187                }
188        }
189
190        /**
191         * Deletes the item with a given syncItemKey.
192         *
193         * @param syncItemKey
194         * @param timestamp
195         *            in case of a soft deletion, this will be the registered moment
196         *            of deletion; if a hard deletion is used, this field is
197         *            irrelevant and may also be null
198         * @param softDelete
199         *            it is true if the client requires a soft deletion
200         * @throws SyncSourceException
201         */
202        @Override
203        public void removeSyncItem(SyncItemKey syncItemKey, Timestamp timestamp,
204                        boolean softDelete) throws SyncSourceException {
205
206                try {
207
208                        if (!softDelete) {
209                                if (log.isTraceEnabled()) {
210                                        log.trace("PIMContactSyncSource remove the SyncItem");
211                                }
212
213                                this.manager.removeItem(syncItemKey.getKeyAsString());
214
215                                if (log.isTraceEnabled()) {
216                                        log.trace("PIMContactSyncSource removed SyncItem");
217                                }
218                        }
219
220                } catch (EntityException e) {
221                        log.error("Sync source error: could not delete item with key"
222                                        + syncItemKey, e);
223                        throw new SyncSourceException("Error deleting item. ", e);
224                }
225
226        }
227
228        @Override
229        public SyncItem getSyncItemFromId(SyncItemKey syncItemKey)
230                        throws SyncSourceException {
231
232                String id = null;
233                SyncItem syncItem = null;
234
235                id = syncItemKey.getKeyAsString();
236
237                if (log.isTraceEnabled()) {
238                        log.trace("PIMContactSyncSource get SyncItem from id " + id);
239                }
240
241                try {
242
243                        // Retrieves the contact, wraps it in sync information and uses it
244                        // to create a new SyncItem which is the return value of this method
245                        syncItem = createSyncItem(id,
246                                        this.manager.getItem(id).getContact(), SyncItemState.NEW);
247
248                } catch (EntityException e) {
249                        throw new SyncSourceException("Error seeking SyncItem with ID: "
250                                        + id, e);
251                }
252
253                return syncItem;
254
255        }
256
257        public boolean mergeSyncItems(SyncItemKey syncItemKey, SyncItem syncItem)
258                        throws SyncSourceException {
259
260                try {
261
262                        Contact contact = convert(getContentFromSyncItem(syncItem),
263                                        syncItem.getType());
264
265                        boolean clientUpdateRequired = this.manager.mergeItems(syncItemKey
266                                        .getKeyAsString(), contact, syncItem.getTimestamp());
267
268                        if (clientUpdateRequired) {
269                                syncItem = getSyncItemFromId(syncItemKey);
270                        }
271
272                        // return clientUpdateRequired;
273                        return true;
274
275                } catch (EntityException e) {
276
277                        log.error("SyncSource error: a merge did not succeed.", e);
278                        throw new SyncSourceException("Error merging SyncItem with key '"
279                                        + syncItemKey + "' with SyncItem '" + syncItem + "'", e);
280                }
281        }
282
283        public void init() throws BeanInitializationException {
284        }
285
286        /**
287         * Makes an array of SyncItemKey objects representing all new contact IDs,
288         * filtered according to a given time interval.
289         *
290         * @param since
291         *            the earlier limit of the time interval
292         * @param to
293         *            the later limit of the time interval
294         * @return a SyncItemKey array
295         */
296        @Override
297        public SyncItemKey[] getNewSyncItemKeys(Timestamp since, Timestamp to)
298                        throws SyncSourceException {
299
300                saveSyncTiming(since, to);
301
302                try {
303                        List idList = this.manager.getNewItems(since, to);
304                        SyncItemKey[] keyList = new SyncItemKey[idList.size()];
305                        for (int i = 0; i < idList.size(); i++) {
306                                keyList[i] = new SyncItemKey(idList.get(i));
307                        }
308                        return keyList;
309                } catch (EntityException e) {
310                        throw new SyncSourceException("Error retrieving new item keys.", e);
311                }
312        }
313
314        /**
315         * Makes an array of SyncItemKey objects representing all deleted contact
316         * IDs, filtered according to a given time interval.
317         *
318         * @param since
319         *            the earlier limit of the time interval
320         * @param to
321         *            the later limit of the time interval
322         * @return a SyncItemKey array
323         */
324        public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp since, Timestamp to)
325                        throws SyncSourceException {
326
327                saveSyncTiming(since, to);
328
329                try {
330                        List idList = this.manager.getUpdatedItems(since, to);
331                        SyncItemKey[] keyList = new SyncItemKey[idList.size()];
332                        for (int i = 0; i < idList.size(); i++) {
333                                keyList[i] = new SyncItemKey(idList.get(i));
334                        }
335                        return keyList;
336                } catch (EntityException e) {
337                        throw new SyncSourceException(
338                                        "Error retrieving updated item keys.", e);
339                }
340        }
341
342        /**
343         * Makes an array of SyncItemKey objects representing all deleted contact
344         * IDs, filtered according to a given time interval.
345         *
346         * @param since
347         *            the earlier limit of the time interval
348         * @param to
349         *            the later limit of the time interval
350         * @return a SyncItemKey array
351         */
352        @Override
353        public SyncItemKey[] getDeletedSyncItemKeys(Timestamp since, Timestamp to)
354                        throws SyncSourceException {
355
356                saveSyncTiming(since, to);
357
358                try {
359                        List idList = this.manager.getDeletedItems(since, to);
360                        SyncItemKey[] keyList = new SyncItemKey[idList.size()];
361                        for (int i = 0; i < idList.size(); i++) {
362                                keyList[i] = new SyncItemKey(idList.get(i));
363                        }
364                        return keyList;
365                } catch (EntityException e) {
366                        throw new SyncSourceException(
367                                        "Error retrieving deleted item keys.", e);
368                }
369        }
370
371        /**
372         * Makes an array of SyncItemKey objects representing all contact IDs.
373         *
374         * @return a SyncItemKey array
375         */
376        @Override
377        public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {
378
379                try {
380                        List idList = this.manager.getAllItems();
381                        SyncItemKey[] keyList = new SyncItemKey[idList.size()];
382                        for (int i = 0; i < idList.size(); i++) {
383                                keyList[i] = new SyncItemKey(idList.get(i));
384                        }
385                        return keyList;
386                } catch (EntityException e) {
387                        throw new SyncSourceException("Error retrieving all item keys. ", e);
388                }
389
390        }
391
392        // ---------------------------------------------------------- Private
393        // methods
394
395        private Contact sif2Contact(String sifc) throws EntityException {
396
397                if (log.isTraceEnabled()) {
398                        StringBuilder sb = new StringBuilder(sifc.length() + 60);
399                        sb.append("Converting: SIF-C => Contact").append("\nINPUT = {")
400                                        .append(sifc).append('}');
401                        log.trace(sb.toString());
402                }
403
404                ByteArrayInputStream buffer = null;
405                SIFCParser parser = null;
406                Contact contact = null;
407                try {
408                        contact = new Contact();
409                        buffer = new ByteArrayInputStream(sifc.getBytes());
410                        if ((sifc.getBytes()).length > 0) {
411                                parser = new SIFCParser(buffer);
412                                contact = parser.parse();
413                        }
414                } catch (Exception e) {
415                        throw new EntityException("Error converting SIF-C to Contact. ", e);
416                }
417
418                if (log.isTraceEnabled()) {
419                        log.trace("Conversion done.");
420                }
421                return contact;
422        }
423
424        private Contact vcard2Contact(String vcard) throws EntityException {
425
426                if (log.isTraceEnabled()) {
427                        StringBuilder sb = new StringBuilder(vcard.length() + 60);
428                        sb.append("Converting: VCARD => Contact").append("\nINPUT = {")
429                                        .append(vcard).append('}');
430                        log.trace(sb.toString());
431                }
432
433                ByteArrayInputStream buffer = null;
434                VcardParser parser = null;
435                Contact contact = null;
436                try {
437                        contact = new Contact();
438
439                        buffer = new ByteArrayInputStream(vcard.getBytes());
440                        if ((vcard.getBytes()).length > 0) {
441                                parser = new VcardParser(buffer,
442                                                this.deviceTimeZoneDescription, this.deviceCharset);
443                                contact = parser.vCard();
444                        }
445                } catch (Exception e) {
446                        throw new EntityException("Error converting VCARD to Contact. ", e);
447                }
448
449                if (log.isTraceEnabled()) {
450                        log.trace("Conversion done.");
451                }
452                return contact;
453        }
454
455        private String contact2sif(Contact contact) throws EntityException {
456
457                if (log.isTraceEnabled()) {
458                        log.trace("Converting: Contact => SIF-C");
459                }
460
461                String xml = null;
462                try {
463                        // this.deviceTimeZone, this.deviceCharset
464                        ContactToSIFC c2xml = new ContactToSIFC(null, null);
465                        xml = c2xml.convert(contact);
466
467                        if (log.isTraceEnabled()) {
468                                log.trace("OUTPUT = {" + xml + "}. Conversion done.");
469                        }
470                } catch (Exception e) {
471                        throw new EntityException("Error converting Contact to SIF-C. ", e);
472                }
473                return xml;
474        }
475
476        private String contact2vcard(Contact contact) throws EntityException {
477
478                if (log.isTraceEnabled()) {
479                        log.trace("Converting: Contact => VCARD");
480                }
481
482                String vcard = null;
483                try {
484                        ContactToVcard c2vc = new ContactToVcard(this.deviceTimeZone,
485                                        this.deviceCharset);
486                        vcard = c2vc.convert(contact);
487
488                        if (log.isTraceEnabled()) {
489                                log.trace("OUTPUT = {" + vcard + "}. Conversion done.");
490                        }
491                } catch (Exception e) {
492                        throw new EntityException("Error converting Contact to VCARD. ", e);
493                }
494                return vcard;
495        }
496
497        /**
498         * Create a new SyncItem from a Contact. The target contentType and status
499         * are passed as arguments.
500         *
501         * @param contact
502         *            the Contact object representing the input information
503         * @param contentType
504         *            chosen among the TYPE array's elements
505         * @param status
506         * @throws EntityException
507         *             if the content type is wrong or any problem occurs while
508         *             creating a new SyncItem
509         * @return a newly created SyncItem object
510         */
511        private SyncItem createSyncItem(String id, Contact contact, char status)
512                        throws EntityException {
513
514                String contentType = getInfo().getPreferredType().getType();
515
516                if (log.isTraceEnabled()) {
517                        StringBuilder sb = new StringBuilder(100);
518                        sb.append("PIMCalendarSyncSource - creating item with:").append(
519                                        "\n> id: ").append(id).append("\n> status: ")
520                                        .append(status).append("\n> content-type: ").append(
521                                                        contentType);
522                        log.trace(sb.toString());
523                }
524
525                SyncItem syncItem = null;
526                String stream = convert(contact, contentType);
527
528                try {
529                        syncItem = new SyncItemImpl(this, id, status);
530                } catch (Exception e) {
531                        throw new EntityException(e);
532                }
533
534                syncItem.setType(contentType);
535                syncItem.setContent(stream.getBytes());
536
537                if (log.isTraceEnabled()) {
538                        log.trace("PIMContactSyncSource created SyncItem");
539                }
540
541                return syncItem;
542        }
543
544        /**
545         * Converts a SyncItem to a Contact object, provided it represents a contact
546         * item in VCard or SIF-C format.
547         *
548         * @param syncItem
549         * @throws EntityException
550         *             if the contentType is wrong or the conversion attempt doesn't
551         *             succeed.
552         * @return a Contact object
553         */
554        private Contact convert(SyncItem syncItem) throws EntityException {
555                return convert(getContentFromSyncItem(syncItem), syncItem.getType());
556        }
557
558        /**
559         * Converts a contact in vCard or SIF-C format to a Contact object.
560         *
561         * @param content
562         *            as a String
563         * @param contentType
564         * @throws EntityException
565         *             if the contentType is wrong or the conversion attempt doesn't
566         *             succeed.
567         * @return a Contact object
568         */
569        private Contact convert(String content, String contentType)
570                        throws EntityException {
571                // Finds out which target type is required
572                for (int i = 0; i < TYPE.length; i++) {
573                        if (contentType.equals(TYPE[i])) { // Bingo!
574
575                                // Uses the proper converter method
576                                switch (i) {
577                                case VCARD:
578                                        return vcard2Contact(content);
579                                case SIFC:
580                                        return sif2Contact(content);
581                                default:
582                                        throw new EntityException("Can't make a Contact "
583                                                        + "out of a " + TYPE[i] + "!");
584                                }
585                        }
586                }
587                throw new EntityException("Content type unknown: " + contentType);
588        }
589
590        /**
591         * Converts a Contact back to a streamable (vCard, SIF-C) format.
592         *
593         * @param contact
594         * @param contentType
595         * @throws EntityException
596         *             if the contentType is wrong or the conversion attempt doesn't
597         *             succeed.
598         * @return the result in the required format
599         */
600        private String convert(Contact contact, String contentType)
601                        throws EntityException {
602
603                // Finds out which target type is required
604                for (int i = 0; i < TYPE.length; i++) {
605                        if (contentType.equals(TYPE[i])) { // Bingo!
606
607                                // Uses the proper converter method
608                                switch (i) {
609                                case VCARD:
610                                        return contact2vcard(contact);
611                                case SIFC:
612                                        return contact2sif(contact);
613                                default:
614                                        throw new EntityException("Can't make a " + TYPE[i]
615                                                        + "out of a Contact!");
616                                }
617                        }
618                }
619                throw new EntityException("Content type unknown: " + contentType);
620        }
621}
Note: See TracBrowser for help on using the repository browser.