📜 ⬆️ ⬇️

Save contact information to Google Contacts

Good afternoon, colleagues!

As I promised in my previous article , I want to share with you information regarding the use of the Google Contacts API. Who is interested in learning how to call the Google Contacts API from Java to the Google Apps Engine - welcome under cat.

In my development, I used the Google API Client Livrary for Java 1.8 - in this version there are innovations that simplify authorization through OAuth. In fact, now the library takes over all the authorization work.

First you need to declare a class that will display the structure of the contact. I created a class whose subclasses correspond to the contact information elements — addresses, e-mails, telephones and instant messengers.
')
public class Contact implements Serializable { private static final String GDATA_URI = "http://schemas.google.com/g/2005#"; public static final String SOURCE_LABEL = "Source"; public class TypedSubElem implements Serializable{ protected String getURIPrefix(){ return Contact.GDATA_URI; } protected Map<String,String> getRelList(){ final Map<String,String> relList = Collections.unmodifiableMap(new HashMap<String,String>() {{put("home","."); put("other","."); put("work","."); }}); return relList; } @Key("@rel") protected String type = getURIPrefix()+"other"; @Key("@label") protected String label = null; public String getType(){ return (type == null)?null:type.substring(getURIPrefix().length()); } public String getLabel(){ return label; } public String getReadableType(){ String ret = getRelList().get(getType()); if(ret == null) ret = label; return ret; } public void setType(String type){ this.label = null; this.type = getURIPrefix()+(getRelList().containsKey(type)?type:"other"); } public void setLabel(String label){ this.type = null; this.label = label; } } public class FN implements Serializable { @Key("text()") public String text; } public class Content implements Serializable { @Key("text()") public String text; } public class Org extends TypedSubElem { @Key("gd:orgName") public String orgName; @Key("gd:orgTitle") public String orgTitle; @Key("gd:orgDepartment") public String orgDepartment; /** * @param type the type to set */ public void setWork(Boolean work) { setType(work?"work":"other"); } } public class Phone extends TypedSubElem { @Override protected Map<String,String> getRelList(){ final Map<String,String> relList = Collections.unmodifiableMap(new HashMap<String,String>() {{put("assistant",""); put("callback","."); put("car",""); put("company_main",". .")); put("fax",""); put("home","."); put("home_fax",". "); put("isdn","ISDN"); put("main","."); put("mobile","."); put("other","."); put("other_fax",". "); put("pager",""); put("radio",""); put("telex",""); put("tty_tdd","."); put("work","."); put("work_fax",". "); put("work_mob",". ."); put("work_pager",". "); }}); return relList; } @Key("text()") public String text; } public class Email extends TypedSubElem{ @Key("@address") public String address; } public class Address extends TypedSubElem{ @Key("gd:street") public String street; @Key("gd:city") public String city; @Key("gd:region") public String region; @Key("gd:postcode") public String postcode; @Key("gd:country") public String country; @Key("gd:formattedAddress") public String fullAddress; } public class Name extends SubElem{ @Key("gd:givenName") public String givenName; @Key("gd:additionalName") public String additionalName; @Key("gd:familyName") public String familyName; @Key("gd:namePrefix") public String namePrefix; @Key("gd:nameSuffix") public String nameSuffix; @Key("gd:fullName") private String fullName; } public class Link extends TypedSubElem { @Key("@href") public String url; @Override protected Map<String,String> getRelList(){ final Map<String,String> relList = Collections.unmodifiableMap(new HashMap<String,String>() {{put("home-page","."); put("blog",""); put("work","."); put("profile",""); put("other","."); }}); return relList; } @Override protected String getURIPrefix(){ return ""; } } public class IM extends Email { @Key("@protocol") public String protocol; @Override protected Map<String,String> getRelList(){ final Map<String,String> relList = Collections.unmodifiableMap(new HashMap<String,String>() {{put("home","."); put("other","."); put("netmeeting","NetMeeting"); put("work","."); }}); return relList; } protected Map<String,String> getProtoList(){ final Map<String,String> relList = Collections.unmodifiableMap(new HashMap<String,String>() {{put("AIM","AIM"); put("MSN","MSN"); put("YAHOO","Yahoo"); put("SKYPE","Skype"); put("QQ","QQ"); put("GOOGLE_TALK","GTalk"); put("ICQ","ICQ"); put("JABBER","Jabber"); }}); return relList; } public String getProtocol(){ return (protocol == null)?null:protocol.substring(protocol.lastIndexOf('#')+1); } public String getReadableProto(){ return getProtoList().get(getProtocol()); } public void setProtocol(String type){ this.protocol = (getProtoList().containsKey(type))?GDATA_URI+type:null; } } @Key("gContact:website") public List<Link> links = new ArrayList<Link>(); @Key public Content content; @Key("title") public FN fn; @Key("gd:phoneNumber") public List<Phone> phones = new ArrayList<Phone>(); @Key("gd:email") public List<Email> emails = new ArrayList<Email>(); @Key("gd:organization") public List<Org> orgs = new ArrayList<Org>(); @Key("gd:structuredPostalAddress") public List<Address> addresses = new ArrayList<Address>(); @Key("gd:im") public List<IM> IMs = new ArrayList<IM>(); @Key("gd:name") public Name name; } 


The class and its subclasses are declared as Serializable - this will enable the Google API Client to convert the contact to the correct Atom. For this, all the fields that are supposed to be saved are annotated @‌Key - this is how the correspondence between the fields of our class and the Atom structure is indicated.

The rel and label fields deserve a separate explanation. rel is a typical marking of contact information from a predefined list. For example, for e-mail it will be an indication of whether the address is home or work. The Google API passes a value of the form " schemas.google.com/g/2005# <view>". But besides typical markings, contact information can be marked with an arbitrary designation. For this is the label field. The Google API expects one of them to be filled in - either rel or label.

More details about the contact data structure can be found here .

To send a contact to Google, create a servlet that inherits from AbstractAppEngineAuthorizationCodeServlet. This class allows you to automatically check whether the user is authorized (through authorization in Google Accounts or, if you set the appropriate settings in the Google Apps Engine, using Federated Login). If the user is authorized, then he will be asked (again by means of the library servlet - you don’t need to write anything yourself) permission to access his contacts in Google Contacts. The resulting tokens will be saved for future use in the token store (I used the AppEngineCredentialStore). For the entire sequence of actions responsible GoogleAuthorizationCodeFlow.

 import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.extensions.appengine.auth.oauth2.AbstractAppEngineAuthorizationCodeServlet; import com.google.api.client.extensions.appengine.auth.oauth2.AppEngineCredentialStore; import com.google.api.client.extensions.appengine.http.urlfetch.UrlFetchTransport; import com.google.api.client.googleapis.GoogleHeaders; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.http.*; import com.google.api.client.http.xml.atom.AtomContent; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson.JacksonFactory; import com.google.api.client.xml.XmlNamespaceDictionary; import java.io.IOException; import java.util.Collections; import java.util.ResourceBundle; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Action extends AbstractAppEngineAuthorizationCodeServlet { private static final String DEFAULT_BASE_URL = "https://www.google.com/m8/feeds/contacts/default/full"; public static final String SCOPE = "https://www.google.com/m8/feeds/"; public static final String CALLBACK_URI = "/action/google_contacts/oauth2callback"; private static final String APP_NAME = "< >"; public static final String CLIENT_ID = "< >"; public static final String CLIENT_SECRET = "<>"; private static final HttpTransport transport = new UrlFetchTransport(); private static final JsonFactory jsonFactory = new JacksonFactory(); static final XmlNamespaceDictionary DICTIONARY = new XmlNamespaceDictionary() .set("", "http://www.w3.org/2005/Atom") .set("gd", "http://schemas.google.com/g/2005") .set("gContact", "http://schemas.google.com/contact/2008"); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ Credential credential = getCredential(); Contact contact = (Contact) request.getSession().getAttribute("Contact"); if(credential != null && contact != null) { HttpRequestFactory requestFactory = transport.createRequestFactory(getCredential()); assert requestFactory!=null; HttpRequest req = requestFactory.buildPostRequest(new GenericUrl(DEFAULT_BASE_URL), null); GoogleHeaders headers = new GoogleHeaders(); headers.setApplicationName(APP_NAME); headers.setGDataVersion("3"); req.setHeaders(headers); AtomContent content = AtomContent.forEntry(DICTIONARY, contact); req.setContent(content); try{ HttpResponse resp = req.execute(); } catch (HttpResponseException e) { } } } @Override protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { GenericUrl url = new GenericUrl(req.getRequestURL().toString()); url.setRawPath(CALLBACK_URI); return url.build(); } @Override protected AuthorizationCodeFlow initializeFlow() throws IOException { return new GoogleAuthorizationCodeFlow.Builder(new UrlFetchTransport(), new JacksonFactory(), CLIENT_ID, CLIENT_SECRET, Collections.singleton(SCOPE)).setCredentialStore( new AppEngineCredentialStore()).build(); } } 


In principle, the request headers can be omitted, but in this case, the Google API considers that a contact is sent to version 2, which does not use, in particular, the structured name of the contact, and FN is used instead. Also, if I'm not mistaken, in this version IM data is not saved.

More details on the other methods of the Contacts API can be found on the documentation page .

Source: https://habr.com/ru/post/147179/


All Articles