Everything should be made as simple as possible, but not simpler. (Albert Einstein)

Saturday, October 22, 2011

Google Plus API Java Starter for NetBeans IDE


 This is my personal notes on porting following described package into NetBeans IDE. CMIIW is one of my gods. This is written in 2011, hence it might or might not work with latest update (if any). 

Google Plus Java Starter is a very basic example starter package to learn Google Plus API client for Java. Unfortunately it does not include package for NetBeans IDE (it prefers Eclipse as I guess it due to Oracle factor). 
Here is explanation on how to port starter code into NetBeans IDE 6.9 (Microsoft Windows OS environment).

Credits to google-plus-java-starter project people:
    willnorris.goog chrscha...@gmail.com  chirags@google.com mimm...@google.com wo...@google.com  ai...@google.com yan...@google.com
They are team member creating original starter package.




Google App Engine Java SDK



This example Java code uses GAE (Google App Engine) as server. So I need to install GAE SDK first and GAE plugin for NetBeans.
  • Boris Folgmann explains how to install GAE and configure setting for GAE plugin inside NetBeans IDE in his blog.
  • I need to download the plugin rebuilt by Denis Lunev from here.
Picture shows GAE server inside NetBeans Services window:
















Picture shows GAE library inside NetBeans Library Manager:



Google API Client Library for Java

For sure I need the Java version of Google+ API Client library. I found it in here. Open the download tab, then I get latest version of the library.
 
I extract the package into my main Java folder. I will need some JARs from this distrib.


Google+ API
I also need this Google+ API lib.
I extract it to the same main Java folder.

OAuth and API Key

In order to authorize my access to Google data, I need some auth constants:
  • oauth_client_id
  • oauth_client_secret
  • google_api_key
I get them from here. Steps:
  • In the console website, I create a new project.
  • Click on "API Access" in the left column
  • Click the button labeled "Create an OAuth2 client ID"
  • Give my application a name and click "Next"
  • Select "Web Application" as the "Application type"
  • Under "Your Site or Hostname" select http:// as the protocol and enter "localhost" for the domain name.
  • Click "Create client ID" button.
Preparing a text file to write down my oauth_client_id (Client ID), oauth_client_secret (Client Secret), and google_api_key (Simple API Access | API key).

Code

Now upon installation of these, 
  • Google App Engine SDK,
  • GAE plugin for NetBeans,
  • Google+ API Client for Java installation,
I am ready to write the Google Plus starter's Java code in NetBeans IDE


Add google-api-java-client library to Library Manager: 

For easier locating and accessing all JARs from the Google API Java client library, I create a destination folder in my main java installation folder to store all JARs, then register it into NetBeans Library Manager.
(Note: This part is optional for this project, due to I only need some JARs in the deployment WAR file for a compact size.)

  • In NetBeans, run Tools | Libraries menu to open Library Manager.
  • Click New Library button.
  • Select Library Type = "Class Library"
  • Fill in Library Name = "Google-API-Java-Client-1.5.0" (version number depends on latest version.)
  • Click OK.
  • Now, click "Add JAR/Folder" button. Then locate Google API Client Library installation folder. Select all JARs and click Add. (Exclude the "dependencies" folder).
  • Repeat previous steps. Now add all JARs inside the "dependencies" folder.
  • Repeat the same previous steps again for Google+ API. 
Project:
Now I create my Google+ starter project. I must be careful when selecting project name, because the name must match my GAE account's Application Identifier. E.g. if I register my application ID = nbgaeplus, then I must choose the same name for this project.

  • In NetBeans, I create new project from File menu.
  • In wizard, select Categories = "Java Web", and Projects = "Web Application".
  • Click next, give project a name (in this case I choose "nbgaeplus"), and determine project location. Also select my installed Java EE version.
  • Click next, select Server = "Google App Engine".
  • Click finish to create the framework.
API Library: 
The project needs some JAR from the Google API Java client lib.
  • Right click on the Library node (in Projects window), then from context-menu select "Properties".
  • By clicking "Add JAR/Folder", I need to add following JAR files:

google-api-client-1.5.0-beta.jar
google-api-client-extensions-1.5.0-beta.jar
google-http-client-1.5.0-beta.jar
google-http-client-extensions-1.5.0-beta.jar
google-oauth-client-1.5.0-beta.jar
google-oauth-client-extensions-1.5.0-beta.jar
gson-1.6.jar
guava-r09.jar
google-api-services-plus-v1-1.2.4-beta.jar

also Commons Lang lib:
commons-lang3-3.0.1.jar

In the Project Properties window, it will appears like in this picture:

Overview of the starter code:

To see an overview of the starter codes, I download official version of the starter package. After extracting the file, I have 4 different kinds of project: android, app-engine, command-line and web-app.
Because I choose to use GAE in this project, then I should look at the "app-engine" folder.

In the "src" folder, there are 4 Java source files:
  • ClearSessionServlet.java
  • ConfigHelper.java
  • OAuth2Servlet.java
  • Util.java
With package name: com.google.api.sample. Will be useful to look into these source codes.

Adding config.properties
Config properties file is used to store auth constants in this case.
  • In the Projects window, right click "Web Pages", choose from context menu: "New | Other".
  • Choose Categories = "Other", File Types = "Properties File".
  • Click next, fill in File Name = "config", and Folder = "web".
  • Click Finish.
Now write the config.properties code:

 
# === OAuth Information === #  
 
# Register your application at: https://code.google.com/apis/console/  
oauth_client_id = YOUR_CLIENT_ID
oauth_client_secret = YOUR_CLIENT_SECRET 
google_api_key = YOUR_API_KEY
   
# Space separated list of OAuth scopes your application will request access to  
oauth_scopes = https://www.googleapis.com/auth/plus.me  
   
oauth_redirect_uri = http://localhost:8080/oauth2callback
  • Replace code text in red-italic style with my own authorization code, refering to the "OAuth and API key" section above. 
  • Also to replace the port number "8080" to my Google App Engine server port number. (GAE server port number is set when I register GAE server in the NetBeans IDE.)
ConfigHelper class:
  • Add new class. Right click "Source Packages", choose from context menu: "New | Java Class".
  • Set Class Name = "ConfigHelper" and Package = "org.plus8888.starter".
  • Click Finish.
  • Add following code into ConfigHelper.java: 
(original code by Will Norris, Jenny Murphy)

 
package org.plus8888.starter;  
  
/*  
 * Copyright (c) 2010 Google Inc.  
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except  
 * in compliance with the License. You may obtain a copy of the License at  
 *  
 * http://www.apache.org/licenses/LICENSE-2.0  
 *  
 * Unless required by applicable law or agreed to in writing, software distributed under the License  
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express  
 * or implied. See the License for the specific language governing permissions and limitations under  
 * the License.  
 */  
  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
import java.util.Properties;  
import java.util.logging.Logger;  
  
/**  
 * Assorted utility methods.  
 *  
 * @author Will Norris  
 * @author Jenny Murphy  
 */  
public class ConfigHelper {  
  private static final Logger log = Logger.getLogger(ConfigHelper.class.getName());  
  private static final String CONFIG_PROPERTIES = "config.properties";  
  public static final Properties config = getConfig();  
  
  // OAuth client ID  
  public static String CLIENT_ID = ConfigHelper.getProperty("oauth_client_id");  
  
  // OAuth client secret  
  public static String CLIENT_SECRET = ConfigHelper.getProperty("oauth_client_secret");  
  
  // OAuth client secret  
  public static String GOOGLE_API_KEY = ConfigHelper.getProperty("google_api_key");  
  
  // Space separated list of OAuth scopes  
  public static String SCOPES = ConfigHelper.getProperty("oauth_scopes");  
  
  // OAuth redirect URI  
  public static String REDIRECT_URI = ConfigHelper.getProperty("oauth_redirect_uri");  
  
  
  /**  
   * Load the configuration file for this application.  
   *  
   * @return application configuration properties  
   */  
  private static Properties getConfig() {  
    Properties config = new Properties();  
    try {  
      InputStream input = new FileInputStream(new File(CONFIG_PROPERTIES));  
      config.load(input);  
    } catch (IOException e) {  
      throw new RuntimeException("Unable to load config file: " + CONFIG_PROPERTIES);  
    }  
    return config;  
  }  
  
  /**  
   * A simple static helper method that fetches a configuration  
   *  
   * @param key The name of the property for which you would like the configured  
   *            value  
   * @return A String representation of the configured property value  
   * @throws RuntimeException if request property can not be found  
   */  
  public static String getProperty(String key) {  
    if (!config.containsKey(key) || config.getProperty(key).trim().isEmpty()) {  
      log.severe("Could not find property " + key);  
      throw new RuntimeException("Could not find property " + key);  
    }  
    return config.getProperty(key).trim();  
  }  
}  
Util class:
  • Add new class. Right click "Source Packages", choose from context menu: "New | Java Class".
  • Set Class Name = "Util" and Package = "org.plus8888.starter".
  • Click Finish.
  • Add following code into Util.java: 
(original code by Jenny Murphy)

 
package org.plus8888.starter;  
  
/*  
 * Copyright (c) 2010 Google Inc.  
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except  
 * in compliance with the License. You may obtain a copy of the License at  
 *  
 * http://www.apache.org/licenses/LICENSE-2.0  
 *  
 * Unless required by applicable law or agreed to in writing, software distributed under the License  
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express  
 * or implied. See the License for the specific language governing permissions and limitations under  
 * the License.  
 */  
  
import com.google.api.client.extensions.appengine.http.urlfetch.UrlFetchTransport;  
import com.google.api.client.googleapis.json.GoogleJsonError;  
import com.google.api.client.http.HttpResponseException;  
import com.google.api.client.http.HttpTransport;  
import com.google.api.client.json.Json;  
import com.google.api.client.json.JsonFactory;  
import com.google.api.client.json.gson.GsonFactory;  
import com.google.api.client.util.Strings;  
//import com.google.appengine.repackaged.com.google.common.base.Strings;  
//import com.google.appengine.repackaged.org.apache.http.client.HttpResponseException;  
  
import java.io.IOException;  
  
/**  
 * @author Jennifer Murphy  
 */  
public class Util {  
  public static final JsonFactory JSON_FACTORY = new GsonFactory();  
  public static final HttpTransport TRANSPORT = new UrlFetchTransport();  
  
  /**  
   * A simple HTML tag stripper to prepare HTML for rendering. This is a  
   * quick and dirty solution so please do not depend on it to prevent XSS  
   * attacks.  
   *  
   * @return The same string with all xml/html tags stripped.  
   */  
  public static String stripTags(String input) {  
    return input.replaceAll("\\<[^>]*>","");  
  }  
  
  /**  
   * Extract the request error details from a HttpResponseException  
   * @param e An HttpResponseException that contains error details  
   * @return The String representation of all errors that caused the  
   *     HttpResponseException  
   * @throws java.io.IOException when the response cannot be parsed or stringified  
   */  
  public static String extractError(HttpResponseException e) throws IOException {  
    if (!Json.CONTENT_TYPE.equals(e.getResponse().getContentType())) {  
      return e.getResponse().parseAsString();  
    }  
  
    GoogleJsonError errorResponse =  
            GoogleJsonError.parse(JSON_FACTORY, e.getResponse());  
    StringBuilder errorReportBuilder = new StringBuilder();  
  
    errorReportBuilder.append(errorResponse.code);  
    errorReportBuilder.append(" Error: ");  
    errorReportBuilder.append(errorResponse.message);  
  
    for (GoogleJsonError.ErrorInfo error : errorResponse.errors) {  
      errorReportBuilder.append(JSON_FACTORY.toString(error));  
      errorReportBuilder.append(Strings.LINE_SEPARATOR);  
    }  
    return errorReportBuilder.toString();  
  }  
}  
Servlet, ClearSessionServlet:
  • Add a servlet. Right click "Source Packages", choose from context menu: "New | Servlet".
  • Set Class Name = "ClearSessionServlet" and Package = "org.plus8888.starter".
  • Click next, set Servlet Name = "clearsession", and URL pattern(s) = "/clearsession".
  • Click Finish.
  • Add following code into ClearSessionServlet.java: 
(original code by Jenny Murphy)

 
package org.plus8888.starter;  
  
import java.io.IOException;  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import javax.servlet.http.HttpSession;  
  
/**  
 *  
 * @author Jenny Murphy  
 */  
@SuppressWarnings("serial")  
public class ClearSessionServlet extends HttpServlet {  
     
    /**   
     * Handles the HTTP <code>GET</code> method.  
     * @param request servlet request  
     * @param response servlet response  
     * @throws ServletException if a servlet-specific error occurs  
     * @throws IOException if an I/O error occurs  
     */  
    @Override  
    protected void doGet(HttpServletRequest request, HttpServletResponse response)  
    throws ServletException, IOException {  
        HttpSession session = request.getSession();  
        session.removeAttribute("accessToken");  
        session.removeAttribute("refreshToken");  
  
        // With the accessToken cleared out, return to the index  
        response.sendRedirect("/");  
    }   
  
    /**   
     * Returns a short description of the servlet.  
     * @return a String containing servlet description  
     */  
    @Override  
    public String getServletInfo() {  
        return "Clear Session";  
    }  
}  

Servlet, OAuth2Servlet:
  • Add a servlet. Right click "Source Packages", choose from context menu: "New | Servlet".
  • Set Class Name = "OAuth2Servlet" and Package = "org.plus8888.starter".
  • Click next, set Servlet Name = "oauth2callback", and URL pattern(s) = "/oauth2callback".
  • Click Finish.
  • Add following code into OAuth2Servlet.java: 
(original code by Jenny Murphy)

 
package com.google.api.sample;  
  
/*  
 * Copyright (c) 2010 Google Inc.  
 *   
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except  
 * in compliance with the License. You may obtain a copy of the License at  
 *   
 * http://www.apache.org/licenses/LICENSE-2.0  
 *   
 * Unless required by applicable law or agreed to in writing, software distributed under the License  
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express  
 * or implied. See the License for the specific language governing permissions and limitations under  
 * the License.  
 */  
  
import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;  
import com.google.api.client.auth.oauth2.draft10.AuthorizationRequestUrl;  
import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessTokenRequest;  
import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAuthorizationRequestUrl;  
import com.google.api.client.http.javanet.NetHttpTransport;  
import com.google.api.client.json.gson.GsonFactory;  
  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import javax.servlet.http.HttpSession;  
import java.io.IOException;  
import java.util.logging.Logger;  
  
/**  
 * A servlet which handles the OAuth 2.0 flow. Once authenticated, the OAuth token is stored in session.accessToken.  
 *  
 * @author Jenny Murphy  
 */  
@SuppressWarnings("serial")  
public class OAuth2Servlet extends HttpServlet {  
  private static final Logger log = Logger.getLogger(OAuth2Servlet.class.getName());  
  
  @Override  
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {  
    HttpSession session = req.getSession();  
  
    // Check for an error returned by OAuth  
    String error = req.getParameter("error");  
    if (error != null) {  
      resp.setContentType("text/plain");  
      resp.getWriter().println("There was a problem during authentication: " + error);  
      log.severe("There was a problem during authentication: " + error);  
      return;  
    }  
  
    // When we're redirected back from the OAuth 2.0 grant page, a code will be supplied in a GET parameter named 'code'  
    String code = req.getParameter("code");  
    if (code == null || code.isEmpty()) {  
      // Now that we have the OAuth 2.0 code, we must exchange it for a token to make API requests.  
  
      // Build the authorization URL  
      AuthorizationRequestUrl authorizeUrl = new GoogleAuthorizationRequestUrl(  
              ConfigHelper.CLIENT_ID,  
              ConfigHelper.REDIRECT_URI,  
              ConfigHelper.SCOPES  
      );  
      authorizeUrl.redirectUri = ConfigHelper.REDIRECT_URI;  
      authorizeUrl.scope = ConfigHelper.SCOPES;  
      String authorizationUrl = authorizeUrl.build();  
  
      log.info("Redirecting browser for OAuth 2.0 authorization to " + authorizationUrl);  
      resp.sendRedirect(authorizationUrl);  
      return;  
    } else {  
      log.info("Exchanging OAuth code for access token using server side call");  
  
      AccessTokenResponse accessTokenResponse = new GoogleAccessTokenRequest.GoogleAuthorizationCodeGrant(  
  
              new NetHttpTransport(),  
              new GsonFactory(),  
              ConfigHelper.CLIENT_ID,  
              ConfigHelper.CLIENT_SECRET,  
              code,  
              ConfigHelper.REDIRECT_URI  
      ).execute();  
  
      log.info("Storing authentication token into the session");  
      session.setAttribute("accessToken", accessTokenResponse.accessToken);  
      session.setAttribute("refreshToken", accessTokenResponse.refreshToken);  
  
      //The authentication is all done! Redirect back to the samples index so you can play with them.  
      resp.sendRedirect("/");  
    }  
  }  
}  
Deployment descriptor, web.xml
  • Open Web Pages | WEB-INF | web.xml.
  • Click on Servlet tab.
  • Add new servlet element by clicking "Add servlet element" button:
Servlet Name = "oauth2callback"
Servlet Class = (browse..) to the Source Packages | org | plus8888 | starter | OAuth2Servlet.java.
URL Pattern(s) = "/oauth2callback"
  • Add one more servlet element by clicking "Add servlet element" button:
Servlet Name = "clearsession"
Servlet Class = (browse..) to the Source Packages | org | plus8888 | starter | ClearSessionServlet.java.
URL Pattern(s) = "/clearsession"





Security Constraints
  • Still in the Web Pages | WEB-INF | web.xml.
  • Click on Security tab.
  • Click "Add Security Contraint" button:
Display Name = "oauth1"
Click Add button. Fill in, 
Resource Name = "oauth1"
URL Pattern(s) = "/config.properties"
Http Method(s) = "All HTTP Methods"
Tick on the "Enable Authentication Constraint"


Logging:

Add logging.properties file. (ref: Logging in NetBeans)
  • In the Projects window, right click "Web Pages | WEB-INF", choose from context menu: "New | Other".
  • Choose Categories = "Other", File Types = "Properties File".
  • Click next, fill in File Name = "logging", and Folder = "web\WEB-INF".
  • Click Finish.
Now write the logging.properties code:

 
# A default java.util.logging configuration.  
# (All App Engine logging is through java.util.logging by default).  
#  
# To use this configuration, copy it into your application's WEB-INF  
# folder and add the following to your appengine-web.xml:  
#   
# <system-properties>  
#   <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>  
# </system-properties>  
#  
  
# Set the default logging level for all loggers to WARNING  
.level = INFO  
I could also further configure this logging properties to suit my needs.


JDO for Google App Engine

Let me create JDO configuration file for GAE. (ref: Using JDO with App Engine)
  • In the Projects window, right click on Source Packages, then select New | Other. 
  • Select Categories = "Other", and File Types = "Folder".
  • Click next, fill in Folder Name = "META-INF", Parent Folder = "src\java".
  • Right click on Source Packages | META-INF, then select New | Other. 
  • Select Categories = XML, and File Types = XML Document.
  • Set File Name = "jdoconfig", Folder = "src\java".
  • Click next, select document types = "Well-formed Document".
  • Click Finish.
Write the code for jdoconfig.xml,

<?xml version="1.0" encoding="utf-8"?>  
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"  
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
   xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">  
  
   <persistence-manager-factory name="transactions-optional">  
       <property name="javax.jdo.PersistenceManagerFactoryClass"  
           value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>  
       <property name="javax.jdo.option.ConnectionURL" value="appengine"/>  
       <property name="javax.jdo.option.NontransactionalRead" value="true"/>  
       <property name="javax.jdo.option.NontransactionalWrite" value="true"/>  
       <property name="javax.jdo.option.RetainValues" value="true"/>  
       <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>  
   </persistence-manager-factory>  
</jdoconfig>  
Main JSP, index.jsp

This is main welcome file. Open created index.jsp file from the Web Pages node.
Delete existing code, then replace with following code:

    <!DOCTYPE html> <%-- Copyright (c) 2010 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ page import="com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource" %> <%@ page import="com.google.api.services.plus.Plus" %> <%@ page import="com.google.api.services.plus.model.*" %> <%@ page import="com.google.api.sample.ConfigHelper" %> <%@ page import="java.util.List" %> <%@ page import="org.apache.commons.lang3.StringEscapeUtils" %> <%@ page import="com.google.api.sample.Util" %> <%@ page import="com.google.api.client.http.HttpResponseException" %> <%@ page import="java.util.logging.Logger" %> <% final Logger log = Logger.getLogger("/index.jsp"); %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Google+ platform Java Starter App</title> <link rel="stylesheet" href="style.css" type="text/css"/> </head> <body> <header><h1>Google+ platform Java Starter App</h1></header> <div class="largerbox"> <% if (session.getAttribute("accessToken") == null) { %> <a class='login' href='oauth2callback'>Connect Me!</a> <% } else { %> <p>Your OAuth 2.0 token is in <code>session.accessToken</code>.</p> <p class="logout"><a href="clearsession">Logout</a> by deleting this token. </p> <% // Here's an example of an unauthenticated Plus object. In cases where you // do not need to use the /me/ path segment to discover the current user's // ID, you can skip the OAuth flow with this code. Plus unauthenticatedPlus = new Plus(com.google.api.sample.Util.TRANSPORT, com.google.api.sample.Util.JSON_FACTORY); // When we do not specify access tokens, we must specify our API key instead unauthenticatedPlus.setKey(com.google.api.sample.ConfigHelper.GOOGLE_API_KEY); // If, however, you need to use OAuth to identify the current user you must // create the Plus object differently. Most programs will need only one // of these since you can use an authenticated Plus object for any call. GoogleAccessProtectedResource requestInitializer = new GoogleAccessProtectedResource( session.getAttribute("accessToken").toString(), com.google.api.sample.Util.TRANSPORT, com.google.api.sample.Util.JSON_FACTORY, com.google.api.sample.ConfigHelper.CLIENT_ID, com.google.api.sample.ConfigHelper.CLIENT_SECRET, session.getAttribute("refreshToken").toString()); Plus plus = new Plus(com.google.api.sample.Util.TRANSPORT, requestInitializer, com.google.api.sample.Util.JSON_FACTORY); %> <section class="activity"> <h3>A Specific Public Activity</h3> <% // Fetch a specific known public activity String publicActivityId = "z12gtjhq3qn2xxl2o224exwiqruvtda0i"; Activity publicActivity = null; try { publicActivity = unauthenticatedPlus .activities.get(publicActivityId).execute(); } catch (HttpResponseException e) { System.err.println(Util.extractError(e)); return; } // Render it %> <dl> <dt>ID</dt> <dd> <a href="<%= publicActivity.getUrl() %>"><%= publicActivity.getId() %> </a> </dd> <dt>Contents</dt> <dd> <%= StringEscapeUtils.escapeHtml4( Util.stripTags(publicActivity.getPlusObject().getContent())) %> </dd> </dl> </section> </div> <div class="box"> <section class="me"> <% //fetch your profile object Person profile; try { profile = plus.people.get("me").execute(); } catch (HttpResponseException e) { log.severe(Util.extractError(e)); return; } //render it %> <h3>Your Profile</h3> <dl> <dt>Image</dt> <dd><a href="<%= profile.getUrl() %>"> <img src="<%= profile.getImage().getUrl() %>?sz=100"/></a> </dd> <dt>ID</dt> <dd><%= profile.getId() %> </dd> <dt>Name</dt> <dd> <a href="<%= profile.getUrl() %>"> <%= StringEscapeUtils.escapeHtml4(profile.getDisplayName()) %> </a> </dd> </dl> </section> </div> <div class="largerbox"> <section class="activity_list"> <h3>Your Public Activity</h3> <% //https://plus.google.com/stream/circles/p9a6271c88b18fff //Plus.Activities.List listActivities = plus.activities.list("116355576801964691507", "limited"); Plus.Activities.List listActivities = plus.activities.list("me", "public"); //Plus.Activities.List listActivities = plus.activities.list("110974772585842612281", "public"); listActivities.setMaxResults(20L); // Pro tip: Use partial responses to improve response time considerably listActivities.setFields("nextPageToken,items(id,url,object/content)"); ActivityFeed activityFeed = null; try { activityFeed = listActivities.execute(); } catch (HttpResponseException e) { log.severe(Util.extractError(e)); return; } String aid = activityFeed.getId(); List<Activity> activities = activityFeed.getItems(); String atitle = activityFeed.getTitle(); listActivities.setPageToken(activityFeed.getNextPageToken()); // Keep track of the page number in case we're listing activities // for a user with thousands of activities. We'll limit ourselves // to 5 pages int currentPageNumber = 0; %> <table> <thead> <tr> <th>ID</th> <th>Content</th> </tr> </thead> <tbody> <% while (activities != null && currentPageNumber < 25) { currentPageNumber++; //if (activityFeed.getItems() != null) { if (activities != null) { for (Activity activity : activities) { %> <tr> <td><a href="<%= activity.getUrl() %>"><%= activity.getId() %> </a></td> <td><%= StringEscapeUtils.escapeHtml4( Util.stripTags(activity.getPlusObject().getContent())) %> </td> </tr> <% } // We will know we are on the last page when the next page token is null. // If this is the case, break. if (activityFeed.getNextPageToken() == null) { break; } // Prepare the next page of results listActivities.setPageToken(activityFeed.getNextPageToken()); // Execute and process the next page request try { activityFeed = listActivities.execute(); } catch (HttpResponseException e) { log.severe(Util.extractError(e)); return; } } activities = activityFeed.getItems(); } } %> </tbody> </table> </section> </div> </body> </html>
App Engine config file, appengine-web.xml

This is specific GAE config file. Open Web Pages | WEB-INF | appengine-web.xml file in editor.
Replace existing XML code to the following,

<?xml version="1.0" encoding="UTF-8"?>  
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0"  
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'  
xsi:schemaLocation='http://kenai.com/projects/nbappengine/downloads/download/schema/appengine-web.xsd appengine-web.xsd'>  
    <application>nbgaeplus</application>  
    <version>1</version>  
    <!-- Enable concurrent requests -->  
    <threadsafe>true</threadsafe>  
    <!-- Configure java.util.logging -->  
    <system-properties>  
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>  
    </system-properties>  
    <!-- Enable HTTP sessions -->  
    <sessions-enabled>true</sessions-enabled>  
    <!--  
    It's possible to reduce request latency by configuring your application to  
    asynchronously write HTTP session data to the datastore:  
  
      <async-session-persistence enabled="true" />  
  
    With this feature enabled, there is a very small chance your application will see  
    stale session data. For details, see  
    http://code.google.com/appengine/docs/java/config/appconfig.html#Enabling_Sessions  
  -->  
</appengine-web-app>  

CSS Style
  • Add a default CSS style file.
  • In the Projects window, right click on the Web Pages node.
  • Select New | Other from context menu.
  • Select Categories = Other, and File Types = Cascading Style Sheet.
  • Click next, fill in File Name = "style", and Folder = "web".
  • Click Finish.
Delete existing code, then replace with this default CSS code:

/*  
 * Copyright 2011 Google Inc.  
 * Licensed under the Apache License, Version 2.0 (the "License");  
 * you may not use this file except in compliance with the License.  
 * You may obtain a copy of the License at  
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0  
 *  
 * Unless required by applicable law or agreed to in writing, software  
 * distributed under the License is distributed on an "AS IS" BASIS,  
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
 * See the License for the specific language governing permissions and  
 * limitations under the License.  
 */  
  
body {  
  font-family: Arial,sans-serif;  
  margin: auto;  
}  
  
.box {  
  border: .5em solid #E3E9FF;  
  -webkit-box-orient: vertical;  
  -webkit-box-align: center;  
  
  -moz-box-orient: vertical;  
  -moz-box-align: center;  
  
  display: block;  
  box-orient: vertical;  
  box-align: center;  
  
  width: 350px;  
  height: auto;  
  
  margin: auto;  
  padding: 10px;  
}  
  
.largerbox {  
  border: .5em solid #E3E9FF;  
  -webkit-box-orient: vertical;  
  -webkit-box-align: center;  
  
  -moz-box-orient: vertical;  
  -moz-box-align: center;  
  
  display: block;  
  box-orient: vertical;  
  box-align: center;  
  
  width: 800px;  
  height: auto;  
  
  margin: auto;  
  padding: 10px;  
}  
  
.largerbox dd {  
  width:300px;  
  word-wrap: break-word;  
}  
  
.largerbox td {  
  vertical-align: top;  
}  
  
.box p {  
  margin: 0 2px;  
}  
  
.me {  
  -webkit-box-flex: 1;  
  -moz-box-flex: 1;  
  box-flex: 1;  
  width: 100px;  
}  
  
  
header {  
  color:#000;  
  padding:2px 5px;  
  font-size:100%;  
  width: 400px;  
  margin: auto;  
  text-align: center  
}  
footer {  
  color:#001;  
  font-size:70%;  
  overflow: visible;  
  margin: auto;  
  padding-top: 5px;  
  text-align: center  
}  
  
header h1.logo {  
  margin:6px 0;  
  padding:0;  
  cursor:pointer;  
  font-size:24px;  
  line-height:20px;  
}  
  
.login {  
  font-size: 200%;  
  display: block;  
  margin: auto;  
  cursor: pointer;  
  text-align: center;  
  font-weight: bold;  
  color: #2779AA;  
  line-height: normal;  
}  
  
.logout {  
  font-weight: normal;  
  padding-top: -5px;  
  margin-top: 0;  
}  
  
.contact {  
  color: #36C;  
  text-decoration: underline;  
  font-size: 13px;  
  font-weight: bold;  
  white-space: nowrap;  
  padding: 10px;  
}  
  
.contact-img {  
  float: left;  
  height: 32px;  
  margin-top: 2px;  
  width: 32px;  
}  
.contact-link {  
  overflow: hidden;  
  padding-left: 10px;  
  position: relative;  
}  
  
h3 {  
  border-bottom: 1px solid;  
  width: 320px;  
}  
  
.box dt {  
  float: left;  
  clear: both;  
  width: 140px;  
}  
.box dd {  
  clear: both;  
  margin-left: 140px;  
  margin-top: -15px;  
  width: 200px;  
}  
I could also add a favicon.ico file to the Web Pages (web) folder for remote deployment.

Build, Deploy, Run: Local GAE server

Now I can do normal procedures to the nbgaeplus project: build, deploy, run to the local GAE server.
Please remember that I set oauth_redirect_uri to localhost in the previous steps for local deployment & run.
After running GAE server from NetBeans when deploying the .war file, last message in the "Google App Engine" Output window should be,

[java] INFO: The server is running at http://localhost:8080/

In which, GAE 8080 is port number, can be different, depend on the registered GAE server port number.

Project can be run from NetBeans IDE, or by typing directly in browser's address bar: http://localhost:8080/index.jsp
Click on the "Connect Me!" then I go to the authentication OAuth 2 page.




















Click on "Allow access" button. And I can see my Google+ public activities.

That's only the very basic of the public Activities listing. I could explore more Google+ API calls in this developer website.

Note also, in this example I use OAuth, while it is also possible to use only simple API key for authentication.
A good example project on the use of simple API key (PHP project) is a "Google+Blog WordPress Plugin" by Daniel Treadwell in his website.

In another post in this blog, I explain the simplest form of the client-side HTML+JQuery code to use API key to grab Google+ activities data.

Deploying to the remote GAE server

As describe above, I must have a GAE account. I create one to follow procedures described in this website.

I choose my GAE Application ID = "nbgaeplus" in the GAE account's application, which is the same as the NetBeans project name. This name equivalency is a must to avoid deployment error.
This way I have this URL,
http://nbgaeplus.appspot.com/

Before deploying to my remote GAE server, I add new redirect URI and JS origin, in my https://code.google.com/apis/console/ account.

In the console website, I click left menu "API Access",
Then click Edit Settings,
Add in the "Authorized Redirect URIs" section a new URI:
http://nbgaeplus.appspot.com/oauth2callback

Add in the JavaScript origin section:
http://nbgaeplus.appspot.com/

Now back to NetBeans IDE, I change oauth_redirect_uri in the config.properties file, to the new value,
oauth_redirect_uri = http://nbgaeplus.appspot.com/oauth2callback

Now after re-build the project, I am ready to deploy my project to the remote GAE server.
  • In NetBeans's Projects window, I right click on the "nbgaeplus" project node.
  • Then from context menu I choose "Deploy to Google App Engine". 
  • Wait for some seconds to let plugin do the deployment.
The processes can be monitored in the Output window | Google App Engine Deployment. Last messages I got upon successful deployment is,

20% Scanning files on local disk.
25% Initiating update.
28% Cloning 5 static files.
31% Cloning 45 application files.
40% Uploading 2 files.
52% Uploaded 1 files.
61% Uploaded 2 files.
68% Initializing precompilation...
90% Deploying new version.
95% Will check again in 1 seconds.
98% Will check again in 2 seconds.
99% Closing update: new version is ready to start serving.
99% Uploading index definitions.

Update completed successfully.
Success.
Cleaning up temporary files...



Now I can run my remote project in the browser, by invoking this URL http://nbgaeplus.appspot.com/

Additional notes:
This project can also be run in servers other than GAE.
For real end-application, I need to add/modify CSS and JQuery UI scripts to the project for more user-friendly GUI.

END-of-note/soesilo-wijono