io.apiman.gateway.engine.jdbc.PollCachingJdbcRegistry.java Source code

Java tutorial

Introduction

Here is the source code for io.apiman.gateway.engine.jdbc.PollCachingJdbcRegistry.java

Source

/*
 * Copyright 2015 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.async.IAsyncResult;
import io.apiman.gateway.engine.async.IAsyncResultHandler;
import io.apiman.gateway.engine.beans.Api;
import io.apiman.gateway.engine.beans.Client;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;

/**
 * Extends the {@link JdbcRegistry} to provide multi-node caching.  This caching solution
 * will work in a cluster, although it is a rather naive implementation.  The approach
 * taken is that whenever the data in the DB is modified, a "last modified" record is 
 * inserted/updated.  The registry utilizes a thread to periodically poll the DB to
 * check if this data has been changed.  If the data *has* been changed, then the cache
 * is invalidated.
 *
 * @author eric.wittmann@redhat.com
 */
public class PollCachingJdbcRegistry extends CachingJdbcRegistry {

    private static final int DEFAULT_POLLING_INTERVAL = 10;
    private static final int DEFAULT_STARTUP_DELAY = 30;

    private int pollIntervalMillis;
    private int startupDelayMillis;

    private boolean polling = false;
    private long dataVersion = -1;

    /**
     * Constructor.
     */
    public PollCachingJdbcRegistry(Map<String, String> config) {
        super(config);

        String intervalVal = config.get("cache-polling-interval"); //$NON-NLS-1$
        String startupVal = config.get("cache-polling-startup-delay"); //$NON-NLS-1$

        if (intervalVal != null) {
            pollIntervalMillis = new Integer(intervalVal) * 1000;
        } else {
            pollIntervalMillis = DEFAULT_POLLING_INTERVAL * 1000;
        }

        if (startupVal != null) {
            startupDelayMillis = new Integer(startupVal) * 1000;
        } else {
            startupDelayMillis = DEFAULT_STARTUP_DELAY * 1000;
        }

        startCacheInvalidator();
    }

    /**
     * @see io.apiman.gateway.engine.CachingJdbcRegistry.CachingESRegistry#publishApi(io.apiman.gateway.engine.beans.Api, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void publishApi(Api api, final IAsyncResultHandler<Void> handler) {
        super.publishApi(api, new IAsyncResultHandler<Void>() {
            @Override
            public void handle(IAsyncResult<Void> result) {
                if (result.isSuccess()) {
                    updateDataVersion();
                }
                handler.handle(result);
            }
        });
    }

    /**
     * @see io.apiman.gateway.engine.CachingJdbcRegistry.CachingESRegistry#retireApi(io.apiman.gateway.engine.beans.Api, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void retireApi(Api api, final IAsyncResultHandler<Void> handler) {
        super.retireApi(api, new IAsyncResultHandler<Void>() {
            @Override
            public void handle(IAsyncResult<Void> result) {
                if (result.isSuccess()) {
                    updateDataVersion();
                }
                handler.handle(result);
            }
        });
    }

    /**
     * @see io.apiman.gateway.engine.CachingJdbcRegistry.CachingESRegistry#registerClient(io.apiman.gateway.engine.beans.Client, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void registerClient(Client client, final IAsyncResultHandler<Void> handler) {
        super.registerClient(client, new IAsyncResultHandler<Void>() {
            /**
             * @see io.apiman.gateway.engine.async.IAsyncHandler#handle(java.lang.Object)
             */
            @Override
            public void handle(IAsyncResult<Void> result) {
                if (result.isSuccess()) {
                    updateDataVersion();
                }
                handler.handle(result);
            }
        });
    }

    /**
     * @see io.apiman.gateway.engine.CachingJdbcRegistry.CachingESRegistry#unregisterClient(io.apiman.gateway.engine.beans.Client, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void unregisterClient(Client client, final IAsyncResultHandler<Void> handler) {
        super.unregisterClient(client, new IAsyncResultHandler<Void>() {
            @Override
            public void handle(IAsyncResult<Void> result) {
                if (result.isSuccess()) {
                    updateDataVersion();
                }
                handler.handle(result);
            }
        });
    }

    /**
     * Stores a "dataversion" record in the ES store.  There is only a single one of these.  The
     * return value of the add will include the version number of the entity.  This version
     * number is what we use to determine whether our cache is stale.
     */
    protected void updateDataVersion() {
        Connection conn = null;
        try {
            long newVersion = System.currentTimeMillis();

            conn = ds.getConnection();
            conn.setAutoCommit(false);
            QueryRunner run = new QueryRunner();

            run.update(conn, "DELETE FROM gw_dataversion"); //$NON-NLS-1$
            run.update(conn, "INSERT INTO gw_dataversion (version) VALUES (?)", //$NON-NLS-1$
                    newVersion);

            DbUtils.commitAndClose(conn);
            dataVersion = newVersion;
        } catch (SQLException e) {
            dataVersion = -1;
        }
    }

    /**
     * Starts up a thread that polls the ES store for updates.
     */
    protected void startCacheInvalidator() {
        polling = true;
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // Wait on startup before starting to poll.
                try {
                    Thread.sleep(startupDelayMillis);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }

                while (polling) {
                    try {
                        Thread.sleep(pollIntervalMillis);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    checkCacheVersion();
                }
            }
        }, "JdbcRegistryCacheInvalidator"); //$NON-NLS-1$
        thread.setDaemon(true);
        thread.start();
    }

    /**
     * Stop polling.
     */
    public void stop() {
        polling = false;
    }

    /**
     * Checks the ES store to see if the 'dataVersion' entry has been updated with a newer
     * version #.  If it has, then we need to invalidate our cache.
     */
    protected void checkCacheVersion() {
        // Be very aggressive in invalidating the cache.
        boolean invalidate = true;
        QueryRunner run = new QueryRunner(ds);
        try {
            long latestVersion = run.query("SELECT version FROM gw_dataversion", Handlers.LONG_HANDLER); //$NON-NLS-1$
            if (latestVersion > -1 && dataVersion > -1 && latestVersion == dataVersion) {
                invalidate = false;
            } else {
                dataVersion = latestVersion;
            }
        } catch (SQLException e) {
            // TODO need to use the gateway logger to log this!
            e.printStackTrace();
        }
        if (invalidate) {
            invalidateCache();
        }
    }

    private static final class Handlers {
        public static final ResultSetHandler<Long> LONG_HANDLER = (ResultSet rs) -> {
            if (!rs.next()) {
                return -1L;
            }
            return rs.getLong(1);
        };
    }

}