View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * SAX handler for generating SQL INSERT statements out of given XML file.
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         // skip root element
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 }