@Override
    public void visit(GalContact contact) throws ServiceException {
      Map<String, Object> attrs = contact.getAttrs();
      String id = contact.getId();
      mappings.remove(id);
      attrs.put(ContactConstants.A_dn, id);
      ZimbraLog.gal.debug("processing gal contact " + id);
      DataSourceItem dsItem = DbDataSource.getReverseMapping(getDataSource(), id);
      addFileAsStr(attrs);
      if (dsItem.itemId == 0) {
        ZimbraLog.gal.debug("creating new contact " + id);
        dsItem.remoteId = id;
        ParsedContact pc = new ParsedContact(attrs);
        dsItem.itemId = mbox.createContact(octxt, pc, fid, null).getId();
        DbDataSource.addMapping(getDataSource(), dsItem);
      } else {
        Contact mboxContact = mbox.getContactById(octxt, dsItem.itemId);

        // check for update conditions
        String syncDate = mboxContact.get(MODIFY_TIMESTAMP);
        String modifiedDate = (String) contact.getAttrs().get(MODIFY_TIMESTAMP);
        if (!force && syncDate != null && syncDate.equals(modifiedDate)) {
          ZimbraLog.gal.debug("gal contact %s has not been modified", id);
          return;
        }
        if (!force && allFieldsMatch(attrs, mboxContact.getAllFields())) {
          ZimbraLog.gal.debug("no field has changed in gal contact %s", id);
          return;
        }

        ZimbraLog.gal.debug("modifying contact " + id);
        ParsedContact pc = new ParsedContact(attrs);
        mbox.modifyContact(octxt, dsItem.itemId, pc);
      }
    }
  public void importGal(int fid, boolean fullSync, boolean force) throws ServiceException {
    mbox.beginTrackingSync();
    DataSource ds = getDataSource();
    DataSourceItem folderMapping = DbDataSource.getMapping(ds, fid);
    if (folderMapping.md == null) {
      folderMapping.itemId = fid;
      folderMapping.md = new Metadata();
      folderMapping.md.put(TYPE, FOLDER);
      DbDataSource.addMapping(ds, folderMapping);
    }
    String syncToken = fullSync ? "" : folderMapping.md.get(SYNCTOKEN, "");
    HashMap<String, DataSourceItem> allMappings = new HashMap<String, DataSourceItem>();
    if (fullSync || force)
      for (DataSourceItem dsItem : DbDataSource.getAllMappings(ds))
        if (dsItem.md == null || dsItem.md.get(TYPE, null) == null) // non-folder items
        allMappings.put(dsItem.remoteId, dsItem);
    OperationContext octxt = new OperationContext(mbox);
    SearchGalResult result =
        SearchGalResult.newSearchGalResult(new GalSearchVisitor(mbox, allMappings, fid, force));
    try {
      searchGal(syncToken, result, true);
    } catch (Exception e) {
      setStatus(false);
      ZimbraLog.gal.error("Error executing gal search", e);
      return;
    }

    folderMapping.md.put(SYNCTOKEN, result.getToken());
    DbDataSource.updateMapping(ds, folderMapping);
    if (allMappings.size() == 0 || !fullSync) {
      setStatus(true);
      return;
    }

    ArrayList<Integer> deleted = new ArrayList<Integer>();
    int[] deletedIds = new int[allMappings.size()];
    int i = 0;
    for (DataSourceItem dsItem : allMappings.values()) {
      deleted.add(dsItem.itemId);
      deletedIds[i++] = dsItem.itemId;
    }
    try {
      mbox.delete(octxt, deletedIds, MailItem.Type.CONTACT, null);
    } catch (ServiceException e) {
      ZimbraLog.gal.warn("Ignoring error deleting gal contacts", e);
    }
    DbDataSource.deleteMappings(getDataSource(), deleted);
    mbox.index.optimize();
    setStatus(true);
  }