/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.windgate.jdbc;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.windgate.core.DriverScript;
import com.asakusafw.windgate.core.ParameterList;
import com.asakusafw.windgate.core.ProcessScript;
import com.asakusafw.windgate.core.resource.DrainDriver;
import com.asakusafw.windgate.core.resource.ResourceManipulator;
import com.asakusafw.windgate.core.resource.SourceDriver;
import com.asakusafw.windgate.core.util.ProcessUtil;
import com.asakusafw.windgate.core.vocabulary.JdbcProcess;
/**
* An implementation of {@link ResourceManipulator} using JDBC.
* @since 0.2.2
*/
public class JdbcResourceManipulator extends ResourceManipulator {
static final Logger LOG = LoggerFactory.getLogger(JdbcResourceManipulator.class);
private final JdbcProfile profile;
private final ParameterList arguments;
/**
* Creates a new instance.
* @param profile the profile
* @param arguments the arguments
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public JdbcResourceManipulator(JdbcProfile profile, ParameterList arguments) {
if (profile == null) {
throw new IllegalArgumentException("profile must not be null"); //$NON-NLS-1$
}
if (arguments == null) {
throw new IllegalArgumentException("arguments must not be null"); //$NON-NLS-1$
}
this.profile = profile;
this.arguments = arguments;
}
@Override
public String getName() {
return profile.getResourceName();
}
@Override
public void cleanupSource(ProcessScript<?> script) throws IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
JdbcScript<?> jdbc = createOppositeJdbcScript(script, DriverScript.Kind.SOURCE);
truncate(jdbc);
}
@Override
public void cleanupDrain(ProcessScript<?> script) throws IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
JdbcScript<?> jdbc = createOppositeJdbcScript(script, DriverScript.Kind.DRAIN);
truncate(jdbc);
}
private void truncate(JdbcScript<?> jdbc) throws IOException {
assert jdbc != null;
Connection conn = profile.openConnection();
Statement statement = null;
try {
LOG.info("Truncating table: {} (for {})",
jdbc.getTableName(),
jdbc.getName());
statement = conn.createStatement();
statement.execute(profile.getTruncateStatement(jdbc.getTableName()));
conn.commit();
} catch (SQLException e) {
for (SQLException ex = e; ex != null; ex = ex.getNextException()) {
LOG.warn(MessageFormat.format(
"Failed to truncate table: {1} (process={0})",
jdbc.getName(),
jdbc.getTableName()), ex);
}
} finally {
close(statement);
close(conn);
}
}
@Override
public <T> SourceDriver<T> createSourceForSource(ProcessScript<T> script) throws IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
JdbcScript<T> jdbc = JdbcResourceUtil.convert(profile, script, arguments, DriverScript.Kind.SOURCE);
T object = ProcessUtil.newDataModel(profile.getResourceName(), script);
boolean succeed = false;
Connection conn = profile.openConnection();
try {
JdbcSourceDriver<T> result = new JdbcSourceDriver<>(profile, jdbc, conn, object);
succeed = true;
return result;
} finally {
if (succeed == false) {
close(conn);
}
}
}
@Override
public <T> DrainDriver<T> createDrainForSource(ProcessScript<T> script) throws IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
JdbcScript<T> jdbc = createOppositeJdbcScript(script, DriverScript.Kind.SOURCE);
boolean succeed = false;
Connection conn = profile.openConnection();
try {
JdbcDrainDriver<T> result = new JdbcDrainDriver<>(profile, jdbc, conn, false);
succeed = true;
return result;
} finally {
if (succeed == false) {
close(conn);
}
}
}
@Override
public <T> SourceDriver<T> createSourceForDrain(ProcessScript<T> script) throws IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
JdbcScript<T> jdbc = createOppositeJdbcScript(script, DriverScript.Kind.DRAIN);
T object = ProcessUtil.newDataModel(profile.getResourceName(), script);
boolean succeed = false;
Connection conn = profile.openConnection();
try {
JdbcSourceDriver<T> result = new JdbcSourceDriver<>(profile, jdbc, conn, object);
succeed = true;
return result;
} finally {
if (succeed == false) {
close(conn);
}
}
}
@Override
public <T> DrainDriver<T> createDrainForDrain(ProcessScript<T> script) throws IOException {
if (script == null) {
throw new IllegalArgumentException("script must not be null"); //$NON-NLS-1$
}
JdbcScript<T> jdbc = JdbcResourceUtil.convert(profile, script, arguments, DriverScript.Kind.DRAIN);
boolean succeed = false;
Connection conn = profile.openConnection();
try {
JdbcDrainDriver<T> result = new JdbcDrainDriver<>(profile, jdbc, conn, false);
succeed = true;
return result;
} finally {
if (succeed == false) {
close(conn);
}
}
}
private void close(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
for (SQLException ex = e; ex != null; ex = ex.getNextException()) {
LOG.warn(MessageFormat.format(
"Failed to close JDBC statement: {0}",
getName()), ex);
}
}
}
}
private void close(Connection conn) {
assert conn != null;
try {
conn.close();
} catch (SQLException e) {
for (SQLException ex = e; ex != null; ex = ex.getNextException()) {
LOG.warn(MessageFormat.format(
"Failed to close JDBC connection: {0}",
getName()), ex);
}
}
}
private <T> JdbcScript<T> createOppositeJdbcScript(
ProcessScript<T> process,
DriverScript.Kind kind) throws IOException {
assert process != null;
assert kind != null;
DriverScript driver = process.getDriverScript(kind);
if (driver.getResourceName().equals(getName()) == false) {
throw new IllegalArgumentException(MessageFormat.format(
"Invalid resource: {0} (direction={1})",
process.getName(),
kind.prefix));
}
ProcessScript<T> opposite;
if (kind == DriverScript.Kind.SOURCE) {
opposite = createDrainProcessFromSource(process);
} else if (kind == DriverScript.Kind.DRAIN) {
opposite = createSourceProcessFromDrain(process);
} else {
throw new AssertionError(kind);
}
return JdbcResourceUtil.convert(profile, opposite, arguments, kind.opposite());
}
private <T> ProcessScript<T> createSourceProcessFromDrain(ProcessScript<T> script) {
assert script != null;
Map<String, String> rebuilt = new HashMap<>(script.getDrainScript().getConfiguration());
rebuilt.remove(JdbcProcess.CONDITION.key());
rebuilt.remove(JdbcProcess.OPERATION.key());
return new ProcessScript<>(
script.getName(),
script.getProcessType(),
script.getDataClass(),
new DriverScript(script.getDrainScript().getResourceName(), rebuilt),
script.getSourceScript());
}
private <T> ProcessScript<T> createDrainProcessFromSource(ProcessScript<T> script) {
assert script != null;
Map<String, String> rebuilt = new HashMap<>(script.getSourceScript().getConfiguration());
rebuilt.remove(JdbcProcess.CONDITION.key());
rebuilt.put(JdbcProcess.OPERATION.key(), JdbcProcess.OperationKind.INSERT_AFTER_TRUNCATE.value());
return new ProcessScript<>(
script.getName(),
script.getProcessType(),
script.getDataClass(),
script.getDrainScript(),
new DriverScript(script.getSourceScript().getResourceName(), rebuilt));
}
}