insideIT.fr : le blog des architectes IT de SFEIR

Aller au contenu | Aller au menu | Aller à la recherche

mercredi 11 février 2009

Editing Google Spreadsheets rows with Google Data API and GWT

In a previous article (here), we saw how to display Google Spreadsheets content with the Google Data API Java client and CellFeed objects.

This time, the proof of concept application shown below uses ListEntry objects to fetch our worksheets content, using header names (see (1) and (2) in the following screenshot) and unique identifiers (see (3)) instead of x and y positioning system.

You will find the source code at the bottom.

The following piece of code shows how to fetch every row entries in a worksheet into a HashMap which maps each row identifier to its ListEntry object:


  1         Map<String, ListEntry> listEntries =
2 new HashMap<String, ListEntry>();
3 List<WorksheetRow> rows = new ArrayList<WorksheetRow>();
4 try
5 {
6 ListFeed feed = sService.getFeed(new URL(sheetFeedURL),
7 ListFeed.class);
8 for (ListEntry entry : feed.getEntries())
9 {
10 WorksheetRow clonedRow = new WorksheetRow();
11 clonedRow.setTitle(entry.getTitle().getPlainText());
12 clonedRow.setId(entry.getId().substring(
13 entry.getId().lastIndexOf('/') + 1));
14 listEntries.put(clonedRow.getId(), entry);
15 CustomElementCollection elementColl =
16 entry.getCustomElements();
17 WorksheetRow.CustomElement rowElement =
18 new WorksheetRow.CustomElement();
19 for (String value : elementColl.getTags())
20 {
21 rowElement.put(value,
22 elementColl.getValue(value));
23 }
24 clonedRow.setElement(rowElement);
25 rows.add(clonedRow);
26 }
27 getSession().setAttribute("listEntries", listEntries);
28 } catch (MalformedURLException e) ...

Each ListEntry object has a list of «Tag» (column names) and a HashMap which maps each tag name to its value in the current entry. Note that each column name is automatically made unique.

To update a row with a modified WorkSheetRow object, we get its associated ListEntry object and set every value of the WorkSheetRow object with the «setValueLocal» method.


  1     public void updateWorkSheetRow(WorksheetRow row) throws AuthException
2 {
3 Map<String, ListEntry> listEntries = (Map<String, ListEntry>) getSession().getAttribute("listEntries");
6 ListEntry entry = listEntries.get(row.getId());
7 for (String tag : row.getElement().keySet())
8 {
9 entry.getCustomElements().setValueLocal(tag, row.getElement().get(tag));
10 try
11 {
12 entry.update();
13 } catch (IOException e)...

Then we simply have to call the modified entry update method.

Going further

Row editing in Spreadsheets API is not limited to the ListEntry class: Indeed, there is also a ListQuery class which enables advanced selection for your Worksheets.

Listing in reverse order:

In order to list the rows in reverse order, you just have to set the «reverse» property of a ListQuery object to «true» and pass this object to the SpreadsheetService object


  1     ListQuery query = new ListQuery(listFeedUrl);
2 query.setReverse(true);
3 ListFeed feed = service.query(query, ListFeed.class);
4 for (ListEntry entry : feed.getEntries())
5 { ...

Full-text search:

For full-text search, use the «setFullTextQuery» method. Note that you can pass several strings.


  1     ListQuery query = new ListQuery(listFeedUrl);
2 query.setFullTextQuery("Florent Tue");
3 ListFeed feed = service.query(query, ListFeed.class);
4 for (ListEntry entry : feed.getEntries())
5 { ...

Structured query:

Structured queries can filter the list by a particular value to a column name. You can also set the ordering of the listing with a column name.


  1     ListQuery query = new ListQuery(listFeedUrl);
2 query.setSpreadsheetQuery("name = 'Florent'");
3 query.setOrderBy("column:name");
4 ListFeed feed = service.query(query, ListFeed.class);
5 for (ListEntry entry : feed.getEntries())
6 { ...

Useful Links:

Article related downloads:

lundi 9 février 2009

Leveraging Google Data APIs Java client library with GWT

While the Java client library for Google DATA API can’t be compiled by the GWT compiler, you can still use it in RPC Servlets with the advantage of hiding the protocol or substituting the default authentication backend with yours.

The screenshot below shows an example of a simple GWT application made up of a tree widget displaying my spreadsheets and their worksheets, and a grid widget on which appears the selected worksheet.

The source code of this demo is at the bottom.

Google Data APIs are based on Atom and RSS formats and the Atom Publishing Protocol. Since they are REST-styled, you may use them with any browser:

First we need to authenticate to the service we want to use (Google Spreadsheets in our case) with our Google account. The easiest way is to use the ClientLogin interface:

$ export TOKEN=`curl https://www.google.com/accounts/ClientLogin \
-d Email=cappelle.florent@gmail.com -d Passwd=$GDATA_PASSWORD \
-d accountType=HOSTED_OR_GOOGLE \
-d source=SFEIR-GWTSheets-1 \
-d service=wise | grep 'Auth=' | cut -d'=' -f2`

The service parameter is the Google service we want to use; in this case the «wise» value stands for the spreadsheets service. If the login is successful, the response will include a token which we will use in the next requests. Note that in some cases the service may require a CAPTCHA challenge to be done.

Then we request the list of the spreadsheets we include our token in the Authorization header:

$ curl -H "Authorization: GoogleLogin auth=$TOKEN" \
"http://spreadsheets.google.com/feeds/spreadsheets/private/full" > SpreadsheetList.xml

Querying and editing our spreadsheets this way (you would have to build PUT requests with XML) would be quite inefficient. Instead of parsing XML and building HTTP requests, you may use the Java client library which already does this for you with the SpreadsheetService class:


  1         SpreadsheetService sService;
  2         sService = new SpreadsheetService("SFEIR-GWTSheets-1");
  3         try
  4         {
  5             sService.setUserCredentials(email, password);
  6             logger.info(email + " authentication successful");
  7             getSession().setAttribute("sService", sService);
  8         } catch (AuthenticationException e)
  9         {
 10             logger.log(Level.WARNING, e.getMessage());
 11             thrownew AuthException(e.getMessage());
 12         }

The preceding piece of code is extracted from a GWT-RPC Service method:

  • The «setUserCredentials» method requests a token and stores it in the sService object, which we store in the session for the next requests.
  • If the login fails or a CAPTCHA challenge is needed, an AuthException is thrown back to the client.

Once the user is logged in, we build a feed URL with the FeedURLFactory class (see the following piece of code). Its «getSpreadsheetsFeedUrl» returns the URL for the spreadsheet list feed, which then is fetched by the SpreadsheetService object (sService).


  1         List<SpreadsheetEntry> spreadsheetEntries = null;
  2         FeedURLFactory factory = FeedURLFactory.getDefault();
  3         SpreadsheetFeed feed;
  4
  5         try
  6         {
  7             feed = service.getFeed(factory.getSpreadsheetsFeedUrl(),
  8                     SpreadsheetFeed.class);
  9             spreadsheetEntries = feed.getEntries();
 10             getSession().setAttribute("spreadsheetEntries",
 11                     spreadsheetEntries);
 12         } catch (IOException e) ...

The «getEntries» method returns a list of SpreadsheetEntry: Each of these entries contains standard Atom information (author, title, etc) and a worksheet list feed URL. However we don’t even need to build a feed URL since the «getWorksheets» method directly returns a list of WorksheetEntry.

The SpreadSheetModel is a list of Worksheet which is returned to the GWT client: the title and the dimensions of each worksheet are used by the tree and the grid widgets. The cell feed URL is also needed to fetch the content of each cell.


  1         SpreadSheetModel model = new SpreadSheetModel();
  2         try
  3         {
  4             List<WorksheetEntry> worksheetEntries =
  5                     entry.getWorksheets();
  6             for (WorksheetEntry sheet : worksheetEntries)
  7             {
  8                 model.add(new SpreadSheetModel.WorkSheet(
  9                         sheet.getTitle().getPlainText(),
 10                         sheet.getCellFeedUrl().toExternalForm(),
 11                         sheet.getRowCount(), sheet.getColCount()));
 12             }
 13         } catch (IOException e) ...

At the client side, the code that fills the tree widget is pretty simple:


 1             public void onSuccess(SpreadSheetModel model)
 2             {
 3                 TreeItem item = getItem(position);
 4                 for (SpreadSheetModel.WorkSheet sheet : model)
 5                 {
 6                     TreeItem newItem = item.addItem(sheet.getTitle());
 7                     newItem.setUserObject(sheet);
 8                 }
 9                 // remove the empty child (see populate method)
10                 item.getChild(0).remove();
11                 item.setState(true, false);
12             }

Once the user clicks on a worksheet title in the tree, the worksheet feed URL is given to a RPC method «fetchworksheetcells», which returns a list of cells: the Cell class of the Google API can’t be used as it is in the client side, so we have to copy its properties to a DTO class (WorksheetCell). «copyProperties» of the Apache Commons Beanutils library does this in a single line.


  1         List<WorksheetCell> cells = new ArrayList<WorksheetCell>();
  2         try
  3         {
  4             CellFeed feed = sService.getFeed(new URL(sheetFeedURL),
  5                     CellFeed.class);
  6             List<CellEntry> cellEntries = feed.getEntries();
  7             for (CellEntry entry : cellEntries)
  8             {
  9                 Cell cell = entry.getCell();
 10                 WorksheetCell clonedCell = new WorksheetCell();
 11                 BeanUtils.copyProperties(clonedCell, cell);
 12                 cells.add(clonedCell);
 13             }
 14         } catch (MalformedURLException e) ...

Note that worksheet feeds also have a list feed, which enables relational alike manipulation of worksheet data. Insertion, modification and deletion are very easy with the Java client, for example the modification of a cell entry needs two lines of code:


  1     cellEntry.changeInputValueLocal(newValue);
  2     cellEntry.update();

Useful Links:

Article related downloads: