1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.caches.ignite;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.ObjectInputStream;
23 import java.io.ObjectOutputStream;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.util.Properties;
27 import java.util.concurrent.locks.ReadWriteLock;
28
29 import org.apache.ibatis.cache.Cache;
30 import org.apache.ibatis.logging.Log;
31 import org.apache.ibatis.logging.LogFactory;
32 import org.apache.ignite.catalog.ColumnType;
33 import org.apache.ignite.catalog.definitions.ColumnDefinition;
34 import org.apache.ignite.catalog.definitions.TableDefinition;
35 import org.apache.ignite.client.IgniteClient;
36 import org.apache.ignite.sql.ResultSet;
37 import org.apache.ignite.sql.SqlRow;
38 import org.apache.ignite.table.KeyValueView;
39 import org.apache.ignite.table.Tuple;
40
41
42
43
44
45
46
47 public final class IgniteCacheAdapter implements Cache {
48
49
50 private static final Log log = LogFactory.getLog(IgniteCacheAdapter.class);
51
52
53 private final String id;
54
55
56 private final String tableName;
57
58
59
60
61 private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
62
63
64 private final IgniteClient client;
65
66
67 private final KeyValueView<Tuple, Tuple> cache;
68
69
70 static final String DEFAULT_ADDRESSES = "127.0.0.1:10800";
71
72
73 static final String CFG_PATH = "config/default-config.properties";
74
75
76 static final String KEY_COL = "key";
77
78
79 static final String VAL_COL = "val";
80
81
82 private static class ClientHolder {
83 private static final IgniteClient INSTANCE = createIgniteClient();
84 }
85
86
87
88
89 private static IgniteClient getOrCreateIgniteClient() {
90 return ClientHolder.INSTANCE;
91 }
92
93
94
95
96 static IgniteClient createIgniteClient() {
97 String addresses = DEFAULT_ADDRESSES;
98 Properties props = new Properties();
99 try (InputStream is = Files.newInputStream(Path.of(CFG_PATH))) {
100 props.load(is);
101 addresses = props.getProperty("ignite.addresses", DEFAULT_ADDRESSES);
102 } catch (IOException e) {
103 log.debug("Ignite config file not found at '" + CFG_PATH + "', using defaults.");
104 log.trace("" + e);
105 }
106 return IgniteClient.builder().addresses(addresses.split(",")).build();
107 }
108
109
110
111
112
113
114
115 public IgniteCacheAdapter(String id) {
116 this(requireNonNullId(id), getOrCreateIgniteClient());
117 }
118
119 private static String requireNonNullId(String id) {
120 if (id == null) {
121 throw new IllegalArgumentException("Cache instances require an ID");
122 }
123 return id;
124 }
125
126
127
128
129
130
131
132
133
134
135 IgniteCacheAdapter(String id, IgniteClient igniteClient) {
136 this.id = requireNonNullId(id);
137 this.tableName = toTableName(id);
138 this.client = igniteClient;
139
140 igniteClient.catalog().createTable(
141 TableDefinition.builder(tableName).ifNotExists().columns(ColumnDefinition.column(KEY_COL, ColumnType.VARBINARY),
142 ColumnDefinition.column(VAL_COL, ColumnType.VARBINARY)).primaryKey(KEY_COL).build());
143
144 cache = igniteClient.tables().table(tableName).keyValueView();
145 }
146
147 @Override
148 public String getId() {
149 return this.id;
150 }
151
152 @Override
153 public void putObject(Object key, Object value) {
154 cache.put(null, Tuple.create().set(KEY_COL, serialize(key)), Tuple.create().set(VAL_COL, serialize(value)));
155 }
156
157 @Override
158 public Object getObject(Object key) {
159 Tuple valueTuple = cache.get(null, Tuple.create().set(KEY_COL, serialize(key)));
160 return valueTuple != null ? deserialize(valueTuple.bytesValue(VAL_COL)) : null;
161 }
162
163 @Override
164 public Object removeObject(Object key) {
165 Tuple valueTuple = cache.getAndRemove(null, Tuple.create().set(KEY_COL, serialize(key)));
166 return valueTuple != null ? deserialize(valueTuple.bytesValue(VAL_COL)) : null;
167 }
168
169 @Override
170 public void clear() {
171 cache.removeAll(null);
172 }
173
174 @Override
175 public int getSize() {
176 try (ResultSet<SqlRow> rs = client.sql().execute(null, "SELECT COUNT(*) FROM " + tableName)) {
177 return rs.hasNext() ? (int) rs.next().longValue(0) : 0;
178 }
179 }
180
181 @Override
182 public ReadWriteLock getReadWriteLock() {
183 return readWriteLock;
184 }
185
186 static String toTableName(String id) {
187
188 return id.replaceAll("[^a-zA-Z0-9_]", "_").toUpperCase();
189 }
190
191 static byte[] serialize(Object obj) {
192 try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
193 ObjectOutputStream oos = new ObjectOutputStream(baos)) {
194 oos.writeObject(obj);
195 return baos.toByteArray();
196 } catch (IOException e) {
197 throw new IllegalArgumentException("Cannot serialize object of type " + obj.getClass().getName(), e);
198 }
199 }
200
201 static Object deserialize(byte[] bytes) {
202 if (bytes == null) {
203 return null;
204 }
205 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
206 ObjectInputStream ois = new ObjectInputStream(bais)) {
207 return ois.readObject();
208 } catch (IOException | ClassNotFoundException e) {
209 throw new IllegalStateException("Cannot deserialize cache object", e);
210 }
211 }
212 }