001package com.nimbusds.infinispan.persistence.sql.transformers; 002 003 004import java.sql.Timestamp; 005import java.time.Instant; 006import java.util.Collection; 007import java.util.Date; 008 009import net.jcip.annotations.ThreadSafe; 010import net.minidev.json.parser.ParseException; 011import org.infinispan.persistence.spi.PersistenceException; 012import org.jooq.Record; 013import org.jooq.SQLDialect; 014import net.minidev.json.*; 015 016 017/** 018 * SQL field transformer. 019 */ 020@ThreadSafe 021public class SQLFieldTransformer { 022 023 024 /** 025 * The preferred collection data type. 026 */ 027 private CollectionDataType collectionDataType; 028 029 030 /** 031 * Creates a new SQL field transformer. 032 * 033 * @param sqlDialect The SQL dialect. Must be for H2, MySQL or 034 * PostgreSQL 9.5. Not {@code null}. 035 */ 036 public SQLFieldTransformer(final SQLDialect sqlDialect) { 037 038 if (SQLDialect.H2.equals(sqlDialect)) { 039 collectionDataType = CollectionDataType.ARRAY; 040 } else if (SQLDialect.MYSQL.equals(sqlDialect)) { 041 collectionDataType = CollectionDataType.JSON; 042 } else if (SQLDialect.POSTGRES_9_5.equals(sqlDialect)) { 043 collectionDataType = CollectionDataType.ARRAY; 044 } else { 045 throw new IllegalArgumentException("Unsupported SQL dialect: " + sqlDialect); 046 } 047 } 048 049 050 /** 051 * Returns the string representation of the specified object. 052 * 053 * @param o The object, {@code null} if not specified. 054 * 055 * @return The string representation, {@code null} if not specified. 056 */ 057 public static String toString(final Object o) { 058 059 if (o == null) return null; 060 061 return o.toString(); 062 } 063 064 065 /** 066 * Returns the appropriate SQL representation of the specified 067 * collection. 068 * 069 * @param collection The collection, {@code null} if not specified. 070 * 071 * @return The SQL collection representation, {@code null} if not 072 * specified. 073 */ 074 public Object toSQLCollection(final Collection<?> collection) { 075 076 if (collection == null || collection.isEmpty()) { 077 return null; 078 } 079 080 if (CollectionDataType.ARRAY.equals(collectionDataType)) { 081 082 String[] out = new String[collection.size()]; 083 084 int i = 0; 085 for (Object item: collection) { 086 out[i++] = item.toString(); 087 } 088 089 return out; 090 091 } else { 092 // serialise to JSON array 093 JSONArray jsonArray = new JSONArray(); 094 for (Object item: collection) { 095 jsonArray.add(item.toString()); 096 } 097 return jsonArray.toJSONString(); 098 } 099 } 100 101 102 /** 103 * Returns the string representation of the specified JSON object. 104 * 105 * @param jsonObject The JSON object, {@code null} if not specified. 106 * 107 * @return The JSON object string representation, {@code null} if not 108 * specified. 109 */ 110 public static String toSQLString(final JSONObject jsonObject) { 111 112 if (jsonObject == null || jsonObject.isEmpty()) { 113 return null; 114 } 115 116 return jsonObject.toJSONString(); 117 } 118 119 120 /** 121 * Returns an SQL timestamp representation of the specified 122 * {@link Date}. 123 * 124 * @param date The date, {@code null} if not specified. 125 * 126 * @return The SQL timestamp, {@code null} if not specified. 127 */ 128 public static Timestamp toTimestamp(final Instant date) { 129 if (date == null) { 130 return null; 131 } 132 133 return new Timestamp(date.toEpochMilli()); 134 } 135 136 137 /** 138 * Parses a string array from the specified SQL record field. 139 * 140 * @param fieldName The SQL field name. Must not be {@code null}. 141 * @param sqlRecord The SQL record. Must not be {@code null}. 142 * 143 * @return The string array, {@code null} if not specified. 144 */ 145 public String[] parseSQLStringCollection(final String fieldName, final Record sqlRecord) { 146 147 if (CollectionDataType.ARRAY.equals(collectionDataType)) { 148 149 // Expect SQL array 150 return sqlRecord.get(fieldName, String[].class); 151 152 } else { 153 // Expect JSON array string 154 String s = sqlRecord.get(fieldName, String.class); 155 156 if (s == null) return null; 157 158 try { 159 JSONArray jsonArray = (JSONArray) JSONValue.parseStrict(s); 160 return jsonArray.toArray(new String[]{}); 161 } catch (ParseException | ClassCastException e) { 162 throw new PersistenceException("Couldn't parse JSON array: " + e.getMessage(), e); 163 } 164 } 165 } 166 167 168 /** 169 * Parses a JSON object from the specified string. 170 * 171 * @param jsonObjectString The JSON object string, {@code null} if not 172 * specified. 173 * 174 * @return The JSON object, {@code null} if not specified. 175 */ 176 public static JSONObject parseJSONObject(final String jsonObjectString) { 177 178 if (jsonObjectString == null) return null; 179 180 try { 181 return (JSONObject) JSONValue.parseStrict(jsonObjectString); 182 } catch (ParseException | ClassCastException e) { 183 throw new PersistenceException("Couldn't parse JSON object: " + e.getMessage(), e); 184 } 185 } 186 187 188 /** 189 * Parses a time instant from the specified SQL timestamp. 190 * 191 * @param timestamp The SQL timestamp, {@code null} if not specified. 192 * 193 * @return The time instant, {@code null} if not specified. 194 */ 195 public static Instant parseInstant(final Timestamp timestamp) { 196 197 if (timestamp == null) return null; 198 199 return Instant.ofEpochMilli(timestamp.getTime()); 200 } 201}