1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.prometheus.data;
18
19 import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20 import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory.GordianFactoryLock;
21 import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipFactory;
22 import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipFileContents;
23 import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipFileEntry;
24 import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipLock;
25 import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipReadFile;
26 import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipWriteFile;
27 import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
28 import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldSetDef;
29 import io.github.tonywasher.joceanus.metis.list.MetisListKey;
30 import io.github.tonywasher.joceanus.metis.toolkit.MetisToolkit;
31 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
32 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
33 import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
34 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues.PrometheusGroupedItem;
35 import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
36 import io.github.tonywasher.joceanus.prometheus.exc.PrometheusIOException;
37 import io.github.tonywasher.joceanus.prometheus.exc.PrometheusSecurityException;
38 import io.github.tonywasher.joceanus.prometheus.security.PrometheusSecurityPasswordManager;
39 import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.Node;
43 import org.xml.sax.SAXException;
44
45 import javax.xml.XMLConstants;
46 import javax.xml.parsers.DocumentBuilder;
47 import javax.xml.parsers.DocumentBuilderFactory;
48 import javax.xml.parsers.ParserConfigurationException;
49 import javax.xml.transform.Transformer;
50 import javax.xml.transform.TransformerConfigurationException;
51 import javax.xml.transform.TransformerException;
52 import javax.xml.transform.TransformerFactory;
53 import javax.xml.transform.dom.DOMSource;
54 import javax.xml.transform.stream.StreamResult;
55 import java.io.File;
56 import java.io.FileInputStream;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.io.OutputStream;
61 import java.util.Iterator;
62
63
64
65
66 public class PrometheusDataValuesFormatter {
67
68
69
70 private static final String SUFFIX_ENTRY = ".xml";
71
72
73
74
75 private static final String ERROR_BACKUP = "Failed to create backup XML";
76
77
78
79
80 private final TethysUIThreadStatusReport theReport;
81
82
83
84
85 private final PrometheusSecurityPasswordManager thePasswordMgr;
86
87
88
89
90 private final DocumentBuilder theBuilder;
91
92
93
94
95 private final Transformer theXformer;
96
97
98
99
100 private Integer theVersion;
101
102
103
104
105
106
107
108
109 public PrometheusDataValuesFormatter(final TethysUIThreadStatusReport pReport,
110 final PrometheusSecurityPasswordManager pPasswordMgr) throws PrometheusIOException {
111
112 theReport = pReport;
113 thePasswordMgr = pPasswordMgr;
114
115
116 try {
117
118 final DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
119 myFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
120 myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
121 myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
122 theBuilder = myFactory.newDocumentBuilder();
123
124
125 final TransformerFactory myXformFactory = TransformerFactory.newInstance();
126 myXformFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
127 myXformFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
128 myXformFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
129 theXformer = myXformFactory.newTransformer();
130
131 } catch (ParserConfigurationException | TransformerConfigurationException e) {
132 throw new PrometheusIOException("Failed to initialise parser", e);
133 }
134 }
135
136
137
138
139
140
141
142
143 public void createBackup(final PrometheusDataSet pData,
144 final File pFile) throws OceanusException {
145 boolean writeFailed = false;
146 try {
147 createBackup(pData, new FileOutputStream(pFile));
148 } catch (IOException
149 | OceanusException e) {
150 writeFailed = true;
151 throw new PrometheusIOException(ERROR_BACKUP, e);
152 } finally {
153
154 if (writeFailed) {
155 MetisToolkit.cleanUpFile(pFile);
156 }
157 }
158 }
159
160
161
162
163
164
165
166
167 public void createBackup(final PrometheusDataSet pData,
168 final OutputStream pZipStream) throws OceanusException {
169
170 final OceanusProfile myTask = theReport.getActiveTask();
171 final OceanusProfile myStage = myTask.startTask("Writing");
172
173
174 try {
175
176 final PrometheusSecurityPasswordManager myPasswordMgr = pData.getPasswordMgr();
177 final GordianFactoryLock myBase = pData.getFactoryLock();
178 final GordianFactoryLock myLock = myPasswordMgr.similarFactoryLock(myBase);
179 final GordianZipFactory myZips = myPasswordMgr.getSecurityFactory().getZipFactory();
180 final GordianZipLock myZipLock = myZips.zipLock(myLock);
181
182
183 theVersion = pData.getControl().getDataVersion();
184
185
186 theReport.setNumStages(pData.getListMap().size());
187
188
189 try (GordianZipWriteFile myZipFile = myZips.createZipFile(myZipLock, pZipStream)) {
190
191 final Iterator<PrometheusDataList<?>> myIterator = pData.iterator();
192 while (myIterator.hasNext()) {
193 final PrometheusDataList<?> myList = myIterator.next();
194
195
196 theReport.setNewStage(myList.listName());
197
198
199 if (myList.includeDataXML()) {
200
201 myStage.startTask(myList.listName());
202 writeXMLListToFile(myList, myZipFile, true);
203 }
204 }
205
206
207 myStage.end();
208
209 } catch (IOException
210 | OceanusException e) {
211 throw new PrometheusIOException(ERROR_BACKUP, e);
212 }
213 } catch (GordianException e) {
214 throw new PrometheusSecurityException(e);
215 }
216 }
217
218
219
220
221
222
223
224
225 public void createExtract(final PrometheusDataSet pData,
226 final File pFile) throws OceanusException {
227 boolean writeFailed = false;
228 try {
229 createExtract(pData, new FileOutputStream(pFile));
230 } catch (IOException
231 | OceanusException e) {
232 writeFailed = true;
233 throw new PrometheusIOException(ERROR_BACKUP, e);
234 } finally {
235
236 if (writeFailed) {
237 MetisToolkit.cleanUpFile(pFile);
238 }
239 }
240 }
241
242
243
244
245
246
247
248
249 public void createExtract(final PrometheusDataSet pData,
250 final OutputStream pZipStream) throws OceanusException {
251
252 final OceanusProfile myTask = theReport.getActiveTask();
253 final OceanusProfile myStage = myTask.startTask("Writing");
254
255
256 theVersion = pData.getControl().getDataVersion();
257
258
259 theReport.setNumStages(pData.getListMap().size());
260 final GordianZipFactory myZips = thePasswordMgr.getSecurityFactory().getZipFactory();
261
262
263 try (GordianZipWriteFile myZipFile = myZips.createZipFile(pZipStream)) {
264
265 final Iterator<PrometheusDataList<?>> myIterator = pData.iterator();
266 while (myIterator.hasNext()) {
267 final PrometheusDataList<?> myList = myIterator.next();
268
269
270 theReport.setNewStage(myList.listName());
271
272
273 if (myList.includeDataXML()) {
274
275 myStage.startTask(myList.listName());
276 writeXMLListToFile(myList, myZipFile, false);
277 }
278 }
279
280
281 myStage.end();
282
283 } catch (IOException
284 | OceanusException e) {
285 throw new PrometheusIOException("Failed to create extract XML", e);
286 }
287 }
288
289
290
291
292
293
294
295
296
297 private void writeXMLListToFile(final PrometheusDataList<?> pList,
298 final GordianZipWriteFile pZipFile,
299 final boolean pStoreIds) throws OceanusException {
300
301 final String myName = pList.listName() + SUFFIX_ENTRY;
302
303
304 try (OutputStream myStream = pZipFile.createOutputStream(new File(myName), true)) {
305
306 final Document myDocument = theBuilder.newDocument();
307
308
309 populateXML(myDocument, pList, pStoreIds);
310
311
312 theXformer.transform(new DOMSource(myDocument), new StreamResult(myStream));
313
314 } catch (GordianException
315 | TransformerException
316 | IOException e) {
317 throw new PrometheusIOException("Failed to transform XML", e);
318 }
319 }
320
321
322
323
324
325
326
327
328
329 private void populateXML(final Document pDocument,
330 final PrometheusDataList<?> pList,
331 final boolean pStoreIds) throws OceanusException {
332
333 final Element myElement = pDocument.createElement(pList.listName());
334 pDocument.appendChild(myElement);
335
336
337 final OceanusDataFormatter myFormatter = pList.getDataSet().getDataFormatter();
338
339
340 final int myTotal = pList.size();
341 theReport.setNumSteps(myTotal);
342
343
344 myElement.setAttribute(PrometheusDataValues.ATTR_TYPE, pList.getItemType().getItemName());
345 myElement.setAttribute(PrometheusDataValues.ATTR_SIZE, Integer.toString(myTotal));
346 myElement.setAttribute(PrometheusDataValues.ATTR_VERS, Integer.toString(theVersion));
347
348
349 final Iterator<?> myIterator = pList.iterator();
350 while (myIterator.hasNext()) {
351 final Object myObject = myIterator.next();
352
353
354 if (!(myObject instanceof PrometheusDataItem myItem)) {
355 continue;
356 }
357
358
359 if (myItem instanceof PrometheusGroupedItem myGrouped
360 && myGrouped.isChild()) {
361 continue;
362 }
363
364
365 final PrometheusDataValues myValues = new PrometheusDataValues(myItem);
366
367
368 final Element myChild = myValues.createXML(pDocument, myFormatter, pStoreIds);
369 myElement.appendChild(myChild);
370
371
372 theReport.setNextStep();
373 }
374 }
375
376
377
378
379
380
381
382
383 public void loadZipFile(final PrometheusDataSet pData,
384 final File pFile) throws OceanusException {
385 try {
386 loadZipFile(pData, new FileInputStream(pFile), pFile.getName());
387 } catch (IOException e) {
388 throw new PrometheusIOException("Failed to access ZipFile", e);
389 }
390 }
391
392
393
394
395
396
397
398
399
400 public void loadZipFile(final PrometheusDataSet pData,
401 final InputStream pInStream,
402 final String pName) throws OceanusException {
403
404 try {
405
406 final OceanusProfile myTask = theReport.getActiveTask();
407 final OceanusProfile myStage = myTask.startTask("Loading");
408 myStage.startTask("Parsing");
409
410
411 final GordianZipFactory myZips = thePasswordMgr.getSecurityFactory().getZipFactory();
412 final GordianZipReadFile myZipFile = myZips.openZipFile(pInStream);
413
414
415 final GordianZipLock myLock = myZipFile.getLock();
416
417
418 if (myLock != null) {
419
420 thePasswordMgr.resolveZipLock(myLock, pName);
421 }
422
423
424 parseZipFile(myStage, pData, myZipFile);
425
426
427 myStage.end();
428
429 } catch (GordianException e) {
430 throw new PrometheusSecurityException(e);
431 }
432 }
433
434
435
436
437
438
439
440
441
442 private void parseZipFile(final OceanusProfile pProfile,
443 final PrometheusDataSet pData,
444 final GordianZipReadFile pZipFile) throws OceanusException {
445
446 final OceanusProfile myStage = pProfile.startTask("Loading");
447
448
449 theReport.setNumStages(pData.getListMap().size());
450
451
452 final Iterator<PrometheusDataList<?>> myIterator = pData.iterator();
453 while (myIterator.hasNext()) {
454 final PrometheusDataList<?> myList = myIterator.next();
455
456
457 theReport.setNewStage(myList.listName());
458
459
460 if (myList.includeDataXML()) {
461
462 myStage.startTask(myList.listName());
463 readXMLListFromFile(myList, pZipFile);
464 }
465
466
467 myList.postProcessOnLoad();
468 }
469
470
471 pData.getControlData().addNewControl(theVersion);
472
473
474 myStage.end();
475 }
476
477
478
479
480
481
482
483
484 private void readXMLListFromFile(final PrometheusDataList<?> pList,
485 final GordianZipReadFile pZipFile) throws OceanusException {
486
487 try {
488
489 final String myName = pList.listName() + SUFFIX_ENTRY;
490
491
492 final GordianZipFileContents myContents = pZipFile.getContents();
493 final GordianZipFileEntry myEntry = myContents.findFileEntry(myName);
494 if (myEntry == null) {
495 throw new PrometheusDataException("List not found " + myName);
496 }
497
498
499 try (InputStream myStream = pZipFile.createInputStream(myEntry)) {
500
501 final Document myDocument = theBuilder.parse(myStream);
502
503
504 parseXMLDocument(myDocument, pList);
505
506 } catch (IOException
507 | SAXException e) {
508 throw new PrometheusIOException("Failed to parse XML", e);
509 }
510 } catch (GordianException e) {
511 throw new PrometheusSecurityException(e);
512 }
513 }
514
515
516
517
518
519
520
521
522 private void parseXMLDocument(final Document pDocument,
523 final PrometheusDataList<?> pList) throws OceanusException {
524
525 final Element myElement = pDocument.getDocumentElement();
526 final MetisListKey myItemType = pList.getItemType();
527
528
529 if (!MetisDataDifference.isEqual(myElement.getNodeName(), pList.listName())
530 || !MetisDataDifference.isEqual(myElement.getAttribute(PrometheusDataValues.ATTR_TYPE), myItemType.getItemName())) {
531 throw new PrometheusDataException("Invalid list type");
532 }
533
534
535 final Integer myVersion = Integer.valueOf(myElement.getAttribute(PrometheusDataValues.ATTR_VERS));
536 if (theVersion == null) {
537 theVersion = myVersion;
538 } else if (!theVersion.equals(myVersion)) {
539 throw new PrometheusDataException("Inconsistent data version");
540 }
541
542
543 final MetisFieldSetDef myFields = pList.getItemFields();
544
545
546 final OceanusDataFormatter myFormatter = pList.getDataSet().getDataFormatter();
547
548
549 final int myTotal = getListCount(myFormatter, myElement);
550 theReport.setNumSteps(myTotal);
551
552
553 for (Node myChild = myElement.getFirstChild(); myChild != null; myChild = myChild.getNextSibling()) {
554
555 if (!(myChild instanceof Element)) {
556 continue;
557 }
558
559
560 final Element myItem = (Element) myChild;
561
562
563 final PrometheusDataValues myValues = new PrometheusDataValues(myItem, myFields);
564
565
566 pList.addValuesItem(myValues);
567
568
569 theReport.setNextStep();
570 }
571 }
572
573
574
575
576
577
578
579
580
581 private static Integer getListCount(final OceanusDataFormatter pFormatter,
582 final Element pElement) throws OceanusException {
583 try {
584
585 final String mySize = pElement.getAttribute(PrometheusDataValues.ATTR_SIZE);
586 return pFormatter.parseValue(mySize, Integer.class);
587 } catch (NumberFormatException e) {
588 throw new PrometheusDataException("Invalid list count", e);
589 }
590 }
591 }