1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.persistence.jpa.content;
20
21 import java.sql.Types;
22 import java.time.format.DateTimeParseException;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Objects;
26 import javax.sql.DataSource;
27 import javax.xml.bind.DatatypeConverter;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.commons.text.StringEscapeUtils;
30 import org.apache.commons.text.StringSubstitutor;
31 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.springframework.core.env.Environment;
35 import org.springframework.dao.DataAccessException;
36 import org.springframework.jdbc.core.JdbcTemplate;
37 import org.xml.sax.Attributes;
38 import org.xml.sax.SAXException;
39 import org.xml.sax.helpers.DefaultHandler;
40
41
42
43
44 public class ContentLoaderHandler extends DefaultHandler {
45
46 private static final Logger LOG = LoggerFactory.getLogger(ContentLoaderHandler.class);
47
48 private static final String CONF_DIR = "syncope.conf.dir";
49
50 private final JdbcTemplate jdbcTemplate;
51
52 private final String rootElement;
53
54 private final boolean continueOnError;
55
56 private final Map<String, String> fetches = new HashMap<>();
57
58 private final StringSubstitutor paramSubstitutor;
59
60 public ContentLoaderHandler(
61 final DataSource dataSource,
62 final String rootElement,
63 final boolean continueOnError,
64 final Environment env) {
65
66 this.jdbcTemplate = new JdbcTemplate(dataSource);
67 this.rootElement = rootElement;
68 this.continueOnError = continueOnError;
69 this.paramSubstitutor = new StringSubstitutor(key -> {
70 String value = env.getProperty(key, fetches.get(key));
71 if (value != null && CONF_DIR.equals(key)) {
72 value = value.replace('\\', '/');
73 }
74 return StringUtils.isBlank(value) ? null : value;
75 });
76 }
77
78 private Object[] getParameters(final String tableName, final Attributes attrs) {
79 Map<String, Integer> colTypes = jdbcTemplate.query(
80 "SELECT * FROM " + tableName + " WHERE 0=1", rs -> {
81 Map<String, Integer> types = new HashMap<>();
82 for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
83 types.put(rs.getMetaData().getColumnName(i).toUpperCase(), rs.getMetaData().getColumnType(i));
84 }
85 return types;
86 });
87
88 Object[] parameters = new Object[attrs.getLength()];
89 for (int i = 0; i < attrs.getLength(); i++) {
90 Integer colType = Objects.requireNonNull(colTypes).get(attrs.getQName(i).toUpperCase());
91 if (colType == null) {
92 LOG.warn("No column type found for {}", attrs.getQName(i).toUpperCase());
93 colType = Types.VARCHAR;
94 }
95
96 String value = paramSubstitutor.replace(attrs.getValue(i));
97 if (value == null) {
98 LOG.warn("Variable ${} could not be resolved", attrs.getValue(i));
99 value = attrs.getValue(i);
100 }
101 value = StringEscapeUtils.unescapeXml(value);
102
103 switch (colType) {
104 case Types.INTEGER:
105 case Types.TINYINT:
106 case Types.SMALLINT:
107 try {
108 parameters[i] = Integer.valueOf(value);
109 } catch (NumberFormatException e) {
110 LOG.error("Unparsable Integer '{}'", value);
111 parameters[i] = value;
112 }
113 break;
114
115 case Types.NUMERIC:
116 case Types.DECIMAL:
117 case Types.BIGINT:
118 try {
119 parameters[i] = Long.valueOf(value);
120 } catch (NumberFormatException e) {
121 LOG.error("Unparsable Long '{}'", value);
122 parameters[i] = value;
123 }
124 break;
125
126 case Types.DOUBLE:
127 try {
128 parameters[i] = Double.valueOf(value);
129 } catch (NumberFormatException e) {
130 LOG.error("Unparsable Double '{}'", value);
131 parameters[i] = value;
132 }
133 break;
134
135 case Types.REAL:
136 case Types.FLOAT:
137 try {
138 parameters[i] = Float.valueOf(value);
139 } catch (NumberFormatException e) {
140 LOG.error("Unparsable Float '{}'", value);
141 parameters[i] = value;
142 }
143 break;
144
145 case Types.DATE:
146 case Types.TIME:
147 case Types.TIMESTAMP:
148 case Types.TIMESTAMP_WITH_TIMEZONE:
149 case -101:
150 try {
151 parameters[i] = FormatUtils.parseDate(value);
152 } catch (DateTimeParseException e) {
153 LOG.error("Unparsable Date '{}'", value);
154 parameters[i] = value;
155 }
156 break;
157
158 case Types.BIT:
159 case Types.BOOLEAN:
160 parameters[i] = "1".equals(value) ? Boolean.TRUE : Boolean.FALSE;
161 break;
162
163 case Types.BINARY:
164 case Types.VARBINARY:
165 case Types.LONGVARBINARY:
166 try {
167 parameters[i] = DatatypeConverter.parseHexBinary(value);
168 } catch (IllegalArgumentException e) {
169 parameters[i] = value;
170 }
171 break;
172
173 case Types.BLOB:
174 try {
175 parameters[i] = DatatypeConverter.parseHexBinary(value);
176 } catch (IllegalArgumentException e) {
177 LOG.warn("Error decoding hex string to specify a blob parameter", e);
178 parameters[i] = value;
179 } catch (Exception e) {
180 LOG.warn("Error creating a new blob parameter", e);
181 }
182 break;
183
184 default:
185 parameters[i] = value;
186 }
187 }
188
189 return parameters;
190 }
191
192 @Override
193 public void startElement(final String uri, final String localName, final String qName, final Attributes atts)
194 throws SAXException {
195
196
197 if (rootElement.equals(qName)) {
198 return;
199 }
200 if ("fetch".equalsIgnoreCase(qName)) {
201 String value = jdbcTemplate.queryForObject(atts.getValue("query"), String.class);
202 String key = atts.getValue("key");
203 fetches.put(key, value);
204 } else {
205 StringBuilder query = new StringBuilder("INSERT INTO ").append(qName).append('(');
206
207 StringBuilder values = new StringBuilder();
208
209 for (int i = 0; i < atts.getLength(); i++) {
210 query.append(atts.getQName(i));
211 values.append('?');
212 if (i < atts.getLength() - 1) {
213 query.append(',');
214 values.append(',');
215 }
216 }
217 query.append(") VALUES (").append(values).append(')');
218
219 try {
220 jdbcTemplate.update(query.toString(), getParameters(qName, atts));
221 } catch (DataAccessException e) {
222 LOG.error("While trying to perform {} with params {}", query, getParameters(qName, atts), e);
223 if (!continueOnError) {
224 throw e;
225 }
226 }
227 }
228 }
229 }