Friday, March 16, 2012

Google Groups Settings : an "how-to" solution without Google's Java API

I'm used to work with Google online services and my opinion is :
  • Google protocol is good (of course!) and I think that the direction which is taken to migrate to REST services is the good one.
  • the documentation is never up-to-date and often contains wrong code samples (which sometimes doesn't compile because it contains deprecated instruction calls or string values).
  • the Java API provides by Google is horrible. The support of Maven is still bad (it's better since of few months). It's confused between the provisioning API (GData library) and the Google Java Client API. Sometimes, you have to use the first one, sometimes the second.
  • the authentication methods are also confused. You have to choice between ClientLogin, AuthSub, OAuth1 and OAuth2 (and OpenID?) : what's the fuck? On OAuth, you have other choice to take because the authentication workflow is different if you application is on the web or embedded. This situation could be clearer and Google should refine its authentication politic. They should consider only authentication for administration apps (without code exchange and OAuth token cooking) and authentication for end-user apps.
So, I had some difficulties to work this the recent API for groups settings and I decided to share some piece of code. The idea of my code it NOT TO USE the Google Java Client API. I prefer to dialog directly with Google and to use the XStream lib to do that.

Here is my code to retrieve a group settings. It is not a code for an end-user apps (where you should use OAuth2 authentication). I use it for admin tasks :



public GoogleGroupConfiguration getConfiguration(String groupName) {

try {
String groupEmail = MY_GROUP_EMAIL;
URL url = new URL("https://www.googleapis.com/groups/v1/groups/" + groupEmail + "?key=MY_APP_KEY_OBTAINED_FROM_GOOGLE_CONSOLE_API&alt=json");
GoogleGroupConfiguration groupConfiguration = new GoogleGroupConfiguration();
HttpTransport transport = new NetHttpTransport();

// authenticate with ClientLogin
ClientLogin authenticator = new ClientLogin();
authenticator.username = MY_USERNAME;
authenticator.password = MY_PASSWORD;
authenticator.accountType = "HOSTED_OR_GOOGLE";
authenticator.authTokenType = "apps";
authenticator.transport = transport;
Response authenticate = authenticator.authenticate();
String authorizationHeaderValue = authenticate.getAuthorizationHeaderValue();

// make query request
HttpRequestFactory requestFactory = transport.createRequestFactory();
HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(url.toString()));
request.getHeaders().setAuthorization(authorizationHeaderValue);
request.addParser(new JsonCParser(new JacksonFactory()));

// Make request and parse result
HttpResponse execute = request.execute();
String parseAsString = execute.parseAsString();
System.out.println(parseAsString);
JettisonMappedXmlDriver hierarchicalStreamDriver = new JettisonMappedXmlDriver();
XStream xstream = new XStream(hierarchicalStreamDriver);
parseAsString = "{\"" + GoogleGroupConfiguration.class.getName() + "\":" + parseAsString + "}";
xstream.fromXML(parseAsString, groupConfiguration);
return groupConfiguration;
} catch (Exception e) {
e.printStackTrace();
throw new GoogleAccessException(e);
}
}

public class GoogleGroupConfiguration {

public enum MessageDisplayFontEnum {
DEFAULT_FONT, FIXED_WIDTH_FONT
}

public enum MessageModerationLevelEnum {
MODERATE_ALL_MESSAGES, MODERATE_NEW_MEMBERS, MODERATE_NONE, MODERATE_NON_MEMBERS
}

public enum ReplyToEnum {
REPLY_TO_CUSTOM, REPLY_TO_IGNORE, REPLY_TO_LIST, REPLY_TO_MANAGERS, REPLY_TO_OWNER, REPLY_TO_SENDER
}

public enum WhoCanInviteEnum {
ALL_MANAGERS_CAN_INVITE, ALL_MEMBERS_CAN_INVITE
}

public enum WhoCanJoinEnum {
ALL_IN_DOMAIN_CAN_JOIN, ANYONE_CAN_JOIN, CAN_REQUEST_TO_JOIN, INVITED_CAN_JOIN
}

public enum WhoCanPostMessageEnum {
ALL_IN_DOMAIN_CAN_POST, ALL_MANAGERS_CAN_POST, ALL_MEMBERS_CAN_POST, ANYONE_CAN_POST, NONE_CAN_POST
}

public enum WhoCanViewGroupEnum {
ALL_IN_DOMAIN_CAN_VIEW, ALL_MANAGERS_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ANYONE_CAN_VIEW
}

public enum WhoCanViewMembershipEnum {
ALL_IN_DOMAIN_CAN_VIEW, ALL_MANAGERS_CAN_VIEW, ALL_MEMBERS_CAN_VIEW
}

private String kind;
private String email;
private String name;
private String description;
private WhoCanJoinEnum whoCanJoin;
private WhoCanViewMembershipEnum whoCanViewMembership;
private WhoCanViewGroupEnum whoCanViewGroup;
private WhoCanInviteEnum whoCanInvite;
private boolean allowExternalMembers;
private WhoCanPostMessageEnum whoCanPostMessage;
private boolean allowWebPosting;
private long maxMessageBytes;
private boolean isArchived;
private boolean archiveOnly;
private MessageModerationLevelEnum messageModerationLevel;
private ReplyToEnum replyTo;
private String customReplyTo;
private boolean sendMessageDenyNotification;
private String defaultMessageDenyNotificationText;
private boolean showInGroupDirectory;
private boolean allowGoogleCommunication;
private boolean membersCanPostAsTheGroup;
private MessageDisplayFontEnum messageDisplayFont;

public void setArchiveOnly(boolean isArchiveOnly) {
if (isArchiveOnly) {
this.whoCanPostMessage = WhoCanPostMessageEnum.NONE_CAN_POST;
}
if (this.archiveOnly && !isArchiveOnly) {
this.whoCanPostMessage = WhoCanPostMessageEnum.ALL_MANAGERS_CAN_POST;
}
this.archiveOnly = isArchiveOnly;
}

public void setWhoCanPostMessage(WhoCanPostMessageEnum newWhoCanPostMessage) {
if (newWhoCanPostMessage.equals(WhoCanPostMessageEnum.ALL_MANAGERS_CAN_POST)) {
this.messageModerationLevel = MessageModerationLevelEnum.MODERATE_NON_MEMBERS;
}
}

// ... add getters/setters

}



The idea is to get an auth token with the classic ClientLogin method. Then, I always put this token in each request header. Don't forget to add the API access key you can obtain from the Google console API. It allows you to access data you don't own (which is the case for administration batch processes). As you can see, I set alt=json into the request URL to get a json response. I created the GoogleGroupSettings class to map the Google response directly to an object through XStream.

If you already use AppsForYourDomain code, you can build requests without using the Clientlogin method. Create an AppsGroupService instance and work with GDataRequest.
I give you an example :



AppsGroupsService groupService = new AppsGroupsService(adminEmail, adminPassword, domain,"gdata-sample-AppsForYourDomain-AppsGroupService");
GDataRequest entryRequest = groupService.createEntryRequest(url);
entryRequest.execute();
InputStream inputStream = entryRequest.getResponseStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (int readBytes = inputStream.read(); readBytes >= 0; readBytes = inputStream.read())
outputStream.write(readBytes);
// Convert the contents of the output stream into a byte array
byte[] byteData = outputStream.toByteArray();
String parseAsString = new String(byteData);
parseAsString = "{\"" + GoogleGroupConfiguration.class.getName() + "\":" + parseAsString + "}";

// Parse result with XStream
JettisonMappedXmlDriver hierarchicalStreamDriver = new JettisonMappedXmlDriver();
XStream xstream = new XStream(hierarchicalStreamDriver);
xstream.fromXML(parseAsString, groupConfiguration);

// Close the streams
inputStream.close();
outputStream.close();


I hope this code will help you.