blob: 1673e9b2ae2861a8d380da8e4645a8cf2099c7b2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.syncope.core.persistence.jpa.content;
import java.sql.Types;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.sql.DataSource;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* SAX handler for generating SQL INSERT statements out of given XML file.
*/
public class ContentLoaderHandler extends DefaultHandler {
private static final Logger LOG = LoggerFactory.getLogger(ContentLoaderHandler.class);
private final JdbcTemplate jdbcTemplate;
private final String rootElement;
private final boolean continueOnError;
private final Map<String, String> fetches = new HashMap<>();
private final StringSubstitutor paramSubstitutor;
public ContentLoaderHandler(
final DataSource dataSource,
final String rootElement,
final boolean continueOnError,
final Environment env) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.rootElement = rootElement;
this.continueOnError = continueOnError;
this.paramSubstitutor = new StringSubstitutor(key -> {
String value = env.getProperty(key, fetches.get(key));
return StringUtils.isBlank(value) ? null : value;
});
}
private Object[] getParameters(final String tableName, final Attributes attrs) {
Map<String, Integer> colTypes = jdbcTemplate.query(
"SELECT * FROM " + tableName + " WHERE 0=1", rs -> {
Map<String, Integer> types = new HashMap<>();
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
types.put(rs.getMetaData().getColumnName(i).toUpperCase(), rs.getMetaData().getColumnType(i));
}
return types;
});
Object[] parameters = new Object[attrs.getLength()];
for (int i = 0; i < attrs.getLength(); i++) {
Integer colType = Objects.requireNonNull(colTypes).get(attrs.getQName(i).toUpperCase());
if (colType == null) {
LOG.warn("No column type found for {}", attrs.getQName(i).toUpperCase());
colType = Types.VARCHAR;
}
String value = paramSubstitutor.replace(attrs.getValue(i));
if (value == null) {
LOG.warn("Variable ${} could not be resolved", attrs.getValue(i));
value = attrs.getValue(i);
}
switch (colType) {
case Types.INTEGER:
case Types.TINYINT:
case Types.SMALLINT:
try {
parameters[i] = Integer.valueOf(value);
} catch (NumberFormatException e) {
LOG.error("Unparsable Integer '{}'", value);
parameters[i] = value;
}
break;
case Types.NUMERIC:
case Types.DECIMAL:
case Types.BIGINT:
try {
parameters[i] = Long.valueOf(value);
} catch (NumberFormatException e) {
LOG.error("Unparsable Long '{}'", value);
parameters[i] = value;
}
break;
case Types.DOUBLE:
try {
parameters[i] = Double.valueOf(value);
} catch (NumberFormatException e) {
LOG.error("Unparsable Double '{}'", value);
parameters[i] = value;
}
break;
case Types.REAL:
case Types.FLOAT:
try {
parameters[i] = Float.valueOf(value);
} catch (NumberFormatException e) {
LOG.error("Unparsable Float '{}'", value);
parameters[i] = value;
}
break;
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
try {
parameters[i] = FormatUtils.parseDate(value);
} catch (ParseException e) {
LOG.error("Unparsable Date '{}'", value);
parameters[i] = value;
}
break;
case Types.BIT:
case Types.BOOLEAN:
parameters[i] = "1".equals(value) ? Boolean.TRUE : Boolean.FALSE;
break;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
try {
parameters[i] = DatatypeConverter.parseHexBinary(value);
} catch (IllegalArgumentException e) {
parameters[i] = value;
}
break;
case Types.BLOB:
try {
parameters[i] = DatatypeConverter.parseHexBinary(value);
} catch (IllegalArgumentException e) {
LOG.warn("Error decoding hex string to specify a blob parameter", e);
parameters[i] = value;
} catch (Exception e) {
LOG.warn("Error creating a new blob parameter", e);
}
break;
default:
parameters[i] = value;
}
}
return parameters;
}
@Override
public void startElement(final String uri, final String localName, final String qName, final Attributes atts)
throws SAXException {
// skip root element
if (rootElement.equals(qName)) {
return;
}
if ("fetch".equalsIgnoreCase(qName)) {
String value = jdbcTemplate.queryForObject(atts.getValue("query"), String.class);
String key = atts.getValue("key");
fetches.put(key, value);
} else {
StringBuilder query = new StringBuilder("INSERT INTO ").append(qName).append('(');
StringBuilder values = new StringBuilder();
for (int i = 0; i < atts.getLength(); i++) {
query.append(atts.getQName(i));
values.append('?');
if (i < atts.getLength() - 1) {
query.append(',');
values.append(',');
}
}
query.append(") VALUES (").append(values).append(')');
try {
jdbcTemplate.update(query.toString(), getParameters(qName, atts));
} catch (DataAccessException e) {
LOG.error("While trying to perform {} with params {}", query, getParameters(qName, atts), e);
if (!continueOnError) {
throw e;
}
}
}
}
}