com.flipkart.aesop.runtime.producer.MysqlEventProducer.java Source code

Java tutorial

Introduction

Here is the source code for com.flipkart.aesop.runtime.producer.MysqlEventProducer.java

Source

/*
 * Copyright 2012-2015, the original author or authors.
 * 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 com.flipkart.aesop.runtime.producer;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.avro.generic.GenericRecord;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.trpr.platform.core.impl.logging.LogFactory;
import org.trpr.platform.core.spi.logging.Logger;

import com.flipkart.aesop.runtime.producer.avro.MysqlAvroEventManager;
import com.flipkart.aesop.runtime.producer.eventlistener.OpenReplicationListener;
import com.flipkart.aesop.runtime.producer.eventprocessor.BinLogEventProcessor;
import com.flipkart.aesop.runtime.producer.mapper.BinLogEventMapper;
import com.flipkart.aesop.runtime.producer.schema.eventprocessor.SchemaChangeEventProcessor;
import com.flipkart.aesop.runtime.producer.spi.SCNGenerator;
import com.flipkart.aesop.runtime.producer.txnprocessor.MysqlTransactionManager;
import com.flipkart.aesop.runtime.producer.txnprocessor.impl.MysqlTransactionManagerImpl;
import com.flipkart.aesop.runtime.producer.txnprocessor.impl.NaiveSCNGenerator;
import com.google.code.or.OpenReplicator;
import com.linkedin.databus.core.UnsupportedKeyException;
import com.linkedin.databus.core.util.InvalidConfigException;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.producers.EventCreationException;
import com.linkedin.databus2.relay.config.LogicalSourceStaticConfig;
import com.linkedin.databus2.relay.config.PhysicalSourceStaticConfig;

/**
 * <code>MysqlEventProducer</code> kick starts bin log event listener to listen to Mysql events using open replicator
 * library and in
 * turn creates change events of {@link GenericRecord}
 * @author Shoury B
 * @version 1.0, 07 Mar 2014
 */
public class MysqlEventProducer<T extends GenericRecord> extends AbstractEventProducer implements InitializingBean {
    /** Logger for this class */
    private static final Logger LOGGER = LogFactory.getLogger(MysqlEventProducer.class);
    /** Default Mysql port */
    private static final Integer DEFAULT_MYSQL_PORT = 3306;
    /** Pattern for extracting /3306/mysql-bin out of mysql://or_test%2For_test@localhost:3306/3306/mysql-bin */
    private static final Pattern PATH_PATTERN = Pattern.compile("/([0-9]+)/[a-z|A-Z|0-9|-]+");
    /** Index of server id in pattern match group */
    private static final int SERVER_ID = 1;
    /** Index of bin log prefix in pattern match group */
    private static final int BIN_LOG_PREFIX = 2;
    /** Event mapper which maps bin log events to one of schemas registered */
    protected Map<Integer, BinLogEventMapper<T>> binLogEventMappers;
    /** Open Replicator which listens and parses bin log events from Mysql */
    protected OpenReplicator openReplicator;
    /** Mysql transaction manager */
    protected MysqlTransactionManager mysqlTxnManager;
    /** Event Id to Event Processor map */
    protected Map<Integer, BinLogEventProcessor> eventsMap;
    /** Schema Change Event Processor */
    protected SchemaChangeEventProcessor schemaChangeEventProcessor;
    /** The SCN generator implementation, initialized to the default simple implementation*/
    protected SCNGenerator scnGenerator = new NaiveSCNGenerator();

    /**
     * Interface method implementation. Checks for mandatory dependencies and creates the Open Replicator
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.binLogEventMappers,
                "'binLogEventMapper' cannot be null. No bin log event mapper found. This Mysql Events producer will not be initialized");
        Assert.notNull(this.eventsMap,
                "'eventsMap' cannot be null. eventsMap is not initialized properly.This Mysql Events producer will not be initialized");
    }

    /**
     * Starting point for this event producer. Starts Open Replicator listener
     * @param sinceSCN starting SCN
     * @see com.linkedin.databus2.producers.EventProducer#start(long)
     */
    public void start(long sinceSCN) {
        this.sinceSCN.set(sinceSCN);
        openReplicator = new OpenReplicator();
        String binlogFile;
        try {
            String binlogFilePrefix = processUri(new URI(physicalSourceStaticConfig.getUri()));
            int offset = offset(sinceSCN);
            int logid = logid(sinceSCN);
            LOGGER.debug("SCN : " + sinceSCN + " logid : " + logid);
            binlogFile = String.format("%s.%06d", binlogFilePrefix, logid);
            LOGGER.debug("Bin Log File Name : " + binlogFile);
            Map<String, Short> tableUriToSrcIdMap = new HashMap<String, Short>();
            Map<String, String> tableUriToSrcNameMap = new HashMap<String, String>();
            Map<Integer, MysqlAvroEventManager<T>> eventManagersMap = new HashMap<Integer, MysqlAvroEventManager<T>>();
            for (LogicalSourceStaticConfig sourceConfig : physicalSourceStaticConfig.getSources()) {
                tableUriToSrcIdMap.put(sourceConfig.getUri().toLowerCase(), sourceConfig.getId());
                tableUriToSrcNameMap.put(sourceConfig.getUri().toLowerCase(), sourceConfig.getName());
                MysqlAvroEventManager<T> manager = null;
                try {
                    manager = buildEventManagers(sourceConfig, physicalSourceStaticConfig);
                } catch (Exception ex) {
                    LOGGER.error("Got exception while building monitored sources for config :" + sourceConfig, ex);
                    throw new InvalidConfigException(ex);
                }
                eventManagersMap.put(Integer.valueOf(sourceConfig.getId()), manager);
            }
            schemaChangeEventProcessor.setSchemaRegistryService(schemaRegistryService);
            schemaChangeEventProcessor.setTableUriToSrcNameMap(tableUriToSrcNameMap);

            /** updating schemas for registered logical sources */
            for (LogicalSourceStaticConfig sourceConfig : physicalSourceStaticConfig.getSources()) {
                String[] parts = sourceConfig.getUri().split("\\.");
                schemaChangeEventProcessor.process(parts[0], parts[1]);
            }

            mysqlTxnManager = new MysqlTransactionManagerImpl<T>(eventBuffer, maxScnReaderWriter,
                    dbusEventsStatisticsCollector, eventManagersMap, logid, tableUriToSrcIdMap,
                    tableUriToSrcNameMap, schemaRegistryService, this.sinceSCN, binLogEventMappers, scnGenerator,
                    this);
            mysqlTxnManager.setShutdownRequested(false);
            OpenReplicationListener orl = new OpenReplicationListener(mysqlTxnManager, eventsMap,
                    schemaChangeEventProcessor, binlogFilePrefix);
            openReplicator.setBinlogFileName(binlogFile);
            openReplicator.setBinlogPosition(offset);
            openReplicator.setBinlogEventListener(orl);
            openReplicator.start();
        } catch (URISyntaxException u) {
            LOGGER.error("Exception occurred while processing uri : " + u);
            return;
        } catch (InvalidConfigException e) {
            LOGGER.error("Exception occurred while processing uri : " + e);
            return;
        } catch (Exception e) {
            LOGGER.error("Error occurred while starting open replication.." + e);
            return;
        }
        LOGGER.info("Open Replicator has been started successfully for the file " + binlogFile);
    }

    /**
     * Builds event managers for the given sources.
     * @param sourceConfig logical source configuration
     * @param pConfig physical source configuration
     * @return event factory for the given source
     * @throws DatabusException Generic Databus Exception
     * @throws EventCreationException Thrown when event creation failed for a databus source
     * @throws UnsupportedKeyException Thrown when the data type of the "key" field is not a supported type
     * @throws InvalidConfigException Throws when invalid source config is present in configuration provided
     */
    public MysqlAvroEventManager<T> buildEventManagers(LogicalSourceStaticConfig sourceConfig,
            PhysicalSourceStaticConfig pConfig)
            throws DatabusException, EventCreationException, UnsupportedKeyException, InvalidConfigException {
        MysqlAvroEventManager<T> manager = new MysqlAvroEventManager<T>(sourceConfig.getId(),
                (short) pConfig.getId());
        return manager;
    }

    /**
     * Returns the logid ( upper 32 bits of the SCN )
     * For e.g., mysql-bin.000001 is said to have an id 000001
     * @param scn system change number
     * @return logid
     */
    public static int logid(long scn) {
        if (scn == -1 || scn == 0) {
            return 1;
        }
        return (int) ((scn >> 32) & 0xFFFFFFFF);
    }

    /**
     * Returns the binlogoffset ( lower 32 bits of the SCN )
     * @param scn system change number
     * @return binlogoffset
     */
    public static int offset(long scn) {
        if (scn == -1 || scn == 0) {
            return 4;
        }
        return (int) (scn & 0xFFFFFFFF);
    }

    /**
     * Interface method implementation. Returns {@link BinLogEventMapper#getUniqueName()}
     * @see com.linkedin.databus2.producers.EventProducer#getName()
     */
    @Override
    public String getName() {
        return this.name;
    }

    /**
     * Interface method implementation.
     * @see com.linkedin.databus2.producers.EventProducer#getSCN()
     */
    public long getSCN() {
        return this.sinceSCN.get();
    }

    public void updateSCN(long latestScn) {
        this.sinceSCN.set(latestScn);
    }

    /**
     * Interface method implementation. Returns inverted status of {@link #isRunning()}
     * @see com.linkedin.databus2.producers.EventProducer#isPaused()
     */
    @Override
    public boolean isPaused() {
        return !this.openReplicator.isRunning();
    }

    /**
     * Interface method implementation. Returns {@link OpenReplicator#isRunning()}
     * @see com.linkedin.databus2.producers.EventProducer#isRunning()
     */
    @Override
    public boolean isRunning() {
        return this.openReplicator.isRunning();
    }

    /**
     * Interface method implementation. Stops the Open Replicator
     * @see com.linkedin.databus2.producers.EventProducer#shutdown()
     */
    @Override
    public void shutdown() {
        LOGGER.info("Shutdown has been requested. MYSQLEventProducer shutttng down");
        try {
            mysqlTxnManager.setShutdownRequested(true);
            LOGGER.info("Open Replicator Shutting down");
            this.openReplicator.stop(10, TimeUnit.SECONDS);
            LOGGER.info("Open Replicator shutdown complete");
            super.shutdown();
        } catch (Exception e) {
            LOGGER.error("Error while stopping open replicator", e);
        }
        LOGGER.info("MYSQLEventProducer shutdown completed");
    }

    /**
     * Returns the host from where the bin log is being read
     * @return the bin log host name
     */
    public String getBinLogHost() {
        return this.openReplicator.getHost();
    }

    /** Methods that are not supported and therefore throw {@link UnsupportedOperationException} */
    public void pause() {
        throw new UnsupportedOperationException("'pause' is not supported on this event producer");
    }

    public void unpause() {
        throw new UnsupportedOperationException("'unpause' is not supported on this event producer");
    }

    public void waitForShutdown() throws InterruptedException, IllegalStateException {
        throw new UnsupportedOperationException("'waitForShutdown' is not supported on this event producer");
    }

    public void waitForShutdown(long time) throws InterruptedException, IllegalStateException {
        throw new UnsupportedOperationException(
                "'waitForShutdown(long time)' is not supported on this event producer");
    }

    /** Getters and Setters for this class */
    public Map<Integer, BinLogEventMapper<T>> getBinLogEventMappers() {
        return binLogEventMappers;
    }

    public void setBinLogEventMappers(Map<Integer, BinLogEventMapper<T>> binLogEventMapper) {
        this.binLogEventMappers = binLogEventMapper;
    }

    public MysqlTransactionManager getMysqlTxnManager() {
        return mysqlTxnManager;
    }

    public void setMysqlTxnManager(MysqlTransactionManager mysqlTxnManager) {
        this.mysqlTxnManager = mysqlTxnManager;
    }

    public Map<Integer, BinLogEventProcessor> getEventsMap() {
        return eventsMap;
    }

    public void setEventsMap(Map<Integer, BinLogEventProcessor> eventsMap) {
        this.eventsMap = eventsMap;
    }

    public SchemaChangeEventProcessor getSchemaChangeEventProcessor() {
        return schemaChangeEventProcessor;
    }

    public void setSchemaChangeEventProcessor(SchemaChangeEventProcessor schemaChangeEventProcessor) {
        this.schemaChangeEventProcessor = schemaChangeEventProcessor;
    }

    public SCNGenerator getScnGenerator() {
        return scnGenerator;
    }

    public void setScnGenerator(SCNGenerator scnGenerator) {
        this.scnGenerator = scnGenerator;
    }

    /**
     * Extracts individual attributes such as username, password, hostname, port ,server id etc from the uri of the
     * format mysql://or_test%2For_test@localhost:3306/3306/mysql-bin
     * @param uri uri of the format mysql://or_test%2For_test@localhost:3306/3306/mysql-bin
     * @return returns bin log prefix
     */
    protected String processUri(URI uri) throws InvalidConfigException {
        String userInfo = uri.getUserInfo();
        if (null == userInfo) {
            String errorMessage = "missing user info in: " + uri;
            LOGGER.error(errorMessage);
            throw new InvalidConfigException(errorMessage);
        }
        int slashPos = userInfo.indexOf('/');
        if (slashPos < 0) {
            slashPos = userInfo.length();
        } else if (0 == slashPos) {
            String errorMessage = "missing user name in user info: " + userInfo;
            LOGGER.error(errorMessage);
            throw new InvalidConfigException(errorMessage);
        }
        String userName = userInfo.substring(0, slashPos);
        String userPass = slashPos < userInfo.length() - 1 ? userInfo.substring(slashPos + 1) : null;
        String hostName = uri.getHost();

        int port = uri.getPort();
        if (port < 0)
            port = DEFAULT_MYSQL_PORT;

        String path = uri.getPath();
        if (null == path) {
            String errorMessage = "missing path: " + uri;
            LOGGER.error(errorMessage);
            throw new InvalidConfigException(errorMessage);
        }
        Matcher matcher = PATH_PATTERN.matcher(path);
        if (!matcher.matches()) {
            String errorMessage = "invalid path:" + path;
            LOGGER.error(errorMessage);
            throw new InvalidConfigException(errorMessage);
        }
        String[] group = matcher.group().split("/");
        if (group.length != 3) {
            String errorMessage = "Invalid format " + Arrays.toString(group);
            LOGGER.error(errorMessage);
            throw new InvalidConfigException(errorMessage);
        }
        String serverIdStr = group[SERVER_ID];

        int serverId = -1;
        try {
            serverId = Integer.parseInt(serverIdStr);
        } catch (NumberFormatException e) {
            String errorMessage = "incorrect mysql serverid:" + serverId;
            LOGGER.error(errorMessage);
            throw new InvalidConfigException(errorMessage);
        }

        /** Assign them to incoming variables */
        if (null != openReplicator) {
            openReplicator.setUser(userName);
            if (null != userPass) {
                openReplicator.setPassword(userPass);
            }
            openReplicator.setHost(hostName);
            openReplicator.setPort(port);
            openReplicator.setServerId(serverId);
        }
        LOGGER.debug("Extracted bin log prefix is " + group[BIN_LOG_PREFIX]);
        return group[BIN_LOG_PREFIX];
    }

}