Package hu.sztaki.ilab.longneck.process.access

Source Code of hu.sztaki.ilab.longneck.process.access.SimpleDatabaseTarget

package hu.sztaki.ilab.longneck.process.access;

import hu.sztaki.ilab.longneck.Record;
import hu.sztaki.ilab.longneck.util.DatabaseUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

/**
* A simplified version of DatabaseTarget, handling only character and numeric types.
*
* The main goal is to leave type conversions to the database engine, pass only
* string values to it, and convert only numeric fields enumerated
* directly (as numeric-fields-to-convert field of an insert-query).
*
* Type conversion might be carried out using conversion functions of the database,
* for example using TO_NUMBER(:numeric_field) in the insert-query.
*
* @author Csaba Sidló <sidlo@sztaki.mta.hu>
*/
public class SimpleDatabaseTarget extends AbstractDatabaseAccessor implements Target  {

    /** Logger. */
    private final Logger log = Logger.getLogger(SimpleDatabaseTarget.class);
    /** The query to truncate the target table. */   
    private String truncateQuery;
    /** The query to insert record into target table. */
    //private String insertQuery;
    /** Enumeration of the fields that have to be converted to numeric values (numeric-fields-to-convert)*/
    //private String insertQueryNumericFields ;   
    private SimpleDatabaseTargetSQL insertQuery ;   
    /** List of placeholders (bind variables) in insertQuery */
    private List<String> placeholders ;
    /** The mapping between field names and SQL placeholders. */
    private Map<String,String> placeholderMap;
    /** Table column name to SQL type map: only NUMERIC types are used */
    private Map<String,Integer> typeMap = new HashMap<String,Integer>();
    /** The total number of inserted records. */
    private long insertedRecords = 0;
    /** The runtime properties. */
    protected Properties runtimeProperties;
    /** Threshold for error line. */
    protected Integer errorthreshold;
    /** Count error. */
    protected int error_count;
   
    @Override
    public void truncate() {
        jdbcTemplate.update(truncateQuery);
    }

    @Override
    public void appendRecords(Collection<Record> records) {
        // Create jdbc template
        final NamedParameterJdbcTemplate npjt = new NamedParameterJdbcTemplate(jdbcTemplate);       
        // init sql parameters for placeholder fields, for all records       
        final ArrayList<MapSqlParameterSource> parameters = new ArrayList<MapSqlParameterSource>();
       
        // Copy record contents to sql parameter source
        for (Record r: records) {
            try {
                MapSqlParameterSource p = new MapSqlParameterSource() ;
               
                for (String f: placeholders) {
                    try {
                        if (! r.has(f) || r.get(f).getValue() == null) {
                            // Handle undefined or null-valued fields by simply adding null                           
                            p.addValue(placeholderMap.get(f), null);
                        } else {
                            if (typeMap.get(f).equals(java.sql.Types.NUMERIC)) {
                                try {
                                    p.addValue(placeholderMap.get(f), Long.valueOf(r.get(f).getValue()),
                                            java.sql.Types.NUMERIC);
                                } catch (NumberFormatException ex) {
                                    // Parse to big decimal as a fallback option
                                    p.addValue(placeholderMap.get(f), new java.math.BigDecimal(r.get(f).getValue()),
                                            java.sql.Types.NUMERIC);
                                }
                            }
                            else {
                                p.addValue(placeholderMap.get(f), r.get(f).getValue(), typeMap.get(f)) ;
                            }
                        }
                    } catch (NullPointerException ex) {
                        log.warn("Target SQL placeholder '" + f + "' refers to a nonexistent field.");
                        p.addValue(placeholderMap.get(f), null);
                    }
                }
               
                parameters.add(p);
            } catch (NumberFormatException ex) {
                log.warn("Couldn't parse one of the fields of numeric-fields-to-convert; record: " + r.toString());
            }
        }
       
        // Batch update in transaction
        TransactionTemplate txTemplate = new TransactionTemplate(txManager);       
        try {
            txTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus ts) {
                    int[] affectedRows =
                            npjt.batchUpdate(insertQuery.getSql(), parameters.toArray(new MapSqlParameterSource[0]));
                   
                    int sum = 0;
                    for (int i = 0; i < affectedRows.length; ++i) {
                        sum += DatabaseUtils.getAffectedRowsNumber(affectedRows[i]);
                    }
                   
                    insertedRecords += sum;
                    // log.debug(String.format("Inserted %1$d rows in batch.", sum));
                }
            });
           
        } catch (RuntimeException ex) {
            log.warn(this, ex);
       
            // Do line by line
            int insertCount = 0;
            for (int i = 0; i < parameters.size(); ++i) {
                try {
                    int affectedRows = npjt.update(insertQuery.getSql(), parameters.get(i));                   
                    insertCount += DatabaseUtils.getAffectedRowsNumber(affectedRows);
                   
                } catch (RuntimeException ex2) {
                    log.warn("Could not insert record: "
                            + DatabaseUtils.sqlParameterSourceToText(parameters.get(i)), ex2);
                    if (errorthreshold != null && ++error_count >= errorthreshold) {
                        throw new RuntimeException(new ThresHoldException("Reach the error line count database target threshold: " + errorthreshold));
                    }
                }
            }

            insertedRecords += insertCount;
        }
    }

    @Override
    public void init() {
        // Read treshold from runtime properties if it's present
        if(runtimeProperties.getProperty("databaseTarget.errorThreshold") != null) {
            try {
                errorthreshold = Integer.parseInt(runtimeProperties.getProperty("databaseTarget.errorThreshold"));
            } catch (NumberFormatException e) {
                log.warn(String.format("Threshold value isn't a number: %1$s",
                        runtimeProperties.getProperty("databaseTarget.errorThreshold")));
            }
        }
        // Try to parse column placeholders
        try {
            placeholders = getPlaceholders(insertQuery.getSql());
           
            // Check and replace '-' characters in placeholders
            placeholderMap = new HashMap<String,String>(placeholders.size());
            for (String p : placeholders) {
                if (p.contains("-")) {
                    placeholderMap.put(p, p.replaceAll("-", "#"));
                } else {
                    placeholderMap.put(p, p);
                }
            }
           
            // Sort based on length in descending order
            Comparator<String> c = new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return o2.length() - o1.length();
                }           
            };
            Collections.sort(placeholders, c);
           
            // Replace placeholders in the insert query
            String tmpQuery = insertQuery.getSql();
            for (String p : placeholders) {
                tmpQuery = tmpQuery.replaceAll(":" + p, ":" + placeholderMap.get(p));
            }
           
            insertQuery.setSql(tmpQuery);
           
        } catch (QueryParseException ex) {
            // do nothing
        }

        // Set map with types       
        for (String field : placeholders) {
            if ((insertQuery.getNumericFields() != null)
                    && ( (","+ insertQuery.getNumericFields() + ",").toUpperCase().matches(
                            ".*[^\\w]+" + field.toUpperCase() + "[^\\w]+.*" ) )  ) {
                typeMap.put(field, java.sql.Types.NUMERIC);               
            } else {
                typeMap.put(field, SqlParameterSourceRecordWrapper.TYPE_UNKNOWN);
            }
        }
    }

    @Override
    public void close() {
        log.debug(String.format("Total number of inserted records: %1$d", insertedRecords));
    }
   
   
    public static List<String> getPlaceholders(String insertQuery) throws QueryParseException {
        Matcher matcher = Pattern.compile(":([\\w\\-]+)", Pattern.CASE_INSENSITIVE)
                .matcher(insertQuery);

        List<String> placeholders = new LinkedList<String>();
        while ( matcher.find() ) {
            String candid = insertQuery.substring(matcher.start(1), matcher.end(1)) ;
            if insertQuery.substring(0, matcher.start(1) ).replaceAll("[^']", "").length()%2 == ) {
                placeholders.add(candid) ;
            }
        }
        return placeholders;
    }

    public SimpleDatabaseTargetSQL getInsertQuery() {
        return insertQuery ;
    }
    public void setInsertQuery(SimpleDatabaseTargetSQL insertQuery) {
        this.insertQuery = insertQuery ;
    }
    public String getTruncateQuery() {
        return truncateQuery;
    }

    public void setTruncateQuery(String truncateQuery) {
        this.truncateQuery = truncateQuery;
    }

    public Map<String, String> getPlaceholderMap() {
        return placeholderMap;
    }

    public void setPlaceholderMap(Map<String, String> placeholderMap) {
        this.placeholderMap = placeholderMap;
    }

    public List<String> getPlaceholders() {
        return placeholders;
    }

    public void setPlaceholders(List<String> placeholders) {
        this.placeholders = placeholders;
    }

    public Integer getErrorthreshold() {
        return errorthreshold;
    }

    public void setErrorthreshold(Integer errorthreshold) {
        this.errorthreshold = errorthreshold;
    }

    public Properties getRuntimeProperties() {
        return runtimeProperties;
    }

    public void setRuntimeProperties(Properties runtimeProperties) {
        this.runtimeProperties = runtimeProperties;
    }
   
}
TOP

Related Classes of hu.sztaki.ilab.longneck.process.access.SimpleDatabaseTarget

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.