Example #1
0
  @Override
  protected SpreadsheetRow insertRow(HashMap<SpreadsheetColumn, String> values) {
    try {
      // Ensure the worksheet exists in the spreadsheet
      if (!this.existsInSpreadsheet) {
        throw new RuntimeException(this.nonexistentWorksheetMessage());
      }

      // Ensure the worksheet knows its column IDs
      if (!this.hasGoogleIdsSet) {
        this.getGoogleColumnIds();
      }

      // Create a Google representation of the new row
      ListEntry row = new ListEntry();
      for (SpreadsheetColumn sc : values.keySet()) {
        GSColumn c = (GSColumn) sc;
        row.getCustomElements().setValueLocal(c.getGoogleColumnId(), values.get(sc));
      }

      // Insert the row
      row =
          ((GSModel) this.model)
              .spreadsheetService.insert(this.worksheetEntry.getListFeedUrl(), row);

      // Update the worksheet entry and list feed entry
      this.getSelf();

      return new GSRow(this, row);
    } catch (Exception e) {
      return null;
    }
  }
  public static void main(String[] args) {
    SpreadsheetService service = new SpreadsheetService("Sheet1");
    try {
      String sheetUrl =
          "https://spreadsheets.google.com/feeds/list/1BuEN5wHRrQJFgTj9bXN6lXEAcb1lcJ-QYg4Lya-3lCo/default/public/values";

      URL url = new URL(sheetUrl);

      // Get Feed of Spreadsheet url
      ListFeed lf = service.getFeed(url, ListFeed.class);

      // Iterate over feed to get cell value
      for (ListEntry le : lf.getEntries()) {
        CustomElementCollection cec = le.getCustomElements();
        // Pass column name to access it's cell values
        String val = cec.getValue("CustomerName");
        System.out.println(val);
        String val2 = cec.getValue("Customeremailid");
        System.out.println(val2);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ServiceException e) {
      e.printStackTrace();
    }
  }
Example #3
0
 public String next() {
   ListEntry listEntry = entries.next();
   StringBuffer line = new StringBuffer();
   for (String tag : listEntry.getCustomElements().getTags()) {
     line.append("\"").append(listEntry.getCustomElements().getValue(tag)).append("\"");
     line.append(",");
   }
   if (line.length() > 0) {
     line.deleteCharAt(line.length() - 1);
   }
   return line.toString();
 }
Example #4
0
  /**
   * Writes the content of a map into the Google spreadsheet associated with this object.
   *
   * @param data a set of key value pairs, whose keys have to correspond with the headers of the
   *     spreadsheet.
   * @throws ServiceException
   * @throws IOException
   */
  public void addRow(Map<String, Object> data) throws ServiceException, IOException {
    ListEntry rowToAdd = new ListEntry();
    CustomElementCollection keyValuePairs = rowToAdd.getCustomElements();

    // copy all values from the data map into the row
    for (String key : data.keySet()) {
      keyValuePairs.setValueLocal(key, data.get(key).toString());
    }

    // write the data into Google spreadsheet
    URL listFeedUrl = getWorksheet().getListFeedUrl();
    getService().insert(listFeedUrl, rowToAdd);
  }
Example #5
0
  /**
   * Searches a track title in a spreadsheet.
   *
   * @param trackName the track name to search
   * @param activity to get context
   * @param spreadsheetTitle the title of spreadsheet
   * @param isDelete whether delete the information of this track in the document
   * @param accountName the name of Google account
   * @return true means find the track name in the spreadsheet
   */
  private static boolean searchTrackTitleInSpreadsheet(
      String trackName,
      Activity activity,
      String spreadsheetTitle,
      boolean isDelete,
      String accountName) {
    try {
      // Get spreadsheet Id.
      String spreadsheetId =
          searchAllSpreadsheetByTitle(spreadsheetTitle, activity, accountName).get(0).getId();

      // Get spreadsheet service.
      SpreadsheetService spreadsheetService = new SpreadsheetService(spreadsheetTitle);
      Credential credential = new Credential(BearerToken.authorizationHeaderAccessMethod());
      credential.setAccessToken(
          SendToGoogleUtils.getToken(
              activity.getApplicationContext(), accountName, SendToGoogleUtils.SPREADSHEETS_SCOPE));
      spreadsheetService.setOAuth2Credentials(credential);

      // Get work sheet.
      WorksheetFeed feed =
          spreadsheetService.getFeed(
              new URL(
                  String.format(
                      Locale.US, SendSpreadsheetsAsyncTask.GET_WORKSHEETS_URI, spreadsheetId)),
              WorksheetFeed.class);
      List<WorksheetEntry> data = feed.getEntries();
      for (Iterator<WorksheetEntry> iterator = data.iterator(); iterator.hasNext(); ) {
        WorksheetEntry worksheetEntry = (WorksheetEntry) iterator.next();
        String title = worksheetEntry.getTitle().getPlainText();
        if (title.equals(WORK_SHEET_NAME)) {
          URL listFeedUrl = worksheetEntry.getListFeedUrl();
          List<ListEntry> listFeed =
              spreadsheetService.getFeed(listFeedUrl, ListFeed.class).getEntries();
          for (Iterator<ListEntry> iterator2 = listFeed.iterator(); iterator2.hasNext(); ) {
            ListEntry listEntry = (ListEntry) iterator2.next();
            String name = listEntry.getCustomElements().getValue(TRANCK_NAME_COLUMN);
            if (name.equals(trackName)) {
              if (isDelete) {
                listEntry.delete();
              }
              return true;
            }
          }
        }
      }
    } catch (Exception e) {
      Log.e(EndToEndTestUtils.LOG_TAG, "Search spreadsheet failed.");
    }
    return false;
  }
  public String getTagValue(ListFeed listFeed, String tagQuery) {

    String tagValue = "";

    for (ListEntry entry : listFeed.getEntries()) {
      for (String tag : entry.getCustomElements().getTags()) {
        if (tag.equalsIgnoreCase(tagQuery)) {
          tagValue = entry.getCustomElements().getValue(tag);
          break;
        }
      }
    }
    return tagValue;
  }
  public void insertRow(String nameValuePairs, URL listFeedUrl)
      throws IOException, ServiceException {
    ListEntry newEntry = new ListEntry();

    // Split first by the commas between the different fields.
    for (String nameValuePair : nameValuePairs.split(",")) {

      // Then, split by the equal sign.
      String[] parts = nameValuePair.split("=", 2);
      String tag = parts[0]; // such as "name"
      String value = parts[1]; // such as "Fred"

      newEntry.getCustomElements().setValueLocal(tag, value);
    }

    ListEntry createEntry = spreadsheetService.insert(listFeedUrl, newEntry);
    logger.info("Created Entry: " + createEntry.getPlainTextContent());
  }
  @BeforeMethod
  public void setUp() throws IOException, ServiceException {
    spreadsheetService = mock(SpreadsheetService.class);
    listFeed = mock(ListFeed.class);
    TextConstruct textConstruct =
        when(mock(TextConstruct.class).getPlainText()).thenReturn("name").getMock();
    when(listFeed.getTitle()).thenReturn(textConstruct);
    List<ListEntry> entries = new ArrayList<ListEntry>();
    ListEntry entry = mock(ListEntry.class);
    CustomElementCollection customElements = mock(CustomElementCollection.class);
    when(customElements.getTags())
        .thenReturn(new LinkedHashSet<String>(Arrays.asList("col1", "col2", "col3")));
    when(customElements.getValue("col1")).thenReturn("val1");
    when(customElements.getValue("col2")).thenReturn("val2");
    when(customElements.getValue("col3")).thenReturn("val3");
    when(entry.getCustomElements()).thenReturn(customElements);
    entries.add(entry);
    when(listFeed.getEntries()).thenReturn(entries);
    cellFeed = mock(CellFeed.class);
    List<CellEntry> cells = new ArrayList<CellEntry>();

    Cell cell1 = mock(Cell.class);
    when(cell1.getRow()).thenReturn(1);
    when(cell1.getValue()).thenReturn("col1");
    Cell cell2 = mock(Cell.class);
    when(cell2.getRow()).thenReturn(1);
    when(cell2.getValue()).thenReturn("col2");
    Cell cell3 = mock(Cell.class);
    when(cell3.getRow()).thenReturn(1);
    when(cell3.getValue()).thenReturn("col3");
    CellEntry entry1 = when(mock(CellEntry.class).getCell()).thenReturn(cell1).getMock();
    CellEntry entry2 = when(mock(CellEntry.class).getCell()).thenReturn(cell2).getMock();
    CellEntry entry3 = when(mock(CellEntry.class).getCell()).thenReturn(cell3).getMock();
    cells.add(entry1);
    cells.add(entry2);
    cells.add(entry3);
    when(cellFeed.getEntries()).thenReturn(cells);
    when(cellFeed.getTitle()).thenReturn(textConstruct);
    spreadsheetRepository = new GoogleSpreadsheetRepository(spreadsheetService, "key", "id");
  }
  private boolean uploadOneSubmission(
      String id, String instanceFilePath, String jrFormId, String token, String formFilePath) {
    // if the token is null fail immediately
    if (token == null) {
      mResults.put(id, oauth_fail + Collect.getInstance().getString(R.string.invalid_oauth));
      return false;
    }

    HashMap<String, String> answersToUpload = new HashMap<String, String>();
    HashMap<String, String> photosToUpload = new HashMap<String, String>();
    HashMap<String, PhotoEntry> uploadedPhotos = new HashMap<String, PhotoEntry>();

    HttpTransport h = AndroidHttp.newCompatibleTransport();
    GoogleCredential gc = new GoogleCredential();
    gc.setAccessToken(token);

    PicasaClient client = new PicasaClient(h.createRequestFactory(gc));

    // get instance file
    File instanceFile = new File(instanceFilePath);

    // first check to see how many columns we have:
    ArrayList<String> columnNames = new ArrayList<String>();
    try {
      getColumns(formFilePath, columnNames);
    } catch (FileNotFoundException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    } catch (XmlPullParserException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    } catch (IOException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    } catch (FormException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    }

    if (columnNames.size() > 255) {
      mResults.put(
          id, Collect.getInstance().getString(R.string.sheets_max_columns, columnNames.size()));
      return false;
    }

    // make sure column names are legal
    for (String n : columnNames) {
      if (!isValidGoogleSheetsString(n)) {
        mResults.put(
            id, Collect.getInstance().getString(R.string.google_sheets_invalid_column_form, n));
        return false;
      }
    }

    // parses the instance file and populates the answers and photos
    // hashmaps.
    try {
      processInstanceXML(instanceFile, answersToUpload, photosToUpload);
    } catch (XmlPullParserException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (FormException e) {
      mResults.put(id, form_fail + Collect.getInstance().getString(R.string.google_repeat_error));
      return false;
    } catch (FileNotFoundException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    try {
      Thread.sleep(GOOGLE_SLEEP_TIME);
    } catch (InterruptedException e3) {
      e3.printStackTrace();
    }

    // make sure column names in submission are legal (may be different than form)
    for (String n : answersToUpload.keySet()) {
      if (!isValidGoogleSheetsString(n)) {
        mResults.put(
            id, Collect.getInstance().getString(R.string.google_sheets_invalid_column_instance, n));
        return false;
      }
    }

    // if we have any photos to upload,
    // get the picasa album or create a new one
    // then upload the photos
    if (photosToUpload.size() > 0) {
      // First set up a picasa album to upload to:
      // maybe we should move this, because if we don't have any
      // photos we don't care...
      AlbumEntry albumToUse;
      try {
        albumToUse = getOrCreatePicasaAlbum(client, jrFormId);
      } catch (IOException e) {
        e.printStackTrace();
        GoogleAuthUtil.invalidateToken(Collect.getInstance(), token);
        mResults.put(id, picasa_fail + e.getMessage());
        return false;
      }

      try {
        uploadPhotosToPicasa(photosToUpload, uploadedPhotos, client, albumToUse, instanceFile);
      } catch (IOException e1) {
        e1.printStackTrace();
        mResults.put(id, picasa_fail + e1.getMessage());
        return false;
      }
    }

    // All photos have been sent to picasa (if there were any)
    // now upload data to Google Sheet

    String selection = InstanceColumns._ID + "=?";
    String[] selectionArgs = {id};

    Cursor cursor = null;
    String urlString = null;
    try {
      // see if the submission element was defined in the form
      cursor =
          Collect.getInstance()
              .getContentResolver()
              .query(InstanceColumns.CONTENT_URI, null, selection, selectionArgs, null);

      if (cursor.getCount() > 0) {
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
          int subIdx = cursor.getColumnIndex(InstanceColumns.SUBMISSION_URI);
          urlString = cursor.isNull(subIdx) ? null : cursor.getString(subIdx);

          // if we didn't find one in the content provider,
          // try to get from settings
          if (urlString == null) {
            SharedPreferences settings =
                PreferenceManager.getDefaultSharedPreferences(Collect.getInstance());
            urlString =
                settings.getString(
                    PreferencesActivity.KEY_GOOGLE_SHEETS_URL,
                    Collect.getInstance().getString(R.string.default_google_sheets_url));
          }
        }
      }
    } finally {
      if (cursor != null) {
        cursor.close();
      }
    }

    // now parse the url string if we have one
    final String googleHeader = "docs.google.com/spreadsheets/d/";
    String sheetId;
    if (urlString == null || urlString.length() < googleHeader.length()) {
      mResults.put(
          id, form_fail + Collect.getInstance().getString(R.string.invalid_sheet_id, urlString));
      return false;
    } else {
      int start = urlString.indexOf(googleHeader) + googleHeader.length();
      int end = urlString.indexOf("/", start);
      if (end == -1) {
        // if there wasn't a "/", just try to get the end
        end = urlString.length();
      }
      if (start == -1 || end == -1) {
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.invalid_sheet_id, urlString));
        return false;
      }
      sheetId = urlString.substring(start, end);
    }

    SpreadsheetService service = new SpreadsheetService("ODK-Collect");
    service.setAuthSubToken(token);

    // Define the URL to request.
    URL spreadsheetFeedURL = null;
    try {
      spreadsheetFeedURL =
          new URL("https://spreadsheets.google.com/feeds/worksheets/" + sheetId + "/private/full");
    } catch (MalformedURLException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    WorksheetQuery query = new WorksheetQuery(spreadsheetFeedURL);
    WorksheetFeed feed = null;
    try {
      feed = service.query(query, WorksheetFeed.class);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      if (e.getLocalizedMessage().equalsIgnoreCase("forbidden")) {
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.google_sheets_access_denied));
      } else {
        mResults.put(id, form_fail + Html.fromHtml(e.getResponseBody()));
      }
      return false;
    }

    List<WorksheetEntry> spreadsheets = feed.getEntries();
    // get the first worksheet
    WorksheetEntry we = spreadsheets.get(0);

    // check the headers....
    URL headerFeedUrl = null;
    try {
      headerFeedUrl =
          new URI(
                  we.getCellFeedUrl().toString()
                      + "?min-row=1&max-row=1&min-col=1&max-col="
                      + we.getColCount()
                      + "&return-empty=true")
              .toURL();
    } catch (MalformedURLException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    } catch (URISyntaxException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    }

    CellFeed headerFeed = null;
    try {
      headerFeed = service.getFeed(headerFeedUrl, CellFeed.class);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    boolean emptyheaders = true;

    // go through headers
    // if they're empty, resize and add
    for (CellEntry c : headerFeed.getEntries()) {
      if (c.getCell().getValue() != null) {
        emptyheaders = false;
        break;
      }
    }

    if (emptyheaders) {
      // if the headers were empty, resize the spreadsheet
      // and add the headers
      we.setColCount(columnNames.size());
      try {
        we.update();
      } catch (IOException e2) {
        e2.printStackTrace();
        mResults.put(id, form_fail + e2.getMessage());
        return false;
      } catch (ServiceException e2) {
        e2.printStackTrace();
        mResults.put(id, form_fail + e2.getMessage());
        return false;
      } catch (UnsupportedOperationException e) {
        e.printStackTrace();
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.google_sheets_update_error));
        return false;
      }

      // get the cell feed url
      URL cellFeedUrl = null;
      try {
        cellFeedUrl =
            new URI(
                    we.getCellFeedUrl().toString()
                        + "?min-row=1&max-row=1&min-col=1&max-col="
                        + columnNames.size()
                        + "&return-empty=true")
                .toURL();
      } catch (MalformedURLException e1) {
        e1.printStackTrace();
        mResults.put(id, form_fail + e1.getMessage());
        return false;
      } catch (URISyntaxException e1) {
        e1.printStackTrace();
        mResults.put(id, form_fail + e1.getMessage());
        return false;
      }

      // and the cell feed
      CellFeed cellFeed = null;
      try {
        cellFeed = service.getFeed(cellFeedUrl, CellFeed.class);
      } catch (IOException e) {
        e.printStackTrace();
        mResults.put(id, form_fail + e.getMessage());
        return false;
      } catch (ServiceException e) {
        e.printStackTrace();
        mResults.put(id, form_fail + e.getMessage());
        return false;
      }

      // write the headers
      for (int i = 0; i < cellFeed.getEntries().size(); i++) {
        CellEntry cell = cellFeed.getEntries().get(i);
        String column = columnNames.get(i);
        cell.changeInputValueLocal(column);
        try {
          cell.update();
        } catch (IOException e) {
          e.printStackTrace();
          mResults.put(id, form_fail + e.getMessage());
          return false;
        } catch (ServiceException e) {
          e.printStackTrace();
          mResults.put(id, form_fail + e.getMessage());
          return false;
        }
      }
    }

    // we may have updated the feed, so get a new one
    // update the feed
    try {
      headerFeedUrl =
          new URI(
                  we.getCellFeedUrl().toString()
                      + "?min-row=1&max-row=1&min-col=1&max-col="
                      + we.getColCount()
                      + "&return-empty=true")
              .toURL();
    } catch (MalformedURLException e3) {
      e3.printStackTrace();
      mResults.put(id, form_fail + e3.getMessage());
      return false;
    } catch (URISyntaxException e3) {
      e3.printStackTrace();
      mResults.put(id, form_fail + e3.getMessage());
      return false;
    }
    try {
      headerFeed = service.getFeed(headerFeedUrl, CellFeed.class);
    } catch (IOException e2) {
      e2.printStackTrace();
      mResults.put(id, form_fail + e2.getMessage());
      return false;
    } catch (ServiceException e2) {
      e2.printStackTrace();
      mResults.put(id, form_fail + e2.getMessage());
      return false;
    }

    // see if our columns match, now
    URL cellFeedUrl = null;
    try {
      cellFeedUrl =
          new URI(
                  we.getCellFeedUrl().toString()
                      + "?min-row=1&max-row=1&min-col=1&max-col="
                      + headerFeed.getEntries().size()
                      + "&return-empty=true")
              .toURL();
    } catch (MalformedURLException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    } catch (URISyntaxException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    }
    CellFeed cellFeed = null;
    try {
      cellFeed = service.getFeed(cellFeedUrl, CellFeed.class);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    // first, get all the columns in the spreadsheet
    ArrayList<String> sheetCols = new ArrayList<String>();
    for (int i = 0; i < cellFeed.getEntries().size(); i++) {
      CellEntry cell = cellFeed.getEntries().get(i);
      sheetCols.add(cell.getPlainTextContent());
    }

    ArrayList<String> missingColumns = new ArrayList<String>();
    for (String col : columnNames) {
      if (!sheetCols.contains(col)) {
        missingColumns.add(col);
      }
    }

    if (missingColumns.size() > 0) {
      // we had some missing columns, so error out
      String missingString = "";
      for (int i = 0; i < missingColumns.size(); i++) {
        missingString += missingColumns.get(i);
        if (i < missingColumns.size() - 1) {
          missingString += ", ";
        }
      }
      mResults.put(
          id,
          form_fail
              + Collect.getInstance()
                  .getString(R.string.google_sheets_missing_columns, missingString));
      return false;
    }

    // if we get here.. all has matched
    // so write the values
    ListEntry row = new ListEntry();

    // add photos to answer set
    Iterator<String> photoIterator = uploadedPhotos.keySet().iterator();
    while (photoIterator.hasNext()) {
      String key = photoIterator.next();
      String url = uploadedPhotos.get(key).getImageLink();
      answersToUpload.put(key, url);
    }

    Iterator<String> answerIterator = answersToUpload.keySet().iterator();
    while (answerIterator.hasNext()) {
      String path = answerIterator.next();
      String answer = answersToUpload.get(path);
      // Check to see if answer is a location, if so, get rid of accuracy
      // and altitude
      // try to match a fairly specific pattern to determine
      // if it's a location
      // [-]#.# [-]#.# #.# #.#
      Pattern p =
          Pattern.compile(
              "^-?[0-9]+\\.[0-9]+\\s-?[0-9]+\\.[0-9]+\\s-?[0-9]+\\" + ".[0-9]+\\s[0-9]+\\.[0-9]+$");
      Matcher m = p.matcher(answer);
      if (m.matches()) {
        // get rid of everything after the second space
        int firstSpace = answer.indexOf(" ");
        int secondSpace = answer.indexOf(" ", firstSpace + 1);
        answer = answer.substring(0, secondSpace);
        answer = answer.replace(' ', ',');
      }
      row.getCustomElements().setValueLocal(TextUtils.htmlEncode(path), answer);
    }

    // Send the new row to the API for insertion.
    try {
      URL listFeedUrl = we.getListFeedUrl();
      row = service.insert(listFeedUrl, row);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      if (e.getLocalizedMessage().equalsIgnoreCase("Forbidden")) {
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.google_sheets_access_denied));
      } else {
        mResults.put(id, form_fail + Html.fromHtml(e.getResponseBody()));
      }
      return false;
    }

    mResults.put(id, Collect.getInstance().getString(R.string.success));
    return true;
  }
Example #10
0
  /*
   * Loads the column ids from Google.
   */
  protected void getGoogleColumnIds() throws EolModelLoadingException {
    try {
      System.out.print("Loading Google ids for '" + this.getName() + "'... ");

      // Ensure the worksheet exists in the spreadsheet
      if (!this.existsInSpreadsheet) {
        throw new RuntimeException(this.nonexistentWorksheetMessage());
      }

      // Reset the flag early to avoid cycles
      this.hasGoogleIdsSet = true;

      // Position of the last header column
      int maxColIndex = this.getHeader().last().getIndex();

      // Determine the first blank row
      int rowIndex = this.getRows().size() + 2;

      // If the worksheet dimensions are smaller than the first blank row, then expand the worksheet
      if (this.worksheetEntry.getRowCount() < rowIndex) {
        this.worksheetEntry.setRowCount(rowIndex);
        this.worksheetEntry = this.worksheetEntry.update();
      }

      // Fill the first spare row with temporary values
      URL cellFeedUrl =
          new URI(
                  this.worksheetEntry.getCellFeedUrl().toString()
                      + "?min-row="
                      + Integer.toString(rowIndex)
                      + "&max-row="
                      + Integer.toString(rowIndex)
                      + "&min-col=1"
                      + "&max-col="
                      + Integer.toString(maxColIndex)
                      + "&return-empty=true")
              .toURL();
      CellFeed cellFeed =
          ((GSModel) this.model).spreadsheetService.getFeed(cellFeedUrl, CellFeed.class);
      for (CellEntry cell : cellFeed.getEntries()) {
        cell.changeInputValueLocal("TEMP");
        cell.update();
      }

      GSRow row = null;

      // Get the row from the worksheet to populate the Google Unique Id set
      URL listFeedUrl =
          new URL(
              this.worksheetEntry.getListFeedUrl().toString()
                  + "?start-index="
                  + Integer.toString(rowIndex - 1)
                  + "&max-results=1");
      ListFeed lf = ((GSModel) model).spreadsheetService.getFeed(listFeedUrl, ListFeed.class);
      List<ListEntry> list = lf.getEntries();
      if (list.size() == 1) {
        ListEntry le = list.get(0);
        int colIndex = 1;
        // Iterate over all returned tags - Google preserves column ordering in the set
        Iterator<String> it = le.getCustomElements().getTags().iterator();
        while (it.hasNext()) {
          GSColumn c = (GSColumn) this.getColumn(colIndex);

          // If the column is known, then associate the id with it
          if (c != null) {
            c.setGoogleColumnId(it.next());
          } else {
            it.next();
          }

          // Stop once the final known column is reached
          if (colIndex == maxColIndex) break;

          colIndex++;
        }
        row = new GSRow(this, le);
      } else {
        throw new Exception(
            "List feed failed to return the row of temporary values for learning the Unique Column Ids.");
      }

      // Delete the temporary row
      this.removeRow(row);
    } catch (Exception e) {
      throw new EolModelLoadingException(e, this.model);
    }
    System.out.println("<< done");
    System.out.println(this);
  }