GData APIは、「ETag」を利用して排他制御ができます。ETagは、HTTPヘッダの一種でURLに対応するリソースのバージョンを表します。「リソース」とは、Spreadsheets APIであればスプレッドシートやワークシート、行やセルです。これらはそれぞれ一意となるURLを持っており、変更を行うごとに新しいETag(バージョン)が付与されています。
GData APIでは、ETagを利用して、リソースの更新時と削除時に排他制御ができます。ここでは、セルの更新を例にGData APIの排他制御の仕組みを紹介します。なおETagの詳細は、「Resource versioning (ETags)」を参照してください。
Spreadsheetsサービスはクライアントからセルの更新リクエストを受け取った際に、対象セルのETagと更新リクエストに含まれるETagの値を比較し、同じ場合にのみ更新リクエストを受け付けます。ETagの値が異なる場合は、エラーを返します。
実際に試してみましょう。サンプルのExclusionError.javaを実行してください。ExclusionError.javaはA3セルの値を3回更新するプログラムです。
package sample3;
import com.google.gdata.client.spreadsheet.CellQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.CellEntry;
import com.google.gdata.data.spreadsheet.CellFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
public class ExclusionError {
public static void main(String[] args) throws Exception {
// 操作対象のワークシートを取得する。
SpreadsheetService service = SpreadsheetUtil.getSpreadsheetService();
SpreadsheetEntry spreadsheetEntry = SpreadsheetUtil.findSpreadsheet(
service, "セル単位でのデータ操作");
WorksheetEntry worksheetEntry = spreadsheetEntry.getDefaultWorksheet();
// 更新対象のセルを取得
CellQuery cellQuery = new CellQuery(worksheetEntry.getCellFeedUrl());
cellQuery.setRange("A3");
cellQuery.setReturnEmpty(true); // 空セルも返すようにする
CellFeed cellFeed = service.query(cellQuery, CellFeed.class);
CellEntry cellEntry = cellFeed.getEntries().get(0);
// 1回目
System.out.println("1回目のETag:" + cellEntry.getEtag());
cellEntry.changeInputValueLocal("1回目");
cellEntry = cellEntry.update();
// 2回目
System.out.println("2回目のETag:" + cellEntry.getEtag());
cellEntry.changeInputValueLocal("2回目");
cellEntry.update();
// 3回目
System.out.println("3回目のETag:" + cellEntry.getEtag());
cellEntry.changeInputValueLocal("3回目");
cellEntry.update();
}
}
実行すると、2回目までは更新に成功しますが、3回目の更新に失敗しPreconditionFailedExceptionが発生します。
1回目のETag:"ImBtC1NVQSt7" 2回目のETag:"ImBtC1IFTCt7" 3回目のETag:"ImBtC1IFTCt7" Exception in thread "main" com.google.gdata.util.PreconditionFailedException: Precondition Failed Mismatch: etags = ["ImBtC1IFTCt7"], version = [abu6a] at com.google.gdata.client.http.HttpGDataRequest.handleErrorResponse(HttpGDataRequest.java:570) at com.google.gdata.client.http.GoogleGDataRequest.handleErrorResponse(GoogleGDataRequest.java:563) at com.google.gdata.client.http.HttpGDataRequest.checkResponse(HttpGDataRequest.java:536) at com.google.gdata.client.http.HttpGDataRequest.execute(HttpGDataRequest.java:515) at com.google.gdata.client.http.GoogleGDataRequest.execute(GoogleGDataRequest.java:535) at com.google.gdata.client.Service.update(Service.java:1502) at com.google.gdata.client.Service.update(Service.java:1468) at com.google.gdata.client.GoogleService.update(GoogleService.java:583) at com.google.gdata.data.BaseEntry.update(BaseEntry.java:617) at sample3.ExclusionError.main(ExclusionError.java:38)
コンソールに出力されるETagの値は、皆さんの環境と筆者の環境では異なると思いますが、2回目と3回目のETagの値は同じ値になっていると思います。
これは、2回目のCellEntryのupdateメソッドの戻り値を1回目のupdateメソッド実行時のように受け取っていないことが原因です。そのため、3回目のupdateメソッド実行時はサーバ側のセルのETagとクライアント(ExclusionError.java)の更新リクエストのETagの値が一致しないため、Spreadsheetsサービスはエラーを返し、その結果PreconditionFailedExceptionが発生しています。
このようにGData APIで更新処理を行う際は、ETagによる排他制御が自動的に行われます。ただし、強制的に上書きしたいケースもあると思います。そこで、次にETagによる排他制御を無効にする方法を紹介します。
ETagによる排他制御を無効にして強制的に上書きする方法を紹介します。サンプルのForceUpdate.javaを実行してください。
package sample3;
import com.google.gdata.client.spreadsheet.CellQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.CellEntry;
import com.google.gdata.data.spreadsheet.CellFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
public class ForceUpdate {
public static void main(String[] args) throws Exception {
// 操作対象のワークシートを取得する。
SpreadsheetService service = SpreadsheetUtil.getSpreadsheetService();
SpreadsheetEntry spreadsheetEntry = SpreadsheetUtil.findSpreadsheet(
service, "セル単位でのデータ操作");
WorksheetEntry worksheetEntry = spreadsheetEntry.getDefaultWorksheet();
// 更新対象のセルを取得
CellQuery cellQuery = new CellQuery(worksheetEntry.getCellFeedUrl());
cellQuery.setRange("B3");
cellQuery.setReturnEmpty(true); // 空セルも返すようにする
CellFeed cellFeed = service.query(cellQuery, CellFeed.class);
CellEntry cellEntry = cellFeed.getEntries().get(0);
// 1回目
System.out.println("1回目のETag:" + cellEntry.getEtag());
cellEntry.changeInputValueLocal("1回目");
cellEntry = cellEntry.update();
// 2回目
System.out.println("2回目のETag:" + cellEntry.getEtag());
cellEntry.changeInputValueLocal("2回目");
cellEntry.update();
// 3回目
service.getRequestFactory().setHeader("If-Match", "*");
cellEntry.setEtag(null);
System.out.println("3回目のETag:" + cellEntry.getEtag());
cellEntry.changeInputValueLocal("3回目");
cellEntry.update();
service.getRequestFactory().setHeader("If-Match", null);
}
}
今度は、PreconditionFailedExceptionが発生せずに3回目の更新処理が成功したと思います。
ExclusionError.javaとの違いは、3回目の更新処理の前に「If-Matchヘッダ」を設定している点です。「Resource versioning (ETags)」に記述されているように、If-Matchヘッダの値にリソースのETagではなく「*」を指定することで、強制的に上書きが可能になります。
If-Matchヘッダに「*」を指定する方法ですが、まずSpreadsheetServiceのgetRequestFactoryメソッドを利用してGDataRequestFactoryのインスタンスを取得し、そのsetHeaderメソッドを利用してIf-Matchヘッダとその値として「*」を設定します。setHeaderメソッドで設定したヘッダは、それ以降のリクエストを発行する際にHTTPヘッダに追加されます。
ただしgdata-java-clientは、ETagに関しては、EntryクラスのインスタンスにETagが設定されている場合に、その値でIf-Matchヘッダの値を上書きする実装となっています。そのため、次の1行でsetEtagメソッドを使ってCellEntryのETagの値をnullにしています。こうすることで、GDataRequestFactoryのsetHeaderメソッドで設定したIf-Matchヘッダの値が上書きされないようにしています。
最後に、設定したIf-Matchヘッダを削除するために、GDataRequestFactoryのsetHeaderメソッドを第2引数の値をnullにして実行しています。これは、nullが指定された場合にGDataRequestFactoryの実装クラスは第1引数で指定されたヘッダを削除する実装となっているためです。
リクエストのHTTPヘッダにIf-Matchヘッダを追加されていることを確認したい場合は、「Fiddler」などのパケット監視ツールを利用してください。
「On the Wire: Network Capture Tools for API Developers」にgdata-java- clientにおけるFiddlerの利用方法が記述されています。筆者も利用していますが、HTTPヘッダだけでなくリクエストやレスポンスの本体を見ると、Spreadshees APIへの理解が深まるのでおすすめです。
Copyright © ITmedia, Inc. All Rights Reserved.