/*
* Copyright (C) 2008-2017, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.juick.database;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRED;
/**
* Created by aalexeev on 12/13/16.
*/
public class MySqlUpdater {
private static final Pattern UPDATE_PATTERN = Pattern.compile(
"update\\s+(version|`version`)\\s+set\\s+(version|`version`)\\s+=\\s*(\\d+)",
Pattern.CASE_INSENSITIVE);
private final Logger logger = LoggerFactory.getLogger(getClass());
private final JdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
private final String updateSqlResource;
public MySqlUpdater(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager, String updateSqlResource) {
Assert.notNull(jdbcTemplate, "JdbcTemplate must be initialized");
Assert.notNull(transactionManager, "PlatformTransactionManager must be initialized");
Assert.notNull(updateSqlResource, "sqlResource must be initialized");
this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.updateSqlResource = updateSqlResource;
}
@PostConstruct
public void init() {
try (
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(updateSqlResource);
) {
if (is != null) {
String content = IOUtils.toString(is, CharEncoding.UTF_8);
if (StringUtils.isNotEmpty(content)) {
String[] sqlArray = content.split(";");
if (sqlArray.length > 0) {
List sqlList = new ArrayList<>(sqlArray.length);
for (String sql : sqlArray)
if (!sql.isEmpty()) {
String sqlTrimmed = sql.trim();
if (!sqlTrimmed.isEmpty())
sqlList.add(sqlTrimmed);
}
if (!sqlList.isEmpty())
processingSql(sqlList);
}
}
}
} catch (Exception e) {
logger.error("MySqlUpdater initialization exception", e);
}
}
private void processingSql(final List sqls) {
long currentDbVersion = getSingleResult(this::getVersionRaw);
long actualVersion;
List changesSql = new ArrayList<>();
for (String sql : sqls) {
changesSql.add(sql);
Matcher m = UPDATE_PATTERN.matcher(sql);
if (m.matches()) {
actualVersion = Long.valueOf(m.group(3));
if (actualVersion > currentDbVersion) {
updateInTransaction(changesSql);
currentDbVersion = actualVersion;
}
changesSql.clear();
}
}
}
private void updateInTransaction(final List sqls) {
transactionTemplate.setReadOnly(false);
transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRED);
transactionTemplate.execute(status -> {
for (String sql : sqls)
jdbcTemplate.execute(sql);
return 0;
});
}
private T getSingleResult(Supplier supplier) {
transactionTemplate.setReadOnly(true);
transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRED);
return transactionTemplate.execute(status -> supplier.get());
}
private long getVersionRaw() {
int cnt = jdbcTemplate.query(
"SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ?",
rs -> {
int result = 0;
if (rs.next())
result = rs.getInt(1);
return result;
},
"juick", "version");
long version = 0l;
if (cnt == 1) {
List list = jdbcTemplate.queryForList("select version from version", Long.class);
if (!list.isEmpty())
version = list.get(0);
}
return version;
}
}