2 * ============LICENSE_START=======================================================
3 * ONAP : ccsdk features
4 * ================================================================================
5 * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.ccsdk.features.sdnr.wt.dataprovider.database.sqldb.database;
24 import com.fasterxml.jackson.core.JsonProcessingException;
25 import com.fasterxml.jackson.databind.JsonMappingException;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.math.BigInteger;
29 import java.sql.ResultSet;
30 import java.sql.SQLException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.List;
36 import javax.sql.rowset.CachedRowSet;
37 import org.onap.ccsdk.features.sdnr.wt.dataprovider.database.sqldb.query.filters.DBKeyValuePair;
38 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.YangToolsMapper;
39 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.YangToolsMapperHelper;
40 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.mapperextensions.YangToolsBuilderAnnotationIntrospector;
41 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.mapperextensions.YangToolsDeserializerModifier;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev201110.Entity;
44 import org.opendaylight.yangtools.concepts.Builder;
45 import org.opendaylight.yangtools.yang.binding.DataObject;
46 import org.opendaylight.yangtools.yang.binding.Enumeration;
47 import org.opendaylight.yangtools.yang.common.Uint16;
48 import org.opendaylight.yangtools.yang.common.Uint32;
49 import org.opendaylight.yangtools.yang.common.Uint64;
50 import org.opendaylight.yangtools.yang.common.Uint8;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 public class SqlDBMapper {
56 private static final Logger LOG = LoggerFactory.getLogger(SqlDBMapper.class);
58 private static final Map<Class<?>, String> mariaDBTypeMap = initTypeMap();
59 private static final String ODLID_DBTYPE = "VARCHAR(40)";
60 private static final String STRING_DBTYPE = "VARCHAR(255)";
61 private static final String ENUM_DBTYPE = "VARCHAR(100)";
62 public static final String ODLID_DBCOL = "controller-id";
63 private static List<Class<?>> numericClasses = Arrays.asList(Byte.class, Integer.class, Long.class,
64 BigInteger.class, Uint8.class, Uint16.class, Uint32.class, Uint64.class);
65 private static final YangToolsMapper mapper = new YangToolsMapper();
66 public static final String TABLENAME_CONTROLLER = "controller";
67 private static final String DEFAULTID_DBTYPE = "int(11)";
69 public static String createTableOdl() {
70 return "CREATE TABLE IF NOT EXISTS " + TABLENAME_CONTROLLER + " (" + "`id` " + ODLID_DBTYPE + " "
71 + getColumnOptions("id", ODLID_DBTYPE) + "," + "`desc` " + STRING_DBTYPE + " "
72 + getColumnOptions("description", STRING_DBTYPE) + "," + "primary key(id))";
75 public static <T> String createTable(Class<T> clazz, Entity e) throws UnableToMapClassException {
76 return createTable(clazz, e, "", false);
79 public static <T> String createTable(Class<T> clazz, Entity e, String suffix) throws UnableToMapClassException {
80 return createTable(clazz, e, suffix, false);
83 public static <T> String createTable(Class<T> clazz, Entity e, boolean autoIndex) throws UnableToMapClassException {
84 return createTable(clazz, e, "", false);
87 public static <T> String createTable(Class<T> clazz, Entity e, String suffix, boolean autoIndex)
88 throws UnableToMapClassException {
89 StringBuilder sb = new StringBuilder();
90 sb.append("CREATE TABLE IF NOT EXISTS `" + e.getName() + suffix + "` (\n");
92 sb.append("`id` " + DEFAULTID_DBTYPE + " " + getColumnOptions("id", DEFAULTID_DBTYPE) + ",\n");
94 sb.append("`id` " + STRING_DBTYPE + " " + getColumnOptions("id", STRING_DBTYPE) + ",\n");
96 sb.append("`" + ODLID_DBCOL + "` " + ODLID_DBTYPE + " " + getColumnOptions(ODLID_DBCOL, ODLID_DBTYPE) + ",\n");
97 for (Method method : getFilteredMethods(clazz, true)) {
98 Class<?> valueType = method.getReturnType();
99 String colName = getColumnName(method);
100 if (colName.equals("id")) {
103 String dbType = getDBType(valueType);
104 String options = getColumnOptions(colName, dbType);
105 sb.append("`" + colName + "` " + dbType + " " + options + ",\n");
107 sb.append("primary key(id),");
108 sb.append("foreign key(`" + ODLID_DBCOL + "`) references " + TABLENAME_CONTROLLER + "(id)");
111 return sb.toString();
114 private static String getColumnOptions(String colName, String dbType) {
115 StringBuilder options = new StringBuilder();
116 if (dbType.contains("VARCHAR")) {
117 options.append("CHARACTER SET utf8 ");
119 if (colName.equals("id") || colName.equals(ODLID_DBCOL)) {
120 if (dbType.equals(DEFAULTID_DBTYPE)) {
121 options.append("NOT NULL AUTO_INCREMENT");
123 options.append("NOT NULL");
126 return options.toString();
129 public static List<Method> getFilteredMethods(Class<?> clazz, boolean getterOrSetter) {
130 Method[] methods = clazz.getMethods();
131 List<Method> list = new ArrayList<>();
132 for (Method method : methods) {
133 if (getterOrSetter) {
134 if (!isGetter(method)) {
138 if (!isSetter(method)) {
142 if (ignoreMethod(method, methods, getterOrSetter)) {
151 private static Map<Class<?>, String> initTypeMap() {
152 Map<Class<?>, String> map = new HashMap<>();
153 map.put(String.class, STRING_DBTYPE);
154 map.put(Boolean.class, "BOOLEAN");
155 map.put(Byte.class, "TINYINT");
156 map.put(Integer.class, "INTEGER");
157 map.put(Long.class, "BIGINT");
158 map.put(BigInteger.class, "BIGINT");
159 map.put(Uint8.class, "SMALLINT");
160 map.put(Uint16.class, "INTEGER");
161 map.put(Uint32.class, "BIGINT");
162 map.put(Uint64.class, "BIGINT"); //????
163 map.put(DateAndTime.class, "DATETIME(3)");
167 private static boolean ignoreMethod(Method method, Method[] classMehtods, boolean getterOrSetter) {
168 final String name = method.getName();
169 if (name.equals("getAugmentations") || name.equals("getImplementedInterface")
170 || name.equals("implementedInterface") || name.equals("getClass")) {
173 for (Method cm : classMehtods) {
174 if (!cm.equals(method) && cm.getName().equals(name)) {
176 return !resolveConflict(method, cm, getterOrSetter);
179 if (method.getReturnType().equals(Boolean.class) && getterOrSetter) {
180 if (name.startsWith("get") && cm.getName().startsWith("is")
181 && cm.getName().endsWith(name.substring(3))) {
189 private static boolean resolveConflict(Method m1, Method m2, boolean getterOrSetter) {
190 Class<?> p1 = getterOrSetter ? m1.getReturnType() : m1.getParameterTypes()[0];
191 Class<?> p2 = getterOrSetter ? m2.getReturnType() : m2.getParameterTypes()[0];
192 if (YangToolsBuilderAnnotationIntrospector.isAssignable(p1, p2, Map.class, List.class)) {
193 return p1.isAssignableFrom(List.class); //prefer List setter
194 } else if (YangToolsBuilderAnnotationIntrospector.isAssignable(p1, p2, Uint64.class, BigInteger.class)) {
195 return p1.isAssignableFrom(Uint64.class);
196 } else if (YangToolsBuilderAnnotationIntrospector.isAssignable(p1, p2, Uint32.class, Long.class)) {
197 return p1.isAssignableFrom(Uint32.class);
198 } else if (YangToolsBuilderAnnotationIntrospector.isAssignable(p1, p2, Uint16.class, Integer.class)) {
199 return p1.isAssignableFrom(Uint16.class);
200 } else if (YangToolsBuilderAnnotationIntrospector.isAssignable(p1, p2, Uint8.class, Short.class)) {
201 return p1.isAssignableFrom(Uint8.class);
206 public static String getColumnName(Method method) {
207 String camelName = (method.getName().startsWith("get") || method.getName().startsWith("set"))
208 ? method.getName().substring(3)
209 : method.getName().substring(2);
210 return convertCamelToKebabCase(camelName);
213 private static String getDBType(Class<?> valueType) throws UnableToMapClassException {
214 String type = mariaDBTypeMap.getOrDefault(valueType, null);
216 if (implementsInterface(valueType, DataObject.class) || implementsInterface(valueType, List.class)
217 || implementsInterface(valueType, Map.class)) {
220 if (implementsInterface(valueType, Enumeration.class)) {
223 throw new UnableToMapClassException("no mapping for " + valueType.getName() + " found");
228 private static boolean implementsInterface(Class<?> valueType, Class<?> iftoImpl) {
229 return iftoImpl.isAssignableFrom(valueType);
232 private static boolean isGetter(Method method) {
233 return method.getName().startsWith("get") || method.getName().startsWith("is")
234 || method.getName().startsWith("do");
237 private static boolean isSetter(Method method) {
238 return method.getName().startsWith("set");
242 * @param input string in Camel Case
243 * @return String in Kebab case Inspiration from KebabCaseStrategy class of com.fasterxml.jackson.databind with an
244 * additional condition to handle numbers as well Using QNAME would have been a more fool proof solution,
245 * however it can lead to performance problems due to usage of Java reflection
247 private static String convertCamelToKebabCase(String input) {
249 return input; // garbage in, garbage out
250 int length = input.length();
255 StringBuilder result = new StringBuilder(length + (length >> 1));
259 for (int i = 0; i < length; ++i) {
260 char ch = input.charAt(i);
261 char lc = Character.toLowerCase(ch);
263 if (lc == ch) { // lower-case letter means we can get new word
264 // but need to check for multi-letter upper-case (acronym), where assumption
265 // is that the last upper-case char is start of a new word
266 if ((upperCount > 1)) {
267 // so insert hyphen before the last character now
268 result.insert(result.length() - 1, '-');
269 } else if ((upperCount == 1) && Character.isDigit(ch) && i != length - 1) {
274 // Otherwise starts new word, unless beginning of string
275 if ((upperCount == 0) && (i > 0)) {
282 return result.toString();
285 public static class UnableToMapClassException extends Exception {
287 private static final long serialVersionUID = 1L;
289 public UnableToMapClassException(String message) {
295 public static String escape(Object o) {
296 return escape(o.toString());
299 public static String escape(String o) {
300 return o.replace("'", "\'");
303 public static boolean isComplex(Class<?> valueType) {
304 return DataObject.class.isAssignableFrom(valueType) || List.class.isAssignableFrom(valueType);
307 public static Object getNumericValue(Object value, Class<?> valueType) {
308 if (valueType.equals(Byte.class) || valueType.equals(Integer.class) || valueType.equals(Long.class)) {
311 if (valueType.equals(Uint8.class) || valueType.equals(Uint16.class) || valueType.equals(Uint32.class)
312 || valueType.equals(Uint64.class)) {
313 return ((Number) value).longValue();
318 public static Object bool2int(Object invoke) {
319 return Boolean.TRUE.equals(invoke) ? 1 : 0;
322 public static boolean isBoolean(Class<?> valueType) {
323 return valueType.equals(Boolean.class);
326 public static boolean isNumeric(Class<?> valueType) {
327 return numericClasses.contains(valueType);
331 private static boolean isDateTime(Class<?> valueType) {
332 return valueType.equals(DateAndTime.class);
335 private static boolean isYangEnum(Class<?> valueType) {
336 return YangToolsMapperHelper.implementsInterface(valueType, Enumeration.class);
339 public static <T extends DataObject> List<T> read(CachedRowSet data, Class<T> clazz)
340 throws JsonMappingException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
341 InstantiationException, SecurityException, NoSuchMethodException, JsonProcessingException, SQLException {
342 return read(data, clazz, null);
345 public static <T extends DataObject> List<T> read(ResultSet data, Class<T> clazz)
346 throws JsonMappingException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
347 InstantiationException, SecurityException, NoSuchMethodException, JsonProcessingException, SQLException {
348 return read(data, clazz, null);
351 @SuppressWarnings("unchecked")
352 public static <T> List<T> read(CachedRowSet data, Class<T> clazz, String column) throws IllegalAccessException,
353 IllegalArgumentException, InvocationTargetException, SQLException, InstantiationException,
354 SecurityException, NoSuchMethodException, JsonMappingException, JsonProcessingException {
356 List<T> list = new ArrayList<>();
357 while (data.next()) {
358 if (column == null) {
359 Builder<T> builder = findPOJOBuilder(clazz);
362 for (Method m : getFilteredMethods(builder.getClass(), false)) {
363 argType = m.getParameterTypes()[0];
364 col = getColumnName(m);
365 m.setAccessible(true);
366 m.invoke(builder, getValueOrDefault(data, col, argType, null));
368 list.add(builder.build());
370 Object value = getValueOrDefault(data, column, clazz, null);
379 @SuppressWarnings("unchecked")
380 public static <T> List<T> read(ResultSet data, Class<T> clazz, String column) throws IllegalAccessException,
381 IllegalArgumentException, InvocationTargetException, SQLException, InstantiationException,
382 SecurityException, NoSuchMethodException, JsonMappingException, JsonProcessingException {
384 List<T> list = new ArrayList<>();
385 while (data.next()) {
386 if (column == null) {
387 Builder<T> builder = findPOJOBuilder(clazz);
390 for (Method m : getFilteredMethods(builder.getClass(), false)) {
391 argType = m.getParameterTypes()[0];
392 col = getColumnName(m);
393 m.setAccessible(true);
394 m.invoke(builder, getValueOrDefault(data, col, argType, null));
396 list.add(builder.build());
398 Object value = getValueOrDefault(data, column, clazz, null);
407 @SuppressWarnings("unchecked")
408 private static <T> Builder<T> findPOJOBuilder(Class<T> ac) throws InstantiationException, IllegalAccessException,
409 IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchMethodException {
411 String builder = null;
413 if (ac.isInterface()) {
414 String clsName = ac.getName();
415 if (clsName.endsWith("Entity")) {
416 clsName = clsName.substring(0, clsName.length() - 6);
418 builder = clsName + "Builder";
420 if (builder != null) {
421 Class<?> innerBuilder = YangToolsMapperHelper.findClass(builder);
422 Class<Builder<T>> builderClass = (Class<Builder<T>>) innerBuilder;
423 return builderClass.getDeclaredConstructor().newInstance();
425 } catch (ClassNotFoundException e) {
431 private static Object getValueOrDefault(CachedRowSet data, String col, Class<?> dstType, Object defaultValue)
432 throws SQLException, JsonMappingException, JsonProcessingException {
433 if (isBoolean(dstType)) {
434 return data.getBoolean(col);
435 } else if (isNumeric(dstType)) {
436 return getNumeric(dstType, data.getLong(col));
437 } else if (String.class.equals(dstType)) {
438 return data.getString(col);
439 } else if (isYangEnum(dstType)) {
440 return getYangEnum(data.getString(col), dstType);
441 } else if (isDateTime(dstType)) {
442 String v = data.getString(col);
443 return v == null || v.equals("null") ? null : DateAndTime.getDefaultInstance(v.replace(" ", "T") + "Z");
444 } else if (isComplex(dstType)) {
445 String v = data.getString(col);
447 return (v == null || v.toLowerCase().equals("null")) ? null : mapper.readValue(v, dstType);
452 private static Object getValueOrDefault(ResultSet data, String col, Class<?> dstType, Object defaultValue)
453 throws SQLException, JsonMappingException, JsonProcessingException {
454 if (isBoolean(dstType)) {
455 return data.getBoolean(col);
456 } else if (isNumeric(dstType)) {
457 return getNumeric(dstType, data.getLong(col));
458 } else if (String.class.equals(dstType)) {
459 return data.getString(col);
460 } else if (isYangEnum(dstType)) {
461 return getYangEnum(data.getString(col), dstType);
462 } else if (isDateTime(dstType)) {
463 String v = data.getString(col);
464 return v == null || v.equals("null") ? null : DateAndTime.getDefaultInstance(v.replace(" ", "T") + "Z");
465 } else if (isComplex(dstType)) {
466 String v = data.getString(col);
468 return (v == null || v.toLowerCase().equals("null")) ? null : mapper.readValue(v, dstType);
475 private static Object getYangEnum(String value, Class<?> dstType) {
476 if (value == null || value.equals("null")) {
480 return YangToolsDeserializerModifier.parseEnum(value, dstType);
481 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
482 | SecurityException e) {
483 LOG.warn("unable to parse enum value '{}' to class {}: ", value, dstType, e);
488 private static Object getNumeric(Class<?> dstType, long value) {
489 if (dstType.equals(Uint64.class)) {
490 return Uint64.valueOf(value);
491 } else if (dstType.equals(Uint32.class)) {
492 return Uint32.valueOf(value);
493 } else if (dstType.equals(Uint16.class)) {
494 return Uint16.valueOf(value);
495 } else if (dstType.equals(Uint16.class)) {
496 return Uint8.valueOf(value);
497 } else if (dstType.equals(Long.class)) {
498 return Long.valueOf(value);
499 } else if (dstType.equals(Integer.class)) {
500 return Long.valueOf(value).intValue();
501 } else if (dstType.equals(Byte.class)) {
502 return Long.valueOf(value).byteValue();
507 public static DBKeyValuePair<String> getEscapedKeyValue(Method m, String col, Object value)
508 throws JsonProcessingException {
509 Class<?> valueType = m.getReturnType();
510 String svalue = null;
511 if (isBoolean(valueType)) {
512 svalue = String.valueOf(bool2int(value));
513 } else if (isNumeric(valueType)) {
514 svalue = String.valueOf(getNumericValue(value, valueType));
515 } else if (isDateTime(valueType)) {
516 svalue = "'" + getDateTimeValue((DateAndTime) value) + "'";
517 } else if (isComplex(valueType)) {
518 svalue = "'" + escape(mapper.writeValueAsString(value)) + "'";
520 svalue = "'" + escape(value) + "'";
522 return new DBKeyValuePair<String>("`" + col + "`", svalue);
525 private static String getDateTimeValue(DateAndTime value) {
526 String s = value.getValue();
527 if (s.endsWith("Z")) {
528 s = s.substring(0, s.length() - 1).replace("T", " ");
529 } else if (s.contains("+")) {
530 s = s.substring(0, s.indexOf("+")).replace("T", " ");