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}