GAE/JからOAuth2.0を使用してSpreadsheetを参照する


以下のサイトを参考にGAEからSpreadsheetを参照するプロジェクトを作ってみたのですがOAuth2.0に対応させるのにえらく手こずったのでメモを残しておきます。

使用するのは「GData API」で環境構築の手順は下記の記事と同じです。また、フレームワークは「Slim3+Scenic3」を利用しています。こちらは公式サイトを参考に。

```GData APIでGoogleスプレッドシートを参照するには```
http://www.atmarkit.co.jp/ait/articles/1001/15/news121.html
```Slim3```
https://sites.google.com/site/slim3documentja/
```Scenic3```
https://sites.google.com/site/aboutscenic3/home


作ったものは http://localhost:8888/spreadsheet にアクセスするとspreadsheetの内容を出力する簡単なWEBアプリです。非常に手抜き処理ばかりですし、たいしてテストしていないプログラムですががご参考までに。


```java:TestPage.java
package test.page;

import test.service.CredentialDataService;

import java.net.URL;
import java.util.Arrays;
import java.util.List;

import org.slim3.controller.Navigation;

import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.DriveScopes;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetEntry;

import scenic3.annotation.ActionPath;
import scenic3.annotation.Page;

@Page("/")
public class TestPage extends CommonPage {

    private static final String CLIENT_ID_DEBUG =
        "<ローカルテスト用クライアントID>";
    private static final String CLIENT_SECRET_DEBUG =
        "＜ローカルテスト用クライアントシークレット＞";
    private static final String REDIRECT_URI_DEBUG =
        "http://localhost:8888/oauth2callback";

    private static final String CLIENT_ID = "＜GAE環境用クライアントID＞";
    private static final String CLIENT_SECRET = "＜GAE環境用シークレット＞";
    private static final String REDIRECT_URI =
        "http://＜自分のプロジェクトのドメイン＞/oauth2callback";

    private static final List<String> SCOPES = Arrays.asList(
        DriveScopes.DRIVE_FILE,
        "https://spreadsheets.google.com/feeds");

    private static final HttpTransport TRANSPORT = new NetHttpTransport();
    private static final JsonFactory JSON_FACTORY = new JacksonFactory();

    private static final String SERVICE_NAME = "＜サービス名＞";

    CredentialDataService credentialService = new CredentialDataService();

    /**
     * デバッグ用サーバかどうか
     */
    private boolean isDebug() {
        String serverName = request.getServerName();
        if (serverName.indexOf("localhost") != -1) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 文字列をレスポンスに出力
     */
    private void writeResponse(String str) {
        try {
            response.setCharacterEncoding("utf-8");
            response.getWriter().println(str);
            response.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * OAuth認証用flowインスタンスを取得
     */
    private GoogleAuthorizationCodeFlow getFlow() {

        String clientId, clientSecret;
        if (isDebug()) {
            clientId = CLIENT_ID_DEBUG;
            clientSecret = CLIENT_SECRET_DEBUG;
        } else {
            clientId = CLIENT_ID;
            clientSecret = CLIENT_SECRET;
        }

        GoogleAuthorizationCodeFlow flow =
            new GoogleAuthorizationCodeFlow.Builder(
                TRANSPORT,
                JSON_FACTORY,
                clientId,
                clientSecret,
                SCOPES)
                .setAccessType("online")
                .setApprovalPrompt("auto")
                .build();

        return flow;
    }

    /**
     * OAuth認証のリダイレクト先を取得
     */
    private String getRedirectURL() {
        String redirectUri;
        if (isDebug()) {
            redirectUri = REDIRECT_URI_DEBUG;
        } else {
            redirectUri = REDIRECT_URI;
        }

        return redirectUri;
    }

    /**
     * OAuth認証／スタート
     */
    @ActionPath("loginOAuth")
    public Navigation loginOAuth() {

        GoogleAuthorizationCodeFlow flow = getFlow();
        String redirectUri = getRedirectURL();

        String url =
            flow.newAuthorizationUrl().setRedirectUri(redirectUri).build();

        return redirect(url);
    }

    /**
     * OAuth認証／コールバック
     */
    @ActionPath("oauth2callback")
    public Navigation oauth2callback() {
        String code = request.getParameter("code");

        GoogleAuthorizationCodeFlow flow = getFlow();
        String redirectUri = getRedirectURL();

        try {

            GoogleTokenResponse response =
                flow
                    .newTokenRequest(code)
                    .setRedirectUri(redirectUri)
                    .execute();
            GoogleCredential credential =
                new GoogleCredential().setFromTokenResponse(response);

            credentialService.save(credential);

        } catch (Exception e) {
            e.printStackTrace();
            writeResponse(e.getMessage());
            return null;
        }

        return redirect("/spreadsheet");
    }

    /**
     * スプレッドシートテスト
     * 
     * @return
     */
    @ActionPath("spreadsheet")
    public Navigation spreadsheet() {
        try {

            GoogleCredential credential = credentialService.load();
            if (credential == null) {
                return redirect("loginOAuth");
            }

            SpreadsheetService service = new SpreadsheetService(SERVICE_NAME);
            service.setOAuth2Credentials(credential);

            String key = "＜スプレッドシートのKey＞";
            URL entryUrl =
                new URL("https://spreadsheets.google.com/feeds/spreadsheets/"
                    + key);
            SpreadsheetEntry spreadsheetEntry =
                service.getEntry(entryUrl, SpreadsheetEntry.class);

            // 検索対象のワークシートを取得
            WorksheetEntry worksheetEntry =
                spreadsheetEntry.getDefaultWorksheet();

            // ワークシート内を検索
            URL listFeedUrl = worksheetEntry.getListFeedUrl();
            ListFeed listFeed = service.getFeed(listFeedUrl, ListFeed.class);

            // ワークシートの内容を表示
            String ret = "";
            for (ListEntry row : listFeed.getEntries()) {
                for (String tag : row.getCustomElements().getTags()) {
                    ret += row.getCustomElements().getValue(tag) + "\t";
                }
                ret += "\n";
            }
            writeResponse(ret);

        } catch (Exception e) {
            // ここ超テキトー
            e.printStackTrace();
            credentialService.delete();
            writeResponse(e.getMessage());
        }

        return null;
    }
}

```

```java:CredentialData.java
package test.model;

import java.io.Serializable;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;

@Model(schemaVersion = 1)
public class CredentialData implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;

    private String accessToken;

    private User user;
    
    /** ------- Setter/Getter -------------------------------------- **/

    public Key getKey() {
        return key;
    }

    public void setKey(Key key) {
        this.key = key;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }
}

```

```java:CredentialDataService.java
package test.service;

import java.util.List;

import org.slim3.datastore.Datastore;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import test.meta.CredentialDataMeta;
import test.model.CredentialData;

public class CredentialDataService {

    private CredentialDataMeta t = new CredentialDataMeta();

    /**
     * OAuth証明情報を保存する
     * 
     * @param credential
     *            OAuth証明情報
     */
    public void save(GoogleCredential credential) {
        Transaction tx = Datastore.beginTransaction();

        if (load() != null) {
            delete();
        }

        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        CredentialData data = new CredentialData();
        data.setUser(user);
        data.setAccessToken(credential.getAccessToken());

        Datastore.put(data);
        tx.commit();
    }

    /**
     * ログインしているユーザのOAuth証明情報を削除する
     */
    public void delete() {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        List<CredentialData> list =
            Datastore.query(t).filter(t.user.equal(user)).asList();
        for (CredentialData d : list) {
            Datastore.delete(d.getKey());
        }
    }

    /**
     * ログインしているユーザのOAuth証明情報を取得する
     * 
     * @return OAuth証明情報
     */
    public GoogleCredential load() {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        List<CredentialData> list =
            Datastore.query(t).filter(t.user.equal(user)).asList();

        if (list.size() == 0) {
            return null;
        } else {
            String accessToken = list.get(0).getAccessToken();
            GoogleCredential credential =
                new GoogleCredential().setAccessToken(accessToken);
            return credential;
        }
    }
}

```