All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.fuxing.airtable.AirtableApi Maven / Gradle / Ivy

The newest version!
package dev.fuxing.airtable;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import dev.fuxing.airtable.exceptions.AirtableApiException;
import dev.fuxing.airtable.exceptions.AirtableClientException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;

import javax.annotation.Nullable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by: Fuxing
 * Date: 2019-04-20
 * Time: 22:04
 */
public class AirtableApi {
    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final String apiKey;
    private final Executor executor;

    /**
     * @param apiKey Airtable ApiKey
     */
    public AirtableApi(String apiKey) {
        this(apiKey, AirtableExecutor.newInstance());
    }

    /**
     * @param apiKey   Airtable ApiKey
     * @param executor Executor to use for this api to use.
     */
    public AirtableApi(String apiKey, Executor executor) {
        this.apiKey = apiKey;
        this.executor = executor;
    }

    /**
     * Go to https://airtable.com/api to find the name of your app.
     *
     * @param app id of the app. (prefixed with 'app')
     * @return {@link AirtableApplication} interface
     * @see AirtableApi#app(String) 'base' is just a synonym for 'app'
     */
    public Application base(String app) {
        return app(app);
    }

    /**
     * Go to https://airtable.com/api to find the name of your app.
     *
     * @param app id of the app. (prefixed with 'app')
     * @return {@link AirtableApplication} interface
     */
    public Application app(String app) {
        return new Application(app);
    }

    /**
     * Implemented AirtableApplication interface.
     * 

* It's named Application because Airtable named it Application. * * @see AirtableApplication inteface for all available methods. */ public class Application implements AirtableApplication { private final String base; private Application(String base) { this.base = base; } /** * @param table name of the table in Airtable * @return Table api object */ public Table table(String table) { return new Table(base, table); } } /** * Implemented AirtableTable interface. * * @see AirtableTable interface for all available methods. */ public class Table implements AirtableTable { private final String base; private final String table; private Table(String base, String table) { this.base = base; this.table = table; } /** * @return name of the base the table is in. */ public String baseName() { return base; } /** * @return name of the table */ public String tableName() { return table; } @Override public PaginationList list(QuerySpec querySpec) { try { URI uri = new URIBuilder(querySpec.build()) .setScheme("https") .setHost("api.airtable.com") .setPathSegments("v0", base, table) .build(); Request request = Request.Get(uri) .addHeader("Authorization", "Bearer " + apiKey); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return new AirtableList(node); } catch (URISyntaxException | IOException e) { throw new AirtableClientException(e); } } @Override public AirtableRecord post(AirtableRecord record, boolean typecast) { try { Request request = Request.Post(createUri()) .addHeader("Authorization", "Bearer " + apiKey) .bodyString(toString(record, typecast), ContentType.APPLICATION_JSON); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return new AirtableRecord(node); } catch (IOException e) { throw new AirtableClientException(e); } } @Override public List patch(List records, boolean typecast) { AirtableClientException.assert10Records(records); try { Request request = Request.Patch(createUri()) .addHeader("Authorization", "Bearer " + apiKey) .bodyString(toString(records, typecast), ContentType.APPLICATION_JSON); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return AirtableList.parse(node.path("records")); } catch (IOException e) { throw new AirtableClientException(e); } } @Override public List put(List records, boolean typecast) { AirtableClientException.assert10Records(records); try { Request request = Request.Put(createUri()) .addHeader("Authorization", "Bearer " + apiKey) .bodyString(toString(records, typecast), ContentType.APPLICATION_JSON); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return AirtableList.parse(node.path("records")); } catch (IOException e) { throw new AirtableClientException(e); } } @Nullable @Override public AirtableRecord get(String recordId) { try { Request request = Request.Get(createUri(recordId)) .addHeader("Authorization", "Bearer " + apiKey); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return new AirtableRecord(node); } catch (IOException e) { throw new AirtableClientException(e); } catch (AirtableApiException e) { // For Get Request, status 404 is resolved into null if (e.getCode() == 404) return null; throw e; } } @Override public List post(List records, boolean typecast) { AirtableClientException.assert10Records(records); try { Request request = Request.Post(createUri()) .addHeader("Authorization", "Bearer " + apiKey) .bodyString(toString(records, typecast), ContentType.APPLICATION_JSON); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return AirtableList.parse(node.path("records")); } catch (IOException e) { throw new AirtableClientException(e); } } @Override public AirtableRecord patch(AirtableRecord record, boolean typecast) { try { Request request = Request.Patch(createUri(record.getId())) .addHeader("Authorization", "Bearer " + apiKey) .bodyString(toString(record, typecast), ContentType.APPLICATION_JSON); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return new AirtableRecord(node); } catch (IOException e) { throw new AirtableClientException(e); } } @Override public boolean delete(String recordId) { try { Request request = Request.Delete(createUri(recordId)) .addHeader("Authorization", "Bearer " + apiKey); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); return node.path("deleted").asBoolean(); } catch (IOException e) { throw new AirtableClientException(e); } } @Override public List delete(List recordIds) { AirtableClientException.assert10Records(recordIds); try { URIBuilder uriBuilder = new URIBuilder() .setScheme("https") .setHost("api.airtable.com") .setPathSegments("v0", base, table); recordIds.forEach(s -> uriBuilder.addParameter("records[]", s)); Request request = Request.Delete(uriBuilder.build()) .addHeader("Authorization", "Bearer " + apiKey); JsonNode node = executor.execute(request) .handleResponse(AirtableApi.this::handleResponse); List ids = new ArrayList<>(); for (JsonNode jsonNode : node.path("records")) { if (jsonNode.path("deleted").asBoolean()) { ids.add(jsonNode.path("id").asText()); } } return ids; } catch (IOException | URISyntaxException e) { throw new AirtableClientException(e); } } /** * @param record to convert to json string * @return json string */ private String toString(AirtableRecord record, boolean typecast) { ObjectNode node = OBJECT_MAPPER.createObjectNode(); if (typecast) { node.put("typecast", typecast); } ObjectNode fields = node.putObject("fields"); record.getFields().forEach(fields::set); try { return OBJECT_MAPPER.writeValueAsString(node); } catch (JsonProcessingException e) { throw new AirtableClientException(e); } } /** * @param records to convert into json string * @return json string */ private String toString(List records, boolean typecast) { ObjectNode node = OBJECT_MAPPER.createObjectNode(); if (typecast) { node.put("typecast", typecast); } ArrayNode arrayNode = node.putArray("records"); for (AirtableRecord record : records) { ObjectNode recordNode = OBJECT_MAPPER.createObjectNode(); if (record.getId() != null) { recordNode.put("id", record.getId()); } ObjectNode fields = recordNode.putObject("fields"); record.getFields().forEach(fields::set); arrayNode.add(recordNode); } try { return OBJECT_MAPPER.writeValueAsString(node); } catch (JsonProcessingException e) { throw new AirtableClientException(e); } } private URI createUri() { try { return new URIBuilder() .setScheme("https") .setHost("api.airtable.com") .setPathSegments("v0", base, table) .build(); } catch (URISyntaxException e) { throw new AirtableClientException(e); } } private URI createUri(String recordId) { try { return new URIBuilder("https://api.airtable.com") .setPathSegments("v0", base, table, recordId) .build(); } catch (URISyntaxException e) { throw new AirtableClientException(e); } } } /** * @param response to handle * @return JsonNode * @throws AirtableClientException client error, not caused by airtable api * @throws AirtableApiException server error, originated from https://api.airtable.com/v0 */ @Nullable private JsonNode handleResponse(HttpResponse response) throws AirtableClientException, AirtableApiException { try { JsonNode node = OBJECT_MAPPER.readTree(response.getEntity().getContent()); StatusLine status = response.getStatusLine(); if (status.getStatusCode() == 200) return node; JsonNode error = node.path("error"); if (error.isTextual()) { throw new AirtableApiException(status.getStatusCode(), error.asText(), error.asText()); } if (error.isObject()) { String type = error.path("type").asText(null); String message = error.path("message").asText(null); throw new AirtableApiException(status.getStatusCode(), type, message); } throw new AirtableApiException(status.getStatusCode(), null, null); } catch (IOException e) { throw new AirtableClientException(e); } } /** * QuerySpec implementation with URI builder */ public static final class QuerySpecImpl implements AirtableTable.QuerySpec { private URIBuilder builder; private Map sort = new HashMap<>(); QuerySpecImpl() { this.builder = new URIBuilder() .setScheme("https") .setHost("api.airtable.com") .setPathSegments("v0"); } @Override public AirtableTable.QuerySpec offset(String offset) { builder.setParameter("offset", offset); return this; } @Override public AirtableTable.QuerySpec fields(List fields) { for (int i = 0; i < fields.size(); i++) { builder.addParameter("fields[]", fields.get(i)); } return this; } @Override public AirtableTable.QuerySpec filterByFormula(String formula) { builder.setParameter("filterByFormula", formula); return this; } @Override public AirtableTable.QuerySpec maxRecords(int size) { builder.setParameter("maxRecords", String.valueOf(size)); return this; } @Override public AirtableTable.QuerySpec pageSize(int size) { builder.setParameter("pageSize", String.valueOf(size)); return this; } @Override public AirtableTable.QuerySpec sort(String field, @Nullable String direction) { sort.put(field, StringUtils.lowerCase(direction)); return this; } @Override public AirtableTable.QuerySpec view(String name) { builder.setParameter("view", name); return this; } @Override public AirtableTable.QuerySpec cellFormat(String format) { builder.setParameter("cellFormat", format); return this; } @Override public AirtableTable.QuerySpec timeZone(String zone) { builder.setParameter("timeZone", zone); return this; } @Override public AirtableTable.QuerySpec userLocale(String locale) { builder.setParameter("userLocale", locale); return this; } /** * @throws AirtableClientException uri syntax exception when building the uri */ @Override public URI build() { int sortI = 0; for (Map.Entry next : sort.entrySet()) { builder.setParameter("sort[" + sortI + "][field]", next.getKey()); if (next.getValue() != null) { builder.setParameter("sort[" + sortI + "][direction]", next.getValue()); } sortI++; } try { return builder.build(); } catch (URISyntaxException e) { throw new AirtableClientException(e); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy