4346 |
10 Feb 17 |
nicklas |
1 |
package net.sf.basedb.reggie.plugins.release; |
4346 |
10 Feb 17 |
nicklas |
2 |
|
4346 |
10 Feb 17 |
nicklas |
3 |
import java.io.IOException; |
4346 |
10 Feb 17 |
nicklas |
4 |
import java.io.OutputStreamWriter; |
4346 |
10 Feb 17 |
nicklas |
5 |
import java.io.Writer; |
4346 |
10 Feb 17 |
nicklas |
6 |
import java.nio.charset.Charset; |
4371 |
02 Mar 17 |
nicklas |
7 |
import java.util.ArrayList; |
7034 |
10 Feb 23 |
nicklas |
8 |
import java.util.Comparator; |
4365 |
27 Feb 17 |
nicklas |
9 |
import java.util.List; |
4466 |
25 Apr 17 |
nicklas |
10 |
import java.util.Map; |
7035 |
10 Feb 23 |
nicklas |
11 |
import java.util.Queue; |
7035 |
10 Feb 23 |
nicklas |
12 |
import java.util.concurrent.ConcurrentLinkedQueue; |
4346 |
10 Feb 17 |
nicklas |
13 |
|
4346 |
10 Feb 17 |
nicklas |
14 |
import org.json.simple.JSONArray; |
4346 |
10 Feb 17 |
nicklas |
15 |
import org.json.simple.JSONObject; |
4346 |
10 Feb 17 |
nicklas |
16 |
|
5089 |
14 Nov 18 |
nicklas |
17 |
import net.sf.basedb.core.BioSource; |
4346 |
10 Feb 17 |
nicklas |
18 |
import net.sf.basedb.core.DbControl; |
4365 |
27 Feb 17 |
nicklas |
19 |
import net.sf.basedb.core.ItemList; |
4346 |
10 Feb 17 |
nicklas |
20 |
import net.sf.basedb.core.plugin.ExportOutputStream; |
5091 |
14 Nov 18 |
nicklas |
21 |
import net.sf.basedb.reggie.json.FilteredJSONArray; |
4346 |
10 Feb 17 |
nicklas |
22 |
import net.sf.basedb.util.FileUtil; |
5091 |
14 Nov 18 |
nicklas |
23 |
import net.sf.basedb.util.filter.NotNullFilter; |
4346 |
10 Feb 17 |
nicklas |
24 |
|
4346 |
10 Feb 17 |
nicklas |
25 |
/** |
4346 |
10 Feb 17 |
nicklas |
A special writer implementation that writes a single JSON data file |
4346 |
10 Feb 17 |
nicklas |
for each cohort item. |
4346 |
10 Feb 17 |
nicklas |
28 |
|
4346 |
10 Feb 17 |
nicklas |
@author nicklas |
4346 |
10 Feb 17 |
nicklas |
@since 4.10 |
4346 |
10 Feb 17 |
nicklas |
31 |
*/ |
4346 |
10 Feb 17 |
nicklas |
32 |
public class JsonWriter |
4346 |
10 Feb 17 |
nicklas |
33 |
{ |
4346 |
10 Feb 17 |
nicklas |
34 |
|
5089 |
14 Nov 18 |
nicklas |
35 |
private final ReleaseWriterOptions options; |
4346 |
10 Feb 17 |
nicklas |
36 |
private final OutputLocation location; |
4371 |
02 Mar 17 |
nicklas |
37 |
private final List<CohortWriter> itemWriters; |
4382 |
07 Mar 17 |
nicklas |
38 |
private final JSONArray jsonFiles; |
7035 |
10 Feb 23 |
nicklas |
39 |
private final Queue<QueuedFile> fileQueue; |
4346 |
10 Feb 17 |
nicklas |
40 |
|
4346 |
10 Feb 17 |
nicklas |
41 |
public JsonWriter(DbControl dc, OutputLocation location, ReleaseWriterOptions options) |
4346 |
10 Feb 17 |
nicklas |
42 |
{ |
5089 |
14 Nov 18 |
nicklas |
43 |
this.options = options; |
4346 |
10 Feb 17 |
nicklas |
44 |
this.location = location; |
4371 |
02 Mar 17 |
nicklas |
45 |
this.itemWriters = new ArrayList<>(); |
4382 |
07 Mar 17 |
nicklas |
46 |
this.jsonFiles = new JSONArray(); |
7035 |
10 Feb 23 |
nicklas |
47 |
this.fileQueue = new ConcurrentLinkedQueue<>(); |
4346 |
10 Feb 17 |
nicklas |
48 |
} |
4346 |
10 Feb 17 |
nicklas |
49 |
|
4371 |
02 Mar 17 |
nicklas |
50 |
/** |
4371 |
02 Mar 17 |
nicklas |
Register the given cohort writer with this JSON writer. |
4371 |
02 Mar 17 |
nicklas |
52 |
*/ |
4371 |
02 Mar 17 |
nicklas |
53 |
public void registerItemWriter(CohortWriter cw) |
4371 |
02 Mar 17 |
nicklas |
54 |
{ |
4371 |
02 Mar 17 |
nicklas |
55 |
itemWriters.add(cw); |
4371 |
02 Mar 17 |
nicklas |
56 |
} |
4371 |
02 Mar 17 |
nicklas |
57 |
|
5089 |
14 Nov 18 |
nicklas |
58 |
public void writeIndex(ItemList list, List<BioSource> biosources) |
4365 |
27 Feb 17 |
nicklas |
59 |
{ |
4365 |
27 Feb 17 |
nicklas |
60 |
ExportOutputStream out = null; |
4365 |
27 Feb 17 |
nicklas |
61 |
try |
4365 |
27 Feb 17 |
nicklas |
62 |
{ |
4427 |
27 Mar 17 |
nicklas |
63 |
out = location.getOutputStream("/index.json", false); |
4365 |
27 Feb 17 |
nicklas |
64 |
out.setCharacterSet("UTF-8"); |
4365 |
27 Feb 17 |
nicklas |
65 |
out.setMimeType("application/json"); |
4365 |
27 Feb 17 |
nicklas |
66 |
|
4365 |
27 Feb 17 |
nicklas |
67 |
Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8")); |
4420 |
23 Mar 17 |
nicklas |
68 |
JSONObject json = new JSONObject(); |
4420 |
23 Mar 17 |
nicklas |
69 |
|
4420 |
23 Mar 17 |
nicklas |
70 |
JSONObject jsonRelease = new JSONObject(); |
5089 |
14 Nov 18 |
nicklas |
71 |
jsonRelease.put("version", options.getVersion()); |
5089 |
14 Nov 18 |
nicklas |
72 |
jsonRelease.put("useExternalId", true); |
4420 |
23 Mar 17 |
nicklas |
73 |
json.put("release", jsonRelease); |
4420 |
23 Mar 17 |
nicklas |
74 |
|
4365 |
27 Feb 17 |
nicklas |
75 |
JSONObject jsonList = new JSONObject(); |
4365 |
27 Feb 17 |
nicklas |
76 |
jsonList.put("name", list.getName()); |
4365 |
27 Feb 17 |
nicklas |
77 |
jsonList.put("size", list.getSize()); |
4365 |
27 Feb 17 |
nicklas |
78 |
json.put("list", jsonList); |
4378 |
03 Mar 17 |
nicklas |
79 |
json.writeJSONString(writer); |
4365 |
27 Feb 17 |
nicklas |
80 |
|
4378 |
03 Mar 17 |
nicklas |
81 |
writer.flush(); |
4378 |
03 Mar 17 |
nicklas |
82 |
out.flush(); |
4378 |
03 Mar 17 |
nicklas |
83 |
writer.close(); |
4378 |
03 Mar 17 |
nicklas |
84 |
} |
4378 |
03 Mar 17 |
nicklas |
85 |
catch (IOException ex) |
4378 |
03 Mar 17 |
nicklas |
86 |
{ |
4378 |
03 Mar 17 |
nicklas |
87 |
throw new RuntimeException(ex); |
4378 |
03 Mar 17 |
nicklas |
88 |
} |
4378 |
03 Mar 17 |
nicklas |
89 |
finally |
4378 |
03 Mar 17 |
nicklas |
90 |
{ |
4378 |
03 Mar 17 |
nicklas |
91 |
FileUtil.close(out); |
4378 |
03 Mar 17 |
nicklas |
92 |
} |
4378 |
03 Mar 17 |
nicklas |
93 |
} |
4378 |
03 Mar 17 |
nicklas |
94 |
|
4408 |
20 Mar 17 |
nicklas |
95 |
public void writeTypeDefs() |
4378 |
03 Mar 17 |
nicklas |
96 |
{ |
4378 |
03 Mar 17 |
nicklas |
97 |
ExportOutputStream out = null; |
4378 |
03 Mar 17 |
nicklas |
98 |
try |
4378 |
03 Mar 17 |
nicklas |
99 |
{ |
4427 |
27 Mar 17 |
nicklas |
100 |
out = location.getOutputStream("/typedefs.json", false); |
4378 |
03 Mar 17 |
nicklas |
101 |
out.setCharacterSet("UTF-8"); |
4378 |
03 Mar 17 |
nicklas |
102 |
out.setMimeType("application/json"); |
4378 |
03 Mar 17 |
nicklas |
103 |
|
4378 |
03 Mar 17 |
nicklas |
104 |
Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8")); |
4378 |
03 Mar 17 |
nicklas |
105 |
JSONArray json = new JSONArray(); |
4378 |
03 Mar 17 |
nicklas |
106 |
|
4378 |
03 Mar 17 |
nicklas |
107 |
for (CohortWriter cw : itemWriters) |
4378 |
03 Mar 17 |
nicklas |
108 |
{ |
4469 |
26 Apr 17 |
nicklas |
109 |
for (CohortTypeDef cdef : cw.getTypeDefsInJSON()) |
4378 |
03 Mar 17 |
nicklas |
110 |
{ |
4469 |
26 Apr 17 |
nicklas |
111 |
json.add(cdef.toJSONObject()); |
4378 |
03 Mar 17 |
nicklas |
112 |
} |
4378 |
03 Mar 17 |
nicklas |
113 |
} |
4378 |
03 Mar 17 |
nicklas |
114 |
|
4365 |
27 Feb 17 |
nicklas |
115 |
json.writeJSONString(writer); |
4365 |
27 Feb 17 |
nicklas |
116 |
|
4365 |
27 Feb 17 |
nicklas |
117 |
writer.flush(); |
4365 |
27 Feb 17 |
nicklas |
118 |
out.flush(); |
4365 |
27 Feb 17 |
nicklas |
119 |
writer.close(); |
4365 |
27 Feb 17 |
nicklas |
120 |
} |
4365 |
27 Feb 17 |
nicklas |
121 |
catch (IOException ex) |
4365 |
27 Feb 17 |
nicklas |
122 |
{ |
4365 |
27 Feb 17 |
nicklas |
123 |
throw new RuntimeException(ex); |
4365 |
27 Feb 17 |
nicklas |
124 |
} |
4365 |
27 Feb 17 |
nicklas |
125 |
finally |
4365 |
27 Feb 17 |
nicklas |
126 |
{ |
4365 |
27 Feb 17 |
nicklas |
127 |
FileUtil.close(out); |
4378 |
03 Mar 17 |
nicklas |
128 |
} |
4365 |
27 Feb 17 |
nicklas |
129 |
} |
4378 |
03 Mar 17 |
nicklas |
130 |
|
4552 |
04 Jul 17 |
nicklas |
131 |
public void writeJsonData(CohortItem item) |
4552 |
04 Jul 17 |
nicklas |
132 |
{ |
7022 |
06 Feb 23 |
nicklas |
// This method may be called by multiple threads |
7022 |
06 Feb 23 |
nicklas |
// The CohortWriter implementations are assumed to be thread-safe |
7035 |
10 Feb 23 |
nicklas |
135 |
QueuedFile file = new QueuedFile(item.getName()); |
4371 |
02 Mar 17 |
nicklas |
136 |
for (CohortWriter cw : itemWriters) |
4371 |
02 Mar 17 |
nicklas |
137 |
{ |
7035 |
10 Feb 23 |
nicklas |
138 |
file.json.addAll(cw.toJSONObjects(item)); |
4371 |
02 Mar 17 |
nicklas |
139 |
} |
7035 |
10 Feb 23 |
nicklas |
// Add to the file queue. The files are written by the main thread |
7035 |
10 Feb 23 |
nicklas |
// in writeQueuedJsonFiles() since the OutputLocaction implementation |
7035 |
10 Feb 23 |
nicklas |
// may not be thread-safe |
7035 |
10 Feb 23 |
nicklas |
143 |
fileQueue.add(file); |
7035 |
10 Feb 23 |
nicklas |
144 |
} |
7035 |
10 Feb 23 |
nicklas |
145 |
|
7035 |
10 Feb 23 |
nicklas |
146 |
/** |
7035 |
10 Feb 23 |
nicklas |
Write all queued file data to the output location. |
7035 |
10 Feb 23 |
nicklas |
This method is intended to be called by the main thread |
7035 |
10 Feb 23 |
nicklas |
only and will write the files that are in the queue at |
7035 |
10 Feb 23 |
nicklas |
the entry of the method. The exporter threads may add |
7035 |
10 Feb 23 |
nicklas |
more files to the queue while this method is executing |
7035 |
10 Feb 23 |
nicklas |
but those files have to wait until the next time. |
7035 |
10 Feb 23 |
nicklas |
153 |
|
7035 |
10 Feb 23 |
nicklas |
To ensure that all files are written this method should be |
7035 |
10 Feb 23 |
nicklas |
called at least once when all exporter threads have finished. |
7035 |
10 Feb 23 |
nicklas |
156 |
*/ |
7035 |
10 Feb 23 |
nicklas |
157 |
public void writeQueuedJsonFiles() |
7035 |
10 Feb 23 |
nicklas |
158 |
{ |
7035 |
10 Feb 23 |
nicklas |
159 |
int maxToWrite = fileQueue.size(); |
7035 |
10 Feb 23 |
nicklas |
160 |
int numWritten = 0; |
7035 |
10 Feb 23 |
nicklas |
161 |
while (numWritten < maxToWrite) |
4346 |
10 Feb 17 |
nicklas |
162 |
{ |
7035 |
10 Feb 23 |
nicklas |
163 |
QueuedFile file = fileQueue.poll(); |
7035 |
10 Feb 23 |
nicklas |
164 |
if (file == null) break; |
7035 |
10 Feb 23 |
nicklas |
165 |
|
7022 |
06 Feb 23 |
nicklas |
166 |
ExportOutputStream out = null; |
7022 |
06 Feb 23 |
nicklas |
167 |
try |
7022 |
06 Feb 23 |
nicklas |
168 |
{ |
7035 |
10 Feb 23 |
nicklas |
169 |
out = location.getOutputStream(file.filename, false); |
7022 |
06 Feb 23 |
nicklas |
170 |
out.setCharacterSet("UTF-8"); |
7022 |
06 Feb 23 |
nicklas |
171 |
out.setMimeType("application/json"); |
7022 |
06 Feb 23 |
nicklas |
172 |
|
7022 |
06 Feb 23 |
nicklas |
173 |
Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8")); |
7035 |
10 Feb 23 |
nicklas |
174 |
file.json.writeJSONString(writer); |
7022 |
06 Feb 23 |
nicklas |
175 |
writer.flush(); |
7022 |
06 Feb 23 |
nicklas |
176 |
out.flush(); |
7022 |
06 Feb 23 |
nicklas |
177 |
writer.close(); |
7035 |
10 Feb 23 |
nicklas |
178 |
jsonFiles.add(file.filename); |
7035 |
10 Feb 23 |
nicklas |
179 |
numWritten++; |
7022 |
06 Feb 23 |
nicklas |
180 |
} |
7022 |
06 Feb 23 |
nicklas |
181 |
catch (IOException ex) |
7022 |
06 Feb 23 |
nicklas |
182 |
{ |
7022 |
06 Feb 23 |
nicklas |
183 |
throw new RuntimeException(ex); |
7022 |
06 Feb 23 |
nicklas |
184 |
} |
7022 |
06 Feb 23 |
nicklas |
185 |
finally |
7022 |
06 Feb 23 |
nicklas |
186 |
{ |
7022 |
06 Feb 23 |
nicklas |
187 |
FileUtil.close(out); |
7022 |
06 Feb 23 |
nicklas |
188 |
} |
4346 |
10 Feb 17 |
nicklas |
189 |
} |
4346 |
10 Feb 17 |
nicklas |
190 |
} |
4346 |
10 Feb 17 |
nicklas |
191 |
|
7034 |
10 Feb 23 |
nicklas |
192 |
@SuppressWarnings({ "unchecked", "rawtypes" }) |
4382 |
07 Mar 17 |
nicklas |
193 |
public void writeFiles() |
4382 |
07 Mar 17 |
nicklas |
194 |
{ |
4382 |
07 Mar 17 |
nicklas |
195 |
ExportOutputStream out = null; |
4382 |
07 Mar 17 |
nicklas |
196 |
try |
4382 |
07 Mar 17 |
nicklas |
197 |
{ |
7034 |
10 Feb 23 |
nicklas |
198 |
jsonFiles.sort((Comparator)Comparator.naturalOrder()); |
4427 |
27 Mar 17 |
nicklas |
199 |
out = location.getOutputStream("/files.json", false); |
4382 |
07 Mar 17 |
nicklas |
200 |
out.setCharacterSet("UTF-8"); |
4382 |
07 Mar 17 |
nicklas |
201 |
out.setMimeType("application/json"); |
4371 |
02 Mar 17 |
nicklas |
202 |
|
4382 |
07 Mar 17 |
nicklas |
203 |
Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8")); |
4382 |
07 Mar 17 |
nicklas |
204 |
jsonFiles.writeJSONString(writer); |
4382 |
07 Mar 17 |
nicklas |
205 |
writer.flush(); |
4382 |
07 Mar 17 |
nicklas |
206 |
out.flush(); |
4382 |
07 Mar 17 |
nicklas |
207 |
writer.close(); |
4382 |
07 Mar 17 |
nicklas |
208 |
} |
4382 |
07 Mar 17 |
nicklas |
209 |
catch (IOException ex) |
4382 |
07 Mar 17 |
nicklas |
210 |
{ |
4382 |
07 Mar 17 |
nicklas |
211 |
throw new RuntimeException(ex); |
4382 |
07 Mar 17 |
nicklas |
212 |
} |
4382 |
07 Mar 17 |
nicklas |
213 |
finally |
4382 |
07 Mar 17 |
nicklas |
214 |
{ |
4382 |
07 Mar 17 |
nicklas |
215 |
FileUtil.close(out); |
4382 |
07 Mar 17 |
nicklas |
216 |
} |
4382 |
07 Mar 17 |
nicklas |
217 |
|
4382 |
07 Mar 17 |
nicklas |
218 |
} |
4466 |
25 Apr 17 |
nicklas |
219 |
|
4466 |
25 Apr 17 |
nicklas |
220 |
/** |
4466 |
25 Apr 17 |
nicklas |
Writes all batch index proxies to a JSON file. The file contains a |
4466 |
25 Apr 17 |
nicklas |
JSON Object which maps batch names and proxies to index values. |
4466 |
25 Apr 17 |
nicklas |
223 |
|
4466 |
25 Apr 17 |
nicklas |
Example: |
4466 |
25 Apr 17 |
nicklas |
225 |
{ |
4466 |
25 Apr 17 |
nicklas |
"PartitionBatchNo": { |
4466 |
25 Apr 17 |
nicklas |
"28b6805f-1cc7-44bd-b6ff-3726a37dbeb1": 1, |
4466 |
25 Apr 17 |
nicklas |
"73c7548e-1f2f-47a3-8ffb-1e9288932b6c": 2 |
4466 |
25 Apr 17 |
nicklas |
229 |
} |
4466 |
25 Apr 17 |
nicklas |
230 |
} |
4466 |
25 Apr 17 |
nicklas |
231 |
*/ |
4466 |
25 Apr 17 |
nicklas |
232 |
public void writeBatchIndexLookupFiles(Map<String, Map<String, String>> batchIndexMap) |
4466 |
25 Apr 17 |
nicklas |
233 |
{ |
4466 |
25 Apr 17 |
nicklas |
234 |
JSONObject jsonLookups = new JSONObject(); |
4466 |
25 Apr 17 |
nicklas |
235 |
|
4466 |
25 Apr 17 |
nicklas |
236 |
for (Map.Entry<String, Map<String, String>> entry : batchIndexMap.entrySet()) |
4466 |
25 Apr 17 |
nicklas |
237 |
{ |
4466 |
25 Apr 17 |
nicklas |
238 |
String batchName = entry.getKey(); |
4466 |
25 Apr 17 |
nicklas |
239 |
Map<String, String> indexes = entry.getValue(); |
4466 |
25 Apr 17 |
nicklas |
240 |
|
4466 |
25 Apr 17 |
nicklas |
241 |
JSONObject jsonValues = new JSONObject(); |
4466 |
25 Apr 17 |
nicklas |
242 |
jsonLookups.put(batchName, jsonValues); |
4466 |
25 Apr 17 |
nicklas |
243 |
|
4466 |
25 Apr 17 |
nicklas |
244 |
int index = 1; |
4466 |
25 Apr 17 |
nicklas |
245 |
for (String proxy : indexes.values()) |
4466 |
25 Apr 17 |
nicklas |
246 |
{ |
4466 |
25 Apr 17 |
nicklas |
247 |
jsonValues.put(proxy, index); |
4466 |
25 Apr 17 |
nicklas |
248 |
index++; |
4466 |
25 Apr 17 |
nicklas |
249 |
} |
4466 |
25 Apr 17 |
nicklas |
250 |
} |
4466 |
25 Apr 17 |
nicklas |
251 |
|
4466 |
25 Apr 17 |
nicklas |
252 |
ExportOutputStream out = null; |
4466 |
25 Apr 17 |
nicklas |
253 |
try |
4466 |
25 Apr 17 |
nicklas |
254 |
{ |
4466 |
25 Apr 17 |
nicklas |
255 |
out = location.getOutputStream("/batch-index-lookup.json", false); |
4466 |
25 Apr 17 |
nicklas |
256 |
out.setCharacterSet("UTF-8"); |
4466 |
25 Apr 17 |
nicklas |
257 |
out.setMimeType("application/json"); |
4466 |
25 Apr 17 |
nicklas |
258 |
|
4466 |
25 Apr 17 |
nicklas |
259 |
Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8")); |
4466 |
25 Apr 17 |
nicklas |
260 |
jsonLookups.writeJSONString(writer); |
4466 |
25 Apr 17 |
nicklas |
261 |
writer.flush(); |
4466 |
25 Apr 17 |
nicklas |
262 |
out.flush(); |
4466 |
25 Apr 17 |
nicklas |
263 |
writer.close(); |
4466 |
25 Apr 17 |
nicklas |
264 |
} |
4466 |
25 Apr 17 |
nicklas |
265 |
catch (IOException ex) |
4466 |
25 Apr 17 |
nicklas |
266 |
{ |
4466 |
25 Apr 17 |
nicklas |
267 |
throw new RuntimeException(ex); |
4466 |
25 Apr 17 |
nicklas |
268 |
} |
4466 |
25 Apr 17 |
nicklas |
269 |
finally |
4466 |
25 Apr 17 |
nicklas |
270 |
{ |
4466 |
25 Apr 17 |
nicklas |
271 |
FileUtil.close(out); |
4466 |
25 Apr 17 |
nicklas |
272 |
} |
4466 |
25 Apr 17 |
nicklas |
273 |
|
4466 |
25 Apr 17 |
nicklas |
274 |
} |
4552 |
04 Jul 17 |
nicklas |
275 |
|
7035 |
10 Feb 23 |
nicklas |
276 |
/** |
7035 |
10 Feb 23 |
nicklas |
A file waiting to be writted to the output location. |
7035 |
10 Feb 23 |
nicklas |
278 |
*/ |
7035 |
10 Feb 23 |
nicklas |
279 |
static class QueuedFile |
7035 |
10 Feb 23 |
nicklas |
280 |
{ |
7035 |
10 Feb 23 |
nicklas |
281 |
final String filename; |
7035 |
10 Feb 23 |
nicklas |
282 |
final JSONArray json; |
7035 |
10 Feb 23 |
nicklas |
283 |
QueuedFile(String itemName) |
7035 |
10 Feb 23 |
nicklas |
284 |
{ |
7035 |
10 Feb 23 |
nicklas |
285 |
this.filename = "/json/"+itemName+".json"; |
7035 |
10 Feb 23 |
nicklas |
286 |
this.json = new FilteredJSONArray(new NotNullFilter<>(false)); |
7035 |
10 Feb 23 |
nicklas |
287 |
} |
7035 |
10 Feb 23 |
nicklas |
288 |
} |
7035 |
10 Feb 23 |
nicklas |
289 |
|
4346 |
10 Feb 17 |
nicklas |
290 |
} |