Acceder a hojas de cálculo de Google Docs mediante API desde una aplicación Java

Escrito por picodotdev el .
java planeta-codigo programacion
Comentarios

Java
Google

Google ofrece numerosos productos de desarrollo que permiten automatizar tareas e realizar integración con servicios, aplicaciones y documentos de Google creando un programa con un lenguaje de programacion. Las API que ofrece Google desde Drive, Sheets, Sides, GMail, Calendar, Contacts, Street View, AdSense, Analytics, Youtube, Speech y muchos más.

Para acceder a los servicios mediante APIs hay que obtener unas credenciales. Un ejemplo es el siguiente usando un API key para acceder a una hoja de cálculo compartida para cualquier usuario que tenga el enlace o identificativo del documento en Google Drive. Cada servicio de Google ofrece una API distinta según su contexto y datos que maneja.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.javagoogleapi;

...

public class Main {

    ...

    private static Credential getCredentialsApiKey() {
        return new GoogleCredential().createScoped(SCOPES);
    }

    ...

    public static void main(String... args) throws IOException, GeneralSecurityException {
        NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
        Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsApiKey()).setApplicationName(APPLICATION_NAME).build();

        ...
    }
}

Las hojas de cálculo se utilizan para contener información, son fácilmente editables por los usuarios y se convierten casi en una forma de base de datos. Con las APIs que ofrece Google para Spreadsheets esta información es utilizable en una aplicación, un buen caso de uso es aquel en el que ciertos datos o parámetros potencialmente cambian cada cierto tiempo o según reglas de negocio. Por ejemplo, se puede crear una hoja de cálculo con los precios, descripciones, existencias, disponibilidad o gastos de envío de los productos e importar esta información en la base de datos de una aplicación usando una API de Google, en vez de crear una aplicación backoffice de edición a medida para editar esa información, la aplicación consistiría en procesar el documento e insertar su información en la base de datos.

Como contrapartida de estas integraciones hay que tener en cuenta que una aplicación se hace dependiente del servicio los servicios de Google que utilice, hay que evaluar si esta dependencia es deseable.

Otro posible aplicación es utilizar documentos de texto en Google Drive como plantillas de correos electrónicos, se permite una edición sencilla y posteriormente se importan en la aplicación para que los utilice. A un documento de Google Drive se accede mediante esta petición HTTP GET. Las hojas de cálculo tamibén están disponibles mediante una interfaz REST sin embargo usando las APIs que ofrece Google para cada lenguaje es más cómodo que tratar con los datos en crudo en formato JSON.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ curl -X GET "https://sheets.googleapis.com/v4/spreadsheets/1JhBPGW4F.../values/Hoja1?key=AIzaSyDGwW..."
{
  "range": "Hoja1!A1:AA1000",
  "majorDimension": "ROWS",
  "values": [
    [
      "a1",
      "a3"
    ],
    [
      "a2",
      "a4"
    ]
  ]
}

1
2
3
4
$ curl -X GET "https://www.googleapis.com/drive/v3/files/1YCnD37w6p.../export?key=AIzaSyDGwW...&mimeType=text/plain"
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Para las hojas de cálculo hay dos formas de autorización para una aplicación. Mediante una API key con permisos de utilización de la API permite acceder a cualquier documento público, compartido de forma pública o para los usuarios que tengan el enlace o identificativo del documento. La otra más segura es creando una cuenta de servicio de forma que el documento se comparta únicamente con esa cuenta de servicio como si de cualquier otro usuario se tratase en vez de hacerlo público o para cualquiera que tenga el enlace.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.picodotdev.javagoogleapi;

...

public class Main {

    ...
    
    private static final String CREDENTIALS_FILE_PATH = "/blogbitix-119471bc8ebf.json";

    ...

    private static Credential getCredentialsServiceAccount(NetHttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {
        InputStream in = Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
        return GoogleCredential.fromStream(in, httpTransport, jsonFactory).createScoped(SCOPES);
    }

    public static void main(String... args) throws IOException, GeneralSecurityException {
        NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
        Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsServiceAccount(httpTransport, jsonFactory)).setApplicationName(APPLICATION_NAME).build();

        ...
    }
}

Laa API key se crean en la página de Credenciales para lo que previamente hay que crer un proyecto. Para leer el documento hay que compartirlo al menos para cualquiera que tenga acceso al enlace, al compartirlo se especifica si se hace en modo solo lectura o con permisos de ecritura.

Google Credentials, API Key y compartir documento

El enlace al compartir el documento o al editarlo contiene el identificativo de documento. Con la API key o cuenta de servicio, el identificativo del documento y el documento compartido al menos para cualquiera que tenga el enlace la información del documento está accesible para una aplicación mediante una API REST o de forma programática con una implementación de la API con Java, este programa Java imprime el contenido de las celdas de la hoja de cálculo en la terminal. Se necesita una expresión que identifique la hoja y el contenido de las celdas de las que se quieren datos con un rango en notación A1.

Hoja de cálculo y documento de Google
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.picodotdev.javagoogleapi;

...

public class Main {

    ...

    public static void main(String... args) throws IOException, GeneralSecurityException {
        ...

        Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsApiKey()).setApplicationName(APPLICATION_NAME).build();
        ValueRange response = service.spreadsheets().values().get(SPREADSHEET_ID, RANGE).setKey(API_KEY).execute();
        List<List<Object>> values = response.getValues();

        if (values == null || values.isEmpty()) {
            System.out.println("No data found.");
            return;
        }

        for (List<Object> row : values) {
            System.out.println(row.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")));
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ ./gradlew run
> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes

> Task :run
a1, a3
a2, a4

BUILD SUCCESSFUL in 1s
3 actionable tasks: 2 executed, 1 up-to-date

El método de API key obliga a hacer público el documento lo que no es deseable desde el punto de vista de seguridad aunque es un poco más simple que crear una cuenta de servicio. Para no hacer público el documento pero permitir acceder a una aplicación hay que crear una cuenta de servicio en la página Cuentas de servicio seleccionando o creando un proyecto.

Cuenta de servicio de Google

Al crear una cuenta de servicio y una clave se genera un archivo en formato JSON con las credenciales que hay que guardar y utilizar en una aplicación para acceder a los documentos compartidos con esta cuenta de servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "type": "service_account",
  "project_id": "blogbitix",
  "private_key_id": "119471bc8...",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFQpNVknb3bRp9\n...\n-----END PRIVATE KEY-----\n",
  "client_email": "blogbitix@blogbitix.iam.gserviceaccount.com",
  "client_id": "110222042...",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/blogbitix%40blogbitix.iam.gserviceaccount.com"
}

En vez de compartir el documento con cualquiera que tenga en enlace, con una cuenta de servicio el documento se puede compartir únicamente con esa cuenta de servicio, la cuenta de servicio posee un correo electrónico que la identifica, el documento se puede compartir únicamente con esta cuenta de servicio como si de cualquier otro usuario se tratase.

Documento compartido con cuenta de servicio

El siguiente código Java accede a un documento utilizando las credenciales de una cuenta de servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package io.github.picodotdev.javagoogleapi;

....

public class Main {

    private static final String APPLICATION_NAME = "JavaGoogleApi";
    private static final String API_KEY = "AIzaSyDBZ...";
    private static final String CREDENTIALS_FILE_PATH = "/blogbitix-119471bc8ebf.json";
    private static final String SPREADSHEET_ID = "1JhBPGW4F...";
    private static final String RANGE = "Hoja1";
    private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS_READONLY);

    private static Credential getCredentialsApiKey() {
        return new GoogleCredential().createScoped(SCOPES);
    }

    private static Credential getCredentialsServiceAccount(NetHttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {
        InputStream in = Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
        return GoogleCredential.fromStream(in, httpTransport, jsonFactory).createScoped(SCOPES);
    }

    public static void main(String... args) throws IOException, GeneralSecurityException {
        NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
        Sheets service = new Sheets.Builder(httpTransport, jsonFactory, getCredentialsServiceAccount(httpTransport, jsonFactory)).setApplicationName(APPLICATION_NAME).build();
        ValueRange response = service.spreadsheets().values().get(SPREADSHEET_ID, RANGE).setKey(API_KEY).execute();
        List<List<Object>> values = response.getValues();

        if (values == null || values.isEmpty()) {
            System.out.println("No data found.");
            return;
        }

        for (List<Object> row : values) {
            System.out.println(row.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")));
        }
    }
}

En el caso de Java hay que incluir la dependencia que proporciona la implementación de la API de Google Spreadsheets para Java como se muestra usando Gradle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'io.github.picodotdev.javagoogleapi.Main'
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.google.apis:google-api-services-sheets:v4-rev553-1.25.0'
}

Google ofrece un explorador para probar las peticiones y permisos de los documentos o explorar cualquier otra API de Google por ejemplo esta de Drive para exportar un documento en un formato determinado directamente desde una web sin tener que crear una aplicación, también se pueden hacer peticiones desde las páginas de documentación.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.