From: Cameron S. <mu...@ea...> - 2007-07-16 16:39:41
|
Author: muriwo Date: 2007-07-16 09:39:41 -0700 (Mon, 16 Jul 2007) New Revision: 3834 Modified: versions/1.0/trunk/hot-deploy/funambol/docs/req/ot_vcard_outlook_mapping.xls versions/1.0/trunk/hot-deploy/funambol/src/org/opentaps/funambol/sync/EntitySyncSource.java versions/1.0/trunk/hot-deploy/funambol/webapp/WEB-INF/opentaps-sync-config.xml Log: 1731743/OSSI-9: Two-Way sync working for Address Modified: versions/1.0/trunk/hot-deploy/funambol/docs/req/ot_vcard_outlook_mapping.xls =================================================================== (Binary files differ) Modified: versions/1.0/trunk/hot-deploy/funambol/src/org/opentaps/funambol/sync/EntitySyncSource.java =================================================================== --- versions/1.0/trunk/hot-deploy/funambol/src/org/opentaps/funambol/sync/EntitySyncSource.java 2007-07-16 11:18:05 UTC (rev 3833) +++ versions/1.0/trunk/hot-deploy/funambol/src/org/opentaps/funambol/sync/EntitySyncSource.java 2007-07-16 16:39:41 UTC (rev 3834) @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -50,6 +51,7 @@ import org.opentaps.funambol.security.OFBizSync4jUser; import org.springframework.context.ApplicationContext; +import com.funambol.common.pim.contact.Address; import com.funambol.common.pim.contact.BusinessDetail; import com.funambol.common.pim.contact.Contact; import com.funambol.common.pim.contact.Name; @@ -283,30 +285,55 @@ // MobileTelephoneNumber buildPhone(contact.getPersonalDetail().getPhones(), contactPartyGV, "PHONE_MOBILE", "MobileTelephoneNumber"); - GenericValue purpose = getPurposeFromParty(contactParty, "GENERAL_LOCATION"); + GenericValue purpose = getPurposeFromParty(contactPartyGV, "PRIMARY_LOCATION"); if(purpose!= null) { - GenericValue postalAddress = purpose.getRelatedOne("PostalAdress"); + GenericValue postalAddress = purpose.getRelatedOne("PostalAddress"); if(postalAddress!=null) { - String city = postalAddress.getString("city"); + String + city = postalAddress.getString("city"), + country = postalAddress.getRelatedOne("CountryGeo").getString("geoName"), + postal = postalAddress.getString("postalCode"), + postOffice = postalAddress.getString("address2"), + street = postalAddress.getString("address1"), + state = postalAddress.getRelatedOne("StateProvinceGeo").getString("geoName"); + + Address address = contact.getBusinessDetail().getAddress(); + if(!UtilValidate.isEmpty(city)) { - BusinessDetail busDetail = contact.getBusinessDetail(); - busDetail.getAddress().getCity().setPropertyValue(postalAddress.getString("city")); + + address.getCity().setPropertyValue(city); } + + if(!UtilValidate.isEmpty(country)) + { + address.getCountry().setPropertyValue(country); + } + + if(!UtilValidate.isEmpty(postal)) + { + address.getPostalCode().setPropertyValue(postal); + } + + if(!UtilValidate.isEmpty(postOffice)) + { + address.getPostOfficeAddress().setPropertyValue(postOffice); + } + + if(!UtilValidate.isEmpty(street)) + { + address.getStreet().setPropertyValue(street); + } + + if(!UtilValidate.isEmpty(state)) + { + address.getState().setPropertyValue(state); + } } } - - /* - busDetail.getAddress().getCountry().setPropertyValue(""); - busDetail.getAddress().getPostalCode().setPropertyValue(""); - busDetail.getAddress().getState().setPropertyValue(""); - busDetail.getAddress().getStreet().setPropertyValue(""); - busDetail.getAddress().getPostOfficeAddress().setPropertyValue(""); - */ - //4. convert Contact -> vcard and return return createItem(partyId, contact2vcard(contact), SyncItemState.UNKNOWN); } @@ -316,9 +343,16 @@ } } - private GenericValue getPurposeFromParty(EntityPreparer contactParty, String purposeType) throws GenericEntityException + private GenericValue getPurposeFromParty(GenericValue contactParty, String purposeType) throws GenericEntityException, SyncSourceException { - return contactParty.getRelated("PartyContactMechPurpose[contactMechPurposeTypeId=" + purposeType + "]"); + List<GenericValue> activePCMPs = contactParty.getRelatedByAnd("PartyContactMechPurpose", UtilMisc.toMap("contactMechPurposeTypeId", purposeType, "thruDate", null)); + + if(activePCMPs.size() > 1){log.warn("Could not find a unique active PCMP for " + purposeType + " and " + contactParty);} + + if(activePCMPs.size() == 0){return null;} + + return activePCMPs.get(0); + } /* * TODO: NOT YET IMPLEMENTED @@ -364,29 +398,33 @@ try { - EntityPreparer contactParty = new EntityPreparer(findByPrimaryKey("Party", UtilMisc.toMap("partyId", partyId))); + GenericValue contactPartyGV = findByPrimaryKey("Party", UtilMisc.toMap("partyId", partyId)); + EntityPreparer contactParty = new EntityPreparer(contactPartyGV); + contact = vcard2Contact(new String(syncItem.getContent())); + addressCompatibilization(contact); + //2.1 if we got here we are going to alter OT data so start a transaction + begin(); - //2.1 if we got here we are going to alter OT data so start a transaction - begin(); + updateOrInsertPostalAddress(contactPartyGV, contact, "PRIMARY_LOCATION"); //update TelecomNumber TODO: use bean query string to avoid need for loop //why partyContactMechpurs.get(0)? - a purpose(PHONE_WORK) is related to only one contactMech(TelecomNumber) for(Phone phone : (List<Phone>)contact.getBusinessDetail().getPhones()) { - updateOrInsertTelecomNumber(contactParty, contact, phone, "PRIMARY_PHONE", "BusinessTelephoneNumber"); + updateOrInsertTelecomNumber(contactPartyGV, contact, phone, "PRIMARY_PHONE", "BusinessTelephoneNumber"); //TODO (after defining purposeType seed data) // BusinessFaxNumber - updateOrInsertTelecomNumber(contactParty, contact, phone, "FAX_NUMBER", "BusinessFaxTelephoneNumber"); + updateOrInsertTelecomNumber(contactPartyGV, contact, phone, "FAX_NUMBER", "BusinessFaxTelephoneNumber"); } for(Phone phone : (List<Phone>)contact.getPersonalDetail().getPhones()) { - updateOrInsertTelecomNumber(contactParty, contact, phone, "PHONE_HOME", "HomeTelephoneNumber"); + updateOrInsertTelecomNumber(contactPartyGV, contact, phone, "PHONE_HOME", "HomeTelephoneNumber"); //TODO (after defining purposeType seed data) //MobileTelephoneNumber - updateOrInsertTelecomNumber(contactParty, contact, phone, "PHONE_MOBILE", "MobileTelephoneNumber"); + updateOrInsertTelecomNumber(contactPartyGV, contact, phone, "PHONE_MOBILE", "MobileTelephoneNumber"); } - + //update Person/PartyGroup TODO:complete Object partyType = contactParty.get("partyTypeId"); if("PERSON".equals(partyType)) @@ -440,7 +478,8 @@ /** * @throws SyncSourceException */ - private void abort(Exception e) throws SyncSourceException + @SuppressWarnings("finally") + private void abort(Exception e) throws SyncSourceException { log.error(e.getMessage()); try @@ -451,22 +490,40 @@ { log.fatal("Could not rollback" + gtx); } - - throw new SyncSourceException(e.getMessage()); + finally + { + throw new SyncSourceException(e); + } } - - private void insertContactMech(String partyId, Contact contact, String purpose) throws GenericEntityException + + private void insertContactMech(String partyId, Contact contact, String purpose, String contactMechType) throws GenericEntityException, SyncSourceException { //create the basic ContactMech which holds everything together String contactMechId = _delegator.getNextSeqId("ContactMech"); - Map createCMParams = UtilMisc.toMap("contactMechId", contactMechId, "contactMechTypeId", "TELECOM_NUMBER"); + Map createCMParams = UtilMisc.toMap("contactMechId", contactMechId, "contactMechTypeId", contactMechType); GenericValue contactMech = _delegator.create("ContactMech",createCMParams); + //now the telecom number - EntityPreparer contactPreparer = new EntityPreparer(contactMech, true); - _converters.toEntityAndStore(contact, contactPreparer.getRelated("TelecomNumber"), purpose); + EntityPreparer contactPreparer = new EntityPreparer(contactMech, true); + if(contactMechType.equals("TELECOM_NUMBER")) + { + _converters.toEntityAndStore(contact, contactPreparer.getRelated("TelecomNumber"), purpose); + } + else if(contactMechType.equals("POSTAL_ADDRESS")) + { + _converters.toEntityAndStore(contact, contactPreparer.getRelated("PostalAddress"), purpose); + } + else if(contactMechType.equals("EMAIL_ADDRESS")) + { + _converters.toEntityAndStore(contact, contactPreparer.getRelated("ContactMech"), purpose); + } + else + { + abort(new IllegalArgumentException("ContactMechType "+ contactMechType + " it's not implemented.")); + } - //create the PartyContactMech which relates person to tel number + //create the PartyContactMech which relates person to mech _delegator.create("PartyContactMech", UtilMisc.toMap("partyId", partyId, "contactMechId", contactMechId, "fromDate", now())); @@ -481,20 +538,16 @@ */ public SyncItem addSyncItem(SyncItem syncItem) throws SyncSourceException { - log.info("addSyncItem(" + - _principal + - " , " + - syncItem.getKey().getKeyAsString() + - ")"); + log.info("addSyncItem("+_principal+" , "+syncItem.getKey().getKeyAsString()+")"); showSyncItem(syncItem); - //1. transformar o SyncItem vcard -> Contact -> ContactMech + //1. transformar o SyncItem vcard -> Contact -> ContactMech Contact contact = null; try { contact = vcard2Contact(new String(syncItem.getContent())); - + addressCompatibilization(contact); //2.1 if we got here we are going to alter OT data so start a transaction begin(); @@ -511,8 +564,8 @@ log.warn("created contactParty " + contactParty); //2.2 create the ContactMech and related details representing tel number - insertContactMech(contactPartyId,contact, "PRIMARY_PHONE"); //TODO: this mapping needs to be more intelligent - //insertContactMech(contactPartyId,contact, "GENERAL_LOCATION"); + insertContactMech(contactPartyId,contact, "PRIMARY_PHONE","TELECOM_NUMBER"); //TODO: this mapping needs to be more intelligent + insertContactMech(contactPartyId,contact, "PRIMARY_LOCATION","POSTAL_ADDRESS"); SyncItemImpl newSyncItem = new SyncItemImpl( this , //syncSource @@ -534,13 +587,12 @@ catch (EntityException e) //TODO: make a standard abort mechanism here { log.error("---ContactERROR---: "+e); - e.printStackTrace(); - return null; + abort(e); } catch(GeneralException badOTX) //TODO: does throwing an X here cause whole sync to fail? { try { rollback(); } catch(GenericTransactionException txX) {} //ignorar TODO: logar - throw new SyncSourceException("OT problem", badOTX); + abort(badOTX); } finally { @@ -549,6 +601,7 @@ try { TransactionUtil.cleanSuspendedTransactions(); } catch(GenericTransactionException txX) {} //TODO: is this really necessary? } } + return null; } /** @@ -590,8 +643,54 @@ return item; } + //TODO:Automatizar no bean + private void addressCompatibilization(Contact contact) throws EntityException, GenericEntityException + { + Address[] addresses = { + contact.getBusinessDetail().getAddress(), + contact.getPersonalDetail().getAddress(), + contact.getPersonalDetail().getOtherAddress() + }; + + for(int i=0 ; i<addresses.length ;i++) + { + String country = addresses[i].getCountry().getPropertyValueAsString(); + if(!UtilValidate.isEmpty(country)) + { + addresses[i].getCountry().setPropertyValue(like("COUNTRY",country)); + } + + String state = addresses[i].getState().getPropertyValueAsString(); + if(!UtilValidate.isEmpty(state)) + { + addresses[i].getState().setPropertyValue(like(null,state)); + } + } + + } + + //TODO: identify "twins"? + private String like(String geoType, String likeXpr) throws GenericEntityException + { + List<EntityExpr> criteria = new ArrayList<EntityExpr>(); + criteria.add(new EntityExpr("geoName", EntityOperator.EQUALS, likeXpr)); + + if(geoType!=null) + { + criteria.add(new EntityExpr("geoTypeId", EntityOperator.EQUALS, geoType)); + } + + List<GenericValue> likes = (List<GenericValue>)_delegator.findByAnd("Geo", criteria); + if(UtilValidate.isEmpty(likes)) + { + log.warn("Could not find expression like:" + likeXpr + " for country."); + return ""; + } + return likes.get(0).getString("geoId"); + } + /** - * Copied from PIMSyncSource + * Copied from PIMSyncSource.vcard2Contact * TODO: refactor and make more generic, put in a centralized place * * @param vcard @@ -699,43 +798,101 @@ { phone = new Phone(); phone.setPropertyType(phoneType); - phone.setPropertyValue(phoneNumberGV.getString("countryCode")+ " " + - phoneNumberGV.getString("areaCode")+ "-" + - phoneNumberGV.getString("contactNumber")); + + String country = phoneNumberGV.getString("countryCode"); + if(UtilValidate.isEmpty(country)) + { + country=""; + } + + String area = phoneNumberGV.getString("areaCode"); + if(UtilValidate.isEmpty(country)) + { + area=""; + } + else + { + area = " ("+area+") "; + } + String contact = phoneNumberGV.getString("contactNumber"); + if(UtilValidate.isEmpty(country)) + { + contact=""; + } + phone.setPropertyValue(country+area+contact); phones.add(phone); } else { log.warn(phonePurpose + " without TelecomNumber"); } } } + private GenericValue getActivePartyContactMechPurpose(GenericValue contactParty, String purpose) throws GenericEntityException, SyncSourceException + { + List<GenericValue> activePCMPs = contactParty.getRelatedByAnd("PartyContactMechPurpose", UtilMisc.toMap("contactMechPurposeTypeId", purpose, "thruDate", null)); + if(activePCMPs.size() > 1) + { + log.warn("Could not find a unique active PCMP for " + purpose + " and " + contactParty); + throw new SyncSourceException("Could not find a unique active PCMP for purpose " + purpose + " and party id " + contactParty.get("partyId")); + } + else if(UtilValidate.isEmpty(activePCMPs)) + { + return null; + } + //it must be olny one element + return activePCMPs.get(0); + } /** * Insert (or updated if already exists) a TelecomNumber for given contactParty and contactMechPurposeType. * * TODO: pull phone out via a query string/jxpath * * @param phoneType - FBol type string - this method does NOTHING if <code>phone</code> does not match this type + * @throws SyncSourceException */ - private void updateOrInsertTelecomNumber(EntityPreparer contactParty, Contact contact, Phone phone, String phonePurpose, String phoneType) - throws GenericEntityException + private void updateOrInsertTelecomNumber(GenericValue contactParty, Contact contact, Phone phone, String phonePurpose, String phoneType) + throws GenericEntityException, SyncSourceException { if(phone.getPropertyType().equals(phoneType)) { - GenericValue phoneGV = contactParty.getRelated("PartyContactMechPurpose[thruDate=null,contactMechPurposeTypeId=" + phonePurpose + "]"); - if(phoneGV == null) //it is NEW - { - insertContactMech((String)contactParty.get("partyId"), contact, phonePurpose); - log.warn("created TelecomNumber as part of update"); - } - else //it is UPDATED - { - //Map updateParams = UtilMisc.toMap("contactNumber", phone.getPropertyValueAsString(), "contactMechId", busPhoneGV.get("contactMechId")); - //runSync("updateTelecomNumber", updateParams); //TODO: this service does not appear to work - _converters.toEntityAndStore(contact, phoneGV.getRelatedOne("TelecomNumber"), phonePurpose); - } + GenericValue phoneGV = getActivePartyContactMechPurpose(contactParty, phonePurpose); + if(phoneGV == null) //it is NEW + { + insertContactMech((String)contactParty.get("partyId"), contact, phonePurpose, "TELECOM_NUMBER"); + log.warn("created TelecomNumber as part of update"); + } + else //it is UPDATED + { + //Map updateParams = UtilMisc.toMap("contactNumber", phone.getPropertyValueAsString(), "contactMechId", busPhoneGV.get("contactMechId")); + //runSync("updateTelecomNumber", updateParams); //TODO: this service does not appear to work + _converters.toEntityAndStore(contact, phoneGV.getRelatedOne("TelecomNumber"), phonePurpose); + } + } } /** + * + * Insert (or updated if already exists) a TelecomNumber for given contactParty and contactMechPurposeType. + * + * @param contactParty + * @param contact + * @param purpose - A ContactMechPurposeType value + * @throws GenericEntityException + * @throws SyncSourceException + */ + private void updateOrInsertPostalAddress(GenericValue contactParty, Contact contact, String purpose) throws GenericEntityException, SyncSourceException + { + GenericValue postalAddrGV = getActivePartyContactMechPurpose(contactParty, purpose); + if(postalAddrGV == null) + { + insertContactMech((String)contactParty.get("partyId"), contact, purpose, "POSTAL_ADDRESS"); + } + else + { + _converters.toEntityAndStore(contact, postalAddrGV.getRelatedOne("PostalAddress"), purpose); + } + } + /** * Convenience method for calling findByPK * * @throws SyncSourceException to wrap any GenericEntityExceptions thrown by the delegator Modified: versions/1.0/trunk/hot-deploy/funambol/webapp/WEB-INF/opentaps-sync-config.xml =================================================================== --- versions/1.0/trunk/hot-deploy/funambol/webapp/WEB-INF/opentaps-sync-config.xml 2007-07-16 11:18:05 UTC (rev 3833) +++ versions/1.0/trunk/hot-deploy/funambol/webapp/WEB-INF/opentaps-sync-config.xml 2007-07-16 16:39:41 UTC (rev 3834) @@ -26,7 +26,7 @@ <bean class="mz.co.dbl.siga.framework.util.SystemPropertiesSetter"> <property name="mappings"> <map> - <entry key="funambol.ds.home" value="/home/cameron/workspace/opentaps/hot-deploy/funambol"/> + <entry key="funambol.ds.home" value="D:/Development/workspace/opentaps2/hot-deploy/funambol"/> </map> </property> </bean> @@ -63,6 +63,16 @@ </property> <property name="converters"> <map> + <entry key="PostalAddress:com.funambol.common.pim.contact.Contact:PRIMARY_LOCATION"> + <value> + address1 = businessDetail.address.street + address2 = businessDetail.address.postOfficeAddress + city = businessDetail.address.city + countryGeoId = businessDetail.address.country + postalCode = businessDetail.address.postalCode + stateProvinceGeoId = businessDetail.address.state + </value> + </entry> <entry key="TelecomNumber:com.funambol.common.pim.contact.Contact:PRIMARY_PHONE"> <value> <!-- TODO: need to have function call possibilities --> contactNumber=businessDetail.phones[propertyType=BusinessTelephoneNumber] |