/** * 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.text.MessageFormat; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; 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.WindGateLogger; import com.asakusafw.windgate.core.vocabulary.DataModelJdbcSupport; import com.asakusafw.windgate.core.vocabulary.JdbcProcess; import com.asakusafw.windgate.core.vocabulary.JdbcProcess.OperationKind; /** * Common utility classes for this package. * @since 0.2.2 * @version 0.7.3 */ final class JdbcResourceUtil { static final WindGateLogger WGLOG = new JdbcLogger(JdbcResourceUtil.class); static final Logger LOG = LoggerFactory.getLogger(JdbcResourceUtil.class); private JdbcResourceUtil() { return; } static String join(Iterable<String> list) { assert list != null; Iterator<String> iterator = list.iterator(); assert iterator.hasNext(); StringBuilder buf = new StringBuilder(); buf.append(iterator.next()); while (iterator.hasNext()) { buf.append(", "); buf.append(iterator.next()); } return buf.toString(); } static <T> JdbcScript<T> convert( JdbcProfile profile, ProcessScript<T> process, ParameterList arguments, DriverScript.Kind kind) throws IOException { assert profile != null; assert process != null; assert arguments != null; assert kind != null; String supportClassName = extract(profile, process, kind, JdbcProcess.JDBC_SUPPORT, true); DataModelJdbcSupport<? super T> support = loadSupport(profile, process, supportClassName); String tableName = extract(profile, process, kind, JdbcProcess.TABLE, true); String columnNameList = extract(profile, process, kind, JdbcProcess.COLUMNS, true); List<String> columnNames = Stream.of(columnNameList.split(",")) //$NON-NLS-1$ .sequential() .map(String::trim) .filter(s -> s.isEmpty() == false) .collect(Collectors.toList()); if (support.isSupported(columnNames) == false) { WGLOG.error("E01002", profile.getResourceName(), process.getName(), supportClassName); throw new IOException(MessageFormat.format( "JDBC support class {2} does not support columns: {3} (resource={0}, process={1})", profile.getResourceName(), process.getName(), support.getClass().getName(), columnNames)); } String condition = extract(profile, process, kind, JdbcProcess.CONDITION, false); String customTruncate = extract(profile, process, kind, JdbcProcess.CUSTOM_TRUNCATE, false); if (kind == DriverScript.Kind.SOURCE) { if (condition == null || condition.isEmpty()) { LOG.debug("\"WHERE\" clause is not specified in source process \"{}\"", profile.getResourceName(), process.getName()); condition = null; } else { try { condition = arguments.replace(condition, true); } catch (IllegalArgumentException e) { WGLOG.error("E01001", profile.getResourceName(), process.getName(), kind.prefix, JdbcProcess.CONDITION.key(), condition); throw new IOException(MessageFormat.format( "\"{3}\" failed to resolve parameters: \"{4}\" (resource={0}, process={1}, kind={2})", profile.getResourceName(), process.getName(), kind, JdbcProcess.CONDITION.key(), condition), e); } } customTruncate = null; } if (kind == DriverScript.Kind.DRAIN) { condition = null; if (customTruncate == null || customTruncate.isEmpty()) { LOG.debug("custom \"TRUNCATE\" statement is not specified in drain process \"{}\"", profile.getResourceName(), process.getName()); customTruncate = null; } else { try { customTruncate = arguments.replace(customTruncate, true); } catch (IllegalArgumentException e) { WGLOG.error("E01001", profile.getResourceName(), process.getName(), kind.prefix, JdbcProcess.CUSTOM_TRUNCATE.key(), customTruncate); throw new IOException(MessageFormat.format( "\"{3}\" failed to resolve parameters: \"{4}\" (resource={0}, process={1}, kind={2})", profile.getResourceName(), process.getName(), kind, JdbcProcess.CUSTOM_TRUNCATE.key(), customTruncate), e); } } String operationString = extract(profile, process, kind, JdbcProcess.OPERATION, true); JdbcProcess.OperationKind op = JdbcProcess.OperationKind.find(operationString); if (op != OperationKind.INSERT_AFTER_TRUNCATE) { WGLOG.error("E01001", profile.getResourceName(), process.getName(), kind.prefix, JdbcProcess.OPERATION.key(), operationString); throw new IOException(MessageFormat.format( "Resource \"{0}\" supports only {4} in \"{3}\": \"{5}\" (process={1}, kind={2})", profile.getResourceName(), process.getName(), kind, JdbcProcess.OPERATION.key(), JdbcProcess.OperationKind.INSERT_AFTER_TRUNCATE, operationString)); } } String optionsString = extract(profile, process, kind, JdbcProcess.OPTIONS, false); Set<String> options = optionsString == null ? Collections.emptySet() : Stream.of(optionsString.split(",")) .sequential() .map(String::trim) .filter(s -> s.isEmpty() == false) .collect(Collectors.toCollection(LinkedHashSet::new)); JdbcScript<T> script = new JdbcScript<T>(process.getName(), support, tableName, columnNames) .withCondition(condition) .withCustomTruncate(customTruncate) .withOptions(options); return script; } private static String extract( JdbcProfile profile, ProcessScript<?> process, DriverScript.Kind kind, JdbcProcess item, boolean mandatory) throws IOException { assert process != null; assert kind != null; assert item != null; Map<String, String> conf = process.getDriverScript(kind).getConfiguration(); String value = conf.get(item.key()); if (mandatory && (value == null || value.isEmpty())) { WGLOG.error("E01001", profile.getResourceName(), process.getName(), kind.prefix, item.key(), value); throw new IOException(MessageFormat.format( "Resource \"{0}\" requires config \"{3}\" (process={1}, direction={2})", profile.getResourceName(), process.getName(), kind, item.key())); } return value == null ? null : value.trim(); } @SuppressWarnings("unchecked") private static <T> DataModelJdbcSupport<? super T> loadSupport( JdbcProfile profile, ProcessScript<T> script, String supportClassName) throws IOException { assert script != null; assert supportClassName != null; LOG.debug("Creating JDBC support object: {} (resource={}, process={})", new Object[] { supportClassName, profile.getResourceName(), script.getName(), }); Class<?> supportClass; try { supportClass = Class.forName(supportClassName, true, profile.getClassLoader()); } catch (ClassNotFoundException e) { WGLOG.error(e, "E01002", profile.getResourceName(), script.getName(), supportClassName); throw new IOException(MessageFormat.format( "Failed to load a JDBC support class: {2} (resource={0}, process={1})", profile.getResourceName(), script.getName(), supportClassName), e); } if (DataModelJdbcSupport.class.isAssignableFrom(supportClass) == false) { WGLOG.error("E01002", profile.getResourceName(), script.getName(), supportClassName); throw new IOException(MessageFormat.format( "JDBC support class must be a subtype of {3}: {2} (resource={0}, process={1})", profile.getResourceName(), script.getName(), supportClass.getName(), DataModelJdbcSupport.class.getName())); } DataModelJdbcSupport<?> obj; try { obj = supportClass.asSubclass(DataModelJdbcSupport.class).newInstance(); } catch (Exception e) { WGLOG.error(e, "E01002", profile.getResourceName(), script.getName(), supportClassName); throw new IOException(MessageFormat.format( "Failed to create a JDBC support object: {2} (resource={0}, process={1})", profile.getResourceName(), script.getName(), supportClass.getName()), e); } if (obj.getSupportedType().isAssignableFrom(script.getDataClass()) == false) { WGLOG.error("E01002", profile.getResourceName(), script.getName(), supportClassName); throw new IOException(MessageFormat.format( "JDBC support class {2} does not support data model: {3} (resource={0}, process={1})", profile.getResourceName(), script.getName(), supportClass.getName(), script.getDataClass().getName())); } return (DataModelJdbcSupport<? super T>) obj; } }