fr.juanwolf.mysqlbinlogreplicator.component.DomainClassAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for fr.juanwolf.mysqlbinlogreplicator.component.DomainClassAnalyzer.java

Source

/*
Copyright (C) 2015  Jean-Loup Adde
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package fr.juanwolf.mysqlbinlogreplicator.component;

import com.github.shyiko.mysql.binlog.event.deserialization.ColumnType;
import fr.juanwolf.mysqlbinlogreplicator.DomainClass;
import fr.juanwolf.mysqlbinlogreplicator.annotations.MysqlMapping;
import fr.juanwolf.mysqlbinlogreplicator.annotations.NestedMapping;
import fr.juanwolf.mysqlbinlogreplicator.nested.NestedRowMapper;
import fr.juanwolf.mysqlbinlogreplicator.nested.requester.SQLRequester;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Component
public class DomainClassAnalyzer {

    /**
     * Map containing every domainClass mapped by table names
     */
    @Getter
    @Setter
    private Map<String, DomainClass> domainClassMap = new HashMap<>();

    /**
     * Map containing every domainClass mapped by nested table names
     */
    @Getter
    @Setter
    private Map<String, DomainClass> nestedDomainClassMap = new HashMap<>();

    @Value("${mysql.schema}")
    String databaseName;

    @Autowired
    private ApplicationContext applicationContext;

    @Setter
    @Value("${mysql.scanmapping}")
    private String scanMapping;

    @Getter
    private List<String> mappingTablesExpected;

    @Getter
    private List<String> nestedTables;

    @Autowired
    private Environment environment;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public static final DateFormat BINLOG_DATETIME_FORMATTER = new SimpleDateFormat("EEE MMM dd hh:mm:ss z yyyy",
            Locale.UK);

    public static final DateFormat BINLOG_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd", Locale.UK);

    @Getter
    public DateFormat binlogOutputDateFormatter;

    @PostConstruct
    public void postConstruct() throws BeansException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        mappingTablesExpected = new ArrayList<>();
        nestedTables = new ArrayList<>();
        Reflections reflections = new Reflections(scanMapping);
        Set<Class<?>> types = reflections.getTypesAnnotatedWith(MysqlMapping.class);
        final Iterator<Class<?>> iterator = types.iterator();
        while (iterator.hasNext()) {
            Class classDomain = iterator.next();
            MysqlMapping mysqlMapping = (MysqlMapping) classDomain.getAnnotation(MysqlMapping.class);
            DomainClass domainClass = new DomainClass();
            domainClass.setDomainClass(classDomain);
            mappingTablesExpected.add(mysqlMapping.table());
            CrudRepository crudRepository = (CrudRepository) applicationContext.getBean(mysqlMapping.repository());
            domainClass.setCrudRepository(crudRepository);
            domainClass.setTable(mysqlMapping.table());
            Map<String, SQLRequester> nestedClassesMap = new HashMap<>();
            for (Field field : classDomain.getDeclaredFields()) {
                NestedMapping nestedMapping = field.getAnnotation(NestedMapping.class);
                if (nestedMapping != null) {
                    Class sqlRequesterClass = nestedMapping.sqlAssociaton().getRequesterClass();
                    Constructor sqlRequesterConstructor = sqlRequesterClass.getConstructor();
                    SQLRequester sqlRequester = (SQLRequester) sqlRequesterConstructor.newInstance();
                    sqlRequester.setDatabaseName(databaseName);
                    sqlRequester.setEntryTableName(mysqlMapping.table());
                    sqlRequester.setExitTableName(nestedMapping.table());
                    sqlRequester.setForeignKey(nestedMapping.foreignKey());
                    sqlRequester.setPrimaryKeyForeignEntity(nestedMapping.primaryKey());
                    sqlRequester.setEntryType(classDomain);
                    sqlRequester.setAssociatedField(field);
                    Class foreignType = field.getType();
                    if (field.getGenericType() instanceof ParameterizedType) {
                        ParameterizedType genericType = (ParameterizedType) field.getGenericType();
                        Class properType = (Class) genericType.getActualTypeArguments()[0];
                        foreignType = properType;
                    }
                    sqlRequester.setForeignType(foreignType);
                    sqlRequester.setJdbcTemplate(jdbcTemplate);
                    NestedRowMapper currentClassNestedRowMapper = new NestedRowMapper(classDomain, this,
                            mysqlMapping.table());
                    NestedRowMapper foreignClassNestedRowMapper = new NestedRowMapper(foreignType, this,
                            mysqlMapping.table());
                    sqlRequester.setRowMapper(currentClassNestedRowMapper);
                    sqlRequester.setForeignRowMapper(foreignClassNestedRowMapper);
                    nestedClassesMap.put(field.getName(), sqlRequester);
                    nestedTables.add(nestedMapping.table());
                    nestedDomainClassMap.put(nestedMapping.table(), domainClass);
                }
            }
            domainClass.setSqlRequesters(nestedClassesMap);
            domainClassMap.put(domainClass.getTable(), domainClass);
        }
        if (environment.getProperty("date.output") != null) {
            binlogOutputDateFormatter = new SimpleDateFormat(environment.getProperty("date.output"));
        }
    }

    public Object generateInstanceFromName(String name) throws ReflectiveOperationException {
        DomainClass domainClass = domainClassMap.get(name);
        if (domainClass == null) {
            log.error("Class with name {} not found.", name);
            throw new ReflectiveOperationException();
        }
        Class classAsked = domainClassMap.get(name).getDomainClass();
        try {
            Constructor classConstructor = classAsked.getConstructor();
            return classConstructor.newInstance();
        } catch (Exception e) {
            log.error("Impossible to instantiate an instance of {}: "
                    + "no empty constructor found or the constructor is private for class {}", name, name);
        }
        // Should never happen.
        return null;
    }

    public void instantiateField(Object object, Field field, Object value, int columnType, String tablename)
            throws ParseException, IllegalAccessException {
        field.setAccessible(true);
        if (columnType == ColumnType.DATETIME.getCode() && field.getType() == Date.class) {
            Date date = BINLOG_DATETIME_FORMATTER.parse((String) value);
            field.set(object, date);
        } else if (columnType == ColumnType.DATE.getCode() && field.getType() == Date.class) {
            Date date = BINLOG_DATE_FORMATTER.parse((String) value);
            field.set(object, date);
        } else if (columnType == ColumnType.DATETIME.getCode() && field.getType() == String.class) {
            Date date = BINLOG_DATETIME_FORMATTER.parse((String) value);
            if (binlogOutputDateFormatter != null) {
                field.set(object, binlogOutputDateFormatter.format(date));
            } else {
                log.warn("No date.output DateFormat found in your property file. If you want anything else than"
                        + "the timestamp as output of your date, set this property with a java DateFormat.");
                field.set(object, date.toString());
            }
        } else if (columnType == ColumnType.TIME.getCode() && field.getType() == Time.class) {
            Time time = Time.valueOf((String) value);
            field.set(object, time);
        } else if (columnType == ColumnType.TIMESTAMP.getCode() || field.getType() == Timestamp.class) {
            Timestamp timestamp = Timestamp.valueOf((String) value);
            field.set(object, timestamp);
        } else if ((columnType == ColumnType.BIT.getCode() || columnType == ColumnType.TINY.getCode())
                && field.getType() == boolean.class) {
            boolean booleanField = ((Byte) value) != 0;
            field.set(object, booleanField);
        } else if (columnType == ColumnType.LONG.getCode() && field.getType() == long.class) {
            field.set(object, Long.parseLong((String) value));
        } else if (columnType == ColumnType.LONG.getCode() && isInteger(field)) {
            field.set(object, Integer.parseInt((String) value));
        } else if (columnType == ColumnType.FLOAT.getCode() && field.getType() == float.class) {
            field.set(object, Float.parseFloat((String) value));
        } else if (field.getType() == String.class) {
            field.set(object, value);
        } else {
            if (mappingTablesExpected.contains(tablename)) {
                Object nestedObject = generateNestedField(field, value, tablename);
                field.set(object, nestedObject);
            }
        }
    }

    public Object generateNestedField(Field field, Object value, String tablename) {
        DomainClass currentDomainClass = domainClassMap.get(tablename);
        SQLRequester sqlRequester = currentDomainClass.getSqlRequesters().get(field.getName());
        return sqlRequester.queryForeignEntity(sqlRequester.getForeignKey(),
                sqlRequester.getPrimaryKeyForeignEntity(), (String) value);
    }

    // TOOLS
    static boolean isInteger(Field field) {
        return field.getType() == Integer.class || field.getType() == int.class;
    }
}