/* * Copyright 2016 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.apiman.gateway.engine.jdbc; import io.apiman.gateway.engine.IRegistry; import io.apiman.gateway.engine.async.AsyncResultImpl; import io.apiman.gateway.engine.async.IAsyncResultHandler; import io.apiman.gateway.engine.beans.Api; import io.apiman.gateway.engine.beans.ApiContract; import io.apiman.gateway.engine.beans.Client; import io.apiman.gateway.engine.beans.Contract; import io.apiman.gateway.engine.beans.exceptions.InvalidContractException; import io.apiman.gateway.engine.beans.exceptions.PublishingException; import io.apiman.gateway.engine.beans.exceptions.RegistrationException; import io.apiman.gateway.engine.jdbc.i18n.Messages; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; import java.util.Set; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.ResultSetHandler; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * A JDBC implementation of the gateway registry. Only suitable for a * synchronous environment - should not be used when running an async * Gateway (e.g. vert.x). * * Must be configured with the JNDI location of the datasource to use. * Example: * * apiman-gateway.registry=io.apiman.gateway.engine.jdbc.JdbcRegistry * apiman-gateway.registry.datasource.jndi-location=java:jboss/datasources/apiman-gateway * * @author ewittman */ public class JdbcRegistry extends AbstractJdbcComponent implements IRegistry { protected static final ObjectMapper mapper = new ObjectMapper(); /** * Constructor. * @param config map of configuration options */ public JdbcRegistry(Map<String, String> config) { super(config); } /** * @see io.apiman.gateway.engine.IRegistry#publishApi(io.apiman.gateway.engine.beans.Api, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void publishApi(Api api, IAsyncResultHandler<Void> handler) { Connection conn = null; try { conn = ds.getConnection(); conn.setAutoCommit(false); QueryRunner run = new QueryRunner(); // First delete any record we might already have. run.update(conn, "DELETE FROM gw_apis WHERE org_id = ? AND id = ? AND version = ?", //$NON-NLS-1$ api.getOrganizationId(), api.getApiId(), api.getVersion()); // Now insert a row for the api. String bean = mapper.writeValueAsString(api); run.update(conn, "INSERT INTO gw_apis (org_id, id, version, bean) VALUES (?, ?, ?, ?)", //$NON-NLS-1$ api.getOrganizationId(), api.getApiId(), api.getVersion(), bean); DbUtils.commitAndClose(conn); handler.handle(AsyncResultImpl.create((Void) null, Void.class)); } catch (SQLException | JsonProcessingException e) { handler.handle(AsyncResultImpl.create(e)); } } /** * @see io.apiman.gateway.engine.IRegistry#registerClient(io.apiman.gateway.engine.beans.Client, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void registerClient(Client client, IAsyncResultHandler<Void> handler) { Connection conn = null; try { conn = ds.getConnection(); conn.setAutoCommit(false); QueryRunner run = new QueryRunner(); // Validate the client and populate the api map with apis found during validation. validateClient(client, conn); // Remove any old data first, then (re)insert run.update(conn, "DELETE FROM gw_clients WHERE org_id = ? AND id = ? AND version = ?", //$NON-NLS-1$ client.getOrganizationId(), client.getClientId(), client.getVersion()); String bean = mapper.writeValueAsString(client); run.update(conn, "INSERT INTO gw_clients (api_key, org_id, id, version, bean) VALUES (?, ?, ?, ?, ?)", //$NON-NLS-1$ client.getApiKey(), client.getOrganizationId(), client.getClientId(), client.getVersion(), bean); DbUtils.commitAndClose(conn); handler.handle(AsyncResultImpl.create((Void) null)); } catch (Exception re) { DbUtils.rollbackAndCloseQuietly(conn); handler.handle(AsyncResultImpl.create(re, Void.class)); } } /** * Removes all of the api contracts from the database. * @param client * @param connection * @throws SQLException */ protected void unregisterApiContracts(Client client, Connection connection) throws SQLException { QueryRunner run = new QueryRunner(); run.update(connection, "DELETE FROM contracts WHERE client_org_id = ? AND client_id = ? AND client_version = ?", //$NON-NLS-1$ client.getOrganizationId(), client.getClientId(), client.getVersion()); } /** * Ensures that the api referenced by the Contract actually exists (is published). * @param contract * @param connection * @throws RegistrationException */ private void validateContract(final Contract contract, Connection connection) throws RegistrationException { QueryRunner run = new QueryRunner(); try { Api api = run.query(connection, "SELECT bean FROM gw_apis WHERE org_id = ? AND id = ? AND version = ?", //$NON-NLS-1$ Handlers.API_HANDLER, contract.getApiOrgId(), contract.getApiId(), contract.getApiVersion()); if (api == null) { String apiId = contract.getApiId(); String orgId = contract.getApiOrgId(); throw new RegistrationException(Messages.i18n.format("JdbcRegistry.ApiNotFoundInOrg", apiId, orgId)); //$NON-NLS-1$ } } catch (SQLException e) { throw new RegistrationException(Messages.i18n.format("JdbcRegistry.ErrorValidatingApp"), e); //$NON-NLS-1$ } } /** * Validate that the client should be registered. * @param client * @param connection */ private void validateClient(Client client, Connection connection) throws RegistrationException { Set<Contract> contracts = client.getContracts(); if (contracts.isEmpty()) { throw new RegistrationException(Messages.i18n.format("JdbcRegistry.NoContracts")); //$NON-NLS-1$ } for (Contract contract : contracts) { validateContract(contract, connection); } } /** * @see io.apiman.gateway.engine.IRegistry#retireApi(io.apiman.gateway.engine.beans.Api, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void retireApi(Api api, IAsyncResultHandler<Void> handler) { QueryRunner run = new QueryRunner(ds); try { run.update("DELETE FROM gw_apis WHERE org_id = ? AND id = ? AND version = ?", //$NON-NLS-1$ api.getOrganizationId(), api.getApiId(), api.getVersion()); handler.handle(AsyncResultImpl.create((Void) null, Void.class)); } catch (SQLException e) { handler.handle(AsyncResultImpl.create(e)); } } /** * @see io.apiman.gateway.engine.IRegistry#unregisterClient(io.apiman.gateway.engine.beans.Client, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void unregisterClient(Client client, IAsyncResultHandler<Void> handler) { try { QueryRunner run = new QueryRunner(ds); run.update("DELETE FROM gw_clients WHERE org_id = ? AND id = ? AND version = ?", //$NON-NLS-1$ client.getOrganizationId(), client.getClientId(), client.getVersion()); handler.handle(AsyncResultImpl.create((Void) null)); } catch (SQLException e) { handler.handle(AsyncResultImpl.create(new PublishingException(Messages.i18n.format("JdbcRegistry.ErrorUnregisteringApp"), e), Void.class)); //$NON-NLS-1$ } } /** * @see io.apiman.gateway.engine.IRegistry#getApi(java.lang.String, java.lang.String, java.lang.String, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void getApi(String organizationId, String apiId, String apiVersion, IAsyncResultHandler<Api> handler) { try { Api api = getApiInternal(organizationId, apiId, apiVersion); handler.handle(AsyncResultImpl.create(api)); } catch (SQLException e) { handler.handle(AsyncResultImpl.create(e)); } } /** * Gets an api from the DB. * @param organizationId * @param apiId * @param apiVersion * @throws SQLException */ protected Api getApiInternal(String organizationId, String apiId, String apiVersion) throws SQLException { QueryRunner run = new QueryRunner(ds); return run.query("SELECT bean FROM gw_apis WHERE org_id = ? AND id = ? AND version = ?", //$NON-NLS-1$ Handlers.API_HANDLER, organizationId, apiId, apiVersion); } /** * @see io.apiman.gateway.engine.IRegistry#getClient(java.lang.String, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void getClient(String apiKey, IAsyncResultHandler<Client> handler) { try { Client client = getClientInternal(apiKey); handler.handle(AsyncResultImpl.create(client)); } catch (SQLException e) { handler.handle(AsyncResultImpl.create(e, Client.class)); } } /** * Simply pull the client from storage. * @param apiKey * @throws SQLException */ protected Client getClientInternal(String apiKey) throws SQLException { QueryRunner run = new QueryRunner(ds); return run.query("SELECT bean FROM gw_clients WHERE api_key = ?", //$NON-NLS-1$ Handlers.CLIENT_HANDLER, apiKey); } /** * @see io.apiman.gateway.engine.IRegistry#getContract(java.lang.String, java.lang.String, java.lang.String, java.lang.String, io.apiman.gateway.engine.async.IAsyncResultHandler) */ @Override public void getContract(String apiOrganizationId, String apiId, String apiVersion, String apiKey, IAsyncResultHandler<ApiContract> handler) { try { Client client = getClientInternal(apiKey); Api api = getApiInternal(apiOrganizationId, apiId, apiVersion); if (client == null) { Exception error = new InvalidContractException(Messages.i18n.format("JdbcRegistry.NoClientForAPIKey", apiKey)); //$NON-NLS-1$ handler.handle(AsyncResultImpl.create(error, ApiContract.class)); return; } if (api == null) { Exception error = new InvalidContractException(Messages.i18n.format("JdbcRegistry.ApiWasRetired", //$NON-NLS-1$ apiId, apiOrganizationId)); handler.handle(AsyncResultImpl.create(error, ApiContract.class)); return; } Contract matchedContract = null; for (Contract contract : client.getContracts()) { if (contract.matches(apiOrganizationId, apiId, apiVersion)) { matchedContract = contract; break; } } if (matchedContract == null) { Exception error = new InvalidContractException(Messages.i18n.format("JdbcRegistry.NoContractFound", //$NON-NLS-1$ client.getClientId(), api.getApiId())); handler.handle(AsyncResultImpl.create(error, ApiContract.class)); return; } ApiContract contract = new ApiContract(api, client, matchedContract.getPlan(), matchedContract.getPolicies()); handler.handle(AsyncResultImpl.create(contract)); } catch (Exception e) { handler.handle(AsyncResultImpl.create(e, ApiContract.class)); } } /** * Generates a valid document ID for a api referenced by a contract, used to * retrieve the api from ES. * @param contract */ protected String getApiId(Contract contract) { return getApiId(contract.getApiOrgId(), contract.getApiId(), contract.getApiVersion()); } /** * Generates a valid document ID for a api, used to index the api in ES. * @param orgId * @param apiId * @param version * @return a api key */ protected String getApiId(String orgId, String apiId, String version) { return orgId + "|" + apiId + "|" + version; //$NON-NLS-1$ //$NON-NLS-2$ } private static final class Handlers { public static final ResultSetHandler<Api> API_HANDLER = (ResultSet rs) -> { if (!rs.next()) { return null; } try (InputStream is = rs.getAsciiStream(1)) { return mapper.reader(Api.class).readValue(is); } catch (IOException e) { throw new RuntimeException(e); } }; public static final ResultSetHandler<Client> CLIENT_HANDLER = (ResultSet rs) -> { if (!rs.next()) { return null; } try (InputStream is = rs.getAsciiStream(1)) { return mapper.reader(Client.class).readValue(is); } catch (IOException e) { throw new RuntimeException(e); } }; } }