org.wso2.throttle.core.Throttler.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.throttle.core.Throttler.java

Source

/*
 * Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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 org.wso2.throttle.core;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.log4j.Logger;
import org.wso2.carbon.databridge.agent.AgentHolder;
import org.wso2.carbon.databridge.agent.DataPublisher;
import org.wso2.carbon.databridge.agent.exception.DataEndpointAgentConfigurationException;
import org.wso2.carbon.databridge.agent.exception.DataEndpointAuthenticationException;
import org.wso2.carbon.databridge.agent.exception.DataEndpointConfigurationException;
import org.wso2.carbon.databridge.agent.exception.DataEndpointException;
import org.wso2.carbon.databridge.commons.exception.TransportException;
import org.wso2.carbon.databridge.commons.utils.DataBridgeCommonsUtils;
import org.wso2.carbon.databridge.core.exception.DataBridgeException;
import org.wso2.carbon.databridge.core.exception.StreamDefinitionStoreException;
import org.wso2.siddhi.core.ExecutionPlanRuntime;
import org.wso2.siddhi.core.SiddhiManager;
import org.wso2.siddhi.core.event.Event;
import org.wso2.siddhi.core.stream.input.InputHandler;
import org.wso2.siddhi.core.stream.output.StreamCallback;
import org.wso2.throttle.api.Request;
import org.wso2.throttle.common.util.DatabridgeServerUtil;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Class which does throttling.
 * 1. Get an instance
 * 2. Start
 * 3. Add rules
 * 4. Invoke isThrottled
 */
public class Throttler {
    private static final Logger log = Logger.getLogger(Throttler.class);
    private static final String RDBMS_THROTTLE_TABLE_NAME = "ThrottleTable";
    private static final String RDBMS_THROTTLE_TABLE_COLUMN_KEY = "keyy";
    private static final String RDBMS_THROTTLE_TABLE_COLUMN_ISTHROTTLED = "isThrottled";

    static Throttler throttler;

    private SiddhiManager siddhiManager;
    private InputHandler eligibilityStreamInputHandler;
    private InputHandler requestStreamInputHandler;
    private List<InputHandler> requestStreamInputHandlerList = new ArrayList<InputHandler>();
    private InputHandler globalThrottleStreamInputHandler;
    private EventReceivingServer eventReceivingServer;
    private static Map<String, ResultContainer> resultMap = new ConcurrentHashMap<String, ResultContainer>();
    private int ruleCount = 0;

    private String hostName = "localhost"; //10.100.5.99
    private DataPublisher dataPublisher = null;
    ExecutorService executorService = null;

    private Throttler() {
    }

    public static synchronized Throttler getInstance() {
        if (throttler == null) {
            throttler = new Throttler();
        }
        return throttler;
    }

    /**
     * Starts throttler engine. Calling method should catch the exceptions and call stop to clean up.
     */
    public void start() throws DataBridgeException, IOException, StreamDefinitionStoreException {
        siddhiManager = new SiddhiManager();

        String commonExecutionPlan = "define stream EligibilityStream (rule string, messageID string, isEligible bool, key string);\n"
                + "define stream GlobalThrottleStream (key string, isThrottled bool);\n" + "\n"
                + "@IndexBy('key')\n" + "define table ThrottleTable (key string, isThrottled bool);\n" + "\n"
                + "FROM EligibilityStream[isEligible==false]\n" + "SELECT rule, messageID, false AS isThrottled\n"
                + "INSERT INTO ThrottleStream;\n" + "\n" + "FROM EligibilityStream[isEligible==true]\n"
                + "SELECT rule, messageID, isEligible, key\n" + "INSERT INTO EligibileStream;\n" + "\n"
                + "FROM EligibileStream JOIN ThrottleTable\n" + "\tON ThrottleTable.key == EligibileStream.key\n"
                + "SELECT rule, messageID, ThrottleTable.isThrottled AS isThrottled\n"
                + "INSERT INTO ThrottleStream;\n" + "\n"
                + "from EligibileStream[not ((EligibileStream.key == ThrottleTable.key) in ThrottleTable)]\n"
                + "select EligibileStream.rule AS rule, EligibileStream.messageID, false AS isThrottled\n"
                + "insert into ThrottleStream;\n" + "\n" + "from GlobalThrottleStream\n" + "select *\n"
                + "insert into ThrottleTable;";

        ExecutionPlanRuntime commonExecutionPlanRuntime = siddhiManager
                .createExecutionPlanRuntime(commonExecutionPlan);

        //add callback to get local throttling result and add it to ResultContainer
        commonExecutionPlanRuntime.addCallback("ThrottleStream", new StreamCallback() {
            @Override
            public void receive(Event[] events) {
                for (Event event : events) {
                    resultMap.get(event.getData(1).toString()).addResult((Boolean) event.getData(2));
                }
            }
        });

        //get and register inputHandler
        setEligibilityStreamInputHandler(commonExecutionPlanRuntime.getInputHandler("EligibilityStream"));
        setGlobalThrottleStreamInputHandler(commonExecutionPlanRuntime.getInputHandler("GlobalThrottleStream"));

        commonExecutionPlanRuntime.start();

        populateThrottleTable();

        //starts binary server to receive events from global CEP instance
        eventReceivingServer = new EventReceivingServer();
        eventReceivingServer.start(9611, 9711);

        //initialize binary data publisher to send requests to global CEP instance
        initDataPublisher();
    }

    /**
     * This method lets a user to add a predefined rule (pre-defined as a template), specifying desired parameters.
     * @param tier
     */
    public synchronized void addRule(String tier) {
        deployRuleToLocalCEP(tier);
        //deployRuleToGlobalCEP(tier);
    }

    //todo: this method has not being implemented completely. Will be done after doing perf tests.
    private void deployRuleToGlobalCEP(String templateID, String parameter1, String parameter2) {
        String queries = TemplateStore.getEnforcementQuery();

        ExecutionPlanRuntime ruleRuntime = siddhiManager.createExecutionPlanRuntime(
                "define stream RequestStream (messageID string, app_key string, api_key string, resource_key string, app_tier string, api_tier string, resource_tier string); "
                        + queries);

        GlobalCEPClient globalCEPClient = new GlobalCEPClient();
        globalCEPClient.deployExecutionPlan(queries);
    }

    private void deployRuleToLocalCEP(String tier) {
        String eligibilityQueries = TemplateStore.getEligibilityQueries(tier);

        ExecutionPlanRuntime ruleRuntime = siddhiManager.createExecutionPlanRuntime(
                "define stream RequestStream (messageID string, app_key string, api_key string, resource_key string, app_tier string, api_tier string, resource_tier string); "
                        + eligibilityQueries);

        //Add call backs. Here, we take output events and insert into EligibilityStream
        ruleRuntime.addCallback("EligibilityStream", new StreamCallback() {
            @Override
            public void receive(Event[] events) {
                try {
                    getEligibilityStreamInputHandler().send(events);
                } catch (InterruptedException e) {
                    log.error("Error occurred when publishing to EligibilityStream.", e);
                }
            }
        });

        //get and register input handler for RequestStream, so isThrottled() can use it.
        requestStreamInputHandlerList.add(ruleRuntime.getInputHandler("RequestStream"));

        //Need to know current rule count to provide synchronous API
        ruleCount++;
        ruleRuntime.start();
    }

    //todo
    public synchronized void removeRule() {
    }

    /**
     * Returns whether the given request is throttled.
     *
     * @param request User request to APIM which needs to be checked whether throttled
     * @return Throttle status for current status
     * @throws InterruptedException
     */
    public boolean isThrottled(Request request) throws InterruptedException {
        UUID uniqueKey = UUID.randomUUID();
        if (ruleCount != 0) {
            ResultContainer result = new ResultContainer(ruleCount);
            resultMap.put(uniqueKey.toString(), result);
            Object[] requestStreamInput = new Object[] { uniqueKey, request.getAppKey(), request.getApiKey(),
                    request.getResourceKey(), request.getAppTier(), request.getApiTier(),
                    request.getResourceTier() };
            Iterator<InputHandler> handlerList = requestStreamInputHandlerList.iterator();
            while (handlerList.hasNext()) {
                InputHandler inputHandler = handlerList.next();
                inputHandler.send(requestStreamInput);
            }
            //Blocked call to return synchronous result
            boolean isThrottled = result.isThrottled();
            if (!isThrottled) { //Only send served request to global throttler
                sendToGlobalThrottler(requestStreamInput);
            }
            resultMap.remove(uniqueKey);
            return isThrottled;
        } else {
            return false;
        }
    }

    public void stop() {
        if (siddhiManager != null) {
            siddhiManager.shutdown();
        }
        if (eventReceivingServer != null) {
            eventReceivingServer.stop();
        }
    }

    /**
     * Copies physical ThrottleTable to this instance's in-memory ThrottleTable.
     */
    private void populateThrottleTable() {
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        basicDataSource.setUrl("jdbc:mysql://localhost/org_wso2_throttle_DataSource");
        basicDataSource.setUsername("root");
        basicDataSource.setPassword("root");

        Connection connection = null;
        try {
            connection = basicDataSource.getConnection();
            DatabaseMetaData dbm = connection.getMetaData();
            // check if "ThrottleTable" table is there
            ResultSet tables = dbm.getTables(null, null, RDBMS_THROTTLE_TABLE_NAME, null);
            if (tables.next()) { // Table exists
                PreparedStatement stmt = connection.prepareStatement("SELECT * FROM " + RDBMS_THROTTLE_TABLE_NAME);
                ResultSet resultSet = stmt.executeQuery();
                while (resultSet.next()) {
                    String key = resultSet.getString(RDBMS_THROTTLE_TABLE_COLUMN_KEY);
                    Boolean isThrottled = resultSet.getBoolean(RDBMS_THROTTLE_TABLE_COLUMN_ISTHROTTLED);
                    try {
                        getGlobalThrottleStreamInputHandler().send(new Object[] { key, isThrottled });
                    } catch (InterruptedException e) {
                        log.error("Error occurred while sending an event.", e);
                    }
                }
            } else { // Table does not exist
                log.warn("RDBMS ThrottleTable does not exist. Make sure global throttler server is started.");
            }
        } catch (SQLException e) {
            log.error("Error occurred while copying throttle data from global throttler server.", e);
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    log.error("Error occurred while closing database connection.", e);
                }
            }
        }
    }

    private InputHandler getEligibilityStreamInputHandler() {
        return eligibilityStreamInputHandler;
    }

    private void setEligibilityStreamInputHandler(InputHandler eligibilityStreamInputHandler) {
        this.eligibilityStreamInputHandler = eligibilityStreamInputHandler;
    }

    private InputHandler getRequestStreamInputHandler() {
        return requestStreamInputHandler;
    }

    private void setRequestStreamInputHandler(InputHandler requestStreamInputHandler) {
        this.requestStreamInputHandler = requestStreamInputHandler;
    }

    public InputHandler getGlobalThrottleStreamInputHandler() {
        return globalThrottleStreamInputHandler;
    }

    private void setGlobalThrottleStreamInputHandler(InputHandler globalThrottleStreamInputHandler) {
        this.globalThrottleStreamInputHandler = globalThrottleStreamInputHandler;
    }

    private void sendToGlobalThrottler(Object[] data) {
        org.wso2.carbon.databridge.commons.Event event = new org.wso2.carbon.databridge.commons.Event();
        event.setStreamId(DataBridgeCommonsUtils.generateStreamId("org.wso2.throttle.request.stream", "1.0.3"));
        event.setMetaData(null);
        event.setCorrelationData(null);
        event.setPayloadData(data);

        dataPublisher.tryPublish(event);
    }

    private void initDataPublisher() {
        AgentHolder.setConfigPath(DatabridgeServerUtil.getDataAgentConfigPath());
        DatabridgeServerUtil.setTrustStoreParams();

        try {
            dataPublisher = new DataPublisher("Binary", "tcp://" + hostName + ":9621",
                    "ssl://" + hostName + ":9721", "admin", "admin");
        } catch (DataEndpointAgentConfigurationException e) {
            log.error(e.getMessage(), e);
        } catch (DataEndpointException e) {
            log.error(e.getMessage(), e);
        } catch (DataEndpointConfigurationException e) {
            log.error(e.getMessage(), e);
        } catch (DataEndpointAuthenticationException e) {
            log.error(e.getMessage(), e);
        } catch (TransportException e) {
            log.error(e.getMessage(), e);
        }

        executorService = Executors.newFixedThreadPool(10);

    }

}