View Javadoc
1   package emissary.core;
2   
3   import emissary.core.IBaseDataObjectXmlCodecs.ElementEncoders;
4   import emissary.core.channels.InMemoryChannelFactory;
5   import emissary.core.channels.SeekableByteChannelFactory;
6   import emissary.core.constants.IbdoXmlElementNames;
7   import emissary.kff.KffDataObjectHandler;
8   import emissary.test.core.junit5.UnitTest;
9   import emissary.util.ByteUtil;
10  import emissary.util.PlaceComparisonHelper;
11  
12  import org.jdom2.Document;
13  import org.jdom2.Element;
14  import org.jdom2.input.SAXBuilder;
15  import org.jdom2.input.sax.XMLReaders;
16  import org.junit.jupiter.api.Test;
17  
18  import java.io.IOException;
19  import java.io.StringReader;
20  import java.nio.ByteBuffer;
21  import java.nio.channels.SeekableByteChannel;
22  import java.nio.charset.StandardCharsets;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  import java.util.Map.Entry;
27  import java.util.Random;
28  import java.util.TreeMap;
29  import javax.annotation.Nullable;
30  
31  import static emissary.core.IBaseDataObjectXmlCodecs.DEFAULT_ELEMENT_DECODERS;
32  import static emissary.core.IBaseDataObjectXmlCodecs.DEFAULT_ELEMENT_ENCODERS;
33  import static emissary.core.IBaseDataObjectXmlCodecs.SHA256_ELEMENT_ENCODERS;
34  import static emissary.core.IBaseDataObjectXmlCodecs.extractBytes;
35  import static org.junit.jupiter.api.Assertions.assertEquals;
36  import static org.junit.jupiter.api.Assertions.assertNull;
37  
38  class IBaseDataObjectXmlHelperTest extends UnitTest {
39      @Test
40      void testParentIbdoNoFieldsChanged() throws Exception {
41          final IBaseDataObject initialIbdo = new BaseDataObject();
42          final IBaseDataObject expectedIbdo = new BaseDataObject();
43          final List<IBaseDataObject> expectedChildren = new ArrayList<>();
44          final List<IBaseDataObject> actualChildren = new ArrayList<>();
45  
46          final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
47                  actualChildren, DEFAULT_ELEMENT_ENCODERS);
48          final String diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, actualIbdo, expectedChildren,
49                  actualChildren, "testParentIbdoNoFieldsChanged", DiffCheckConfiguration.onlyCheckData());
50  
51          assertNull(diff);
52  
53          final IBaseDataObject sha256ActualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
54                  actualChildren, SHA256_ELEMENT_ENCODERS);
55          final String sha256Diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, sha256ActualIbdo, expectedChildren,
56                  actualChildren, "testParentIbdoNoFieldsChangedSha256", DiffCheckConfiguration.onlyCheckData());
57  
58          assertNull(sha256Diff);
59      }
60  
61      private static void setAllFieldsPrintable(final IBaseDataObject ibdo, final byte[] bytes) {
62          ibdo.setData(bytes);
63          ibdo.setBirthOrder(5);
64          ibdo.setBroken("Broken1");
65          ibdo.setBroken("Broken2");
66          ibdo.setClassification("Classification");
67          ibdo.pushCurrentForm("Form1");
68          ibdo.pushCurrentForm("Form2");
69          ibdo.setFilename("Filename");
70          ibdo.setFileType("FileType");
71          ibdo.setFontEncoding("FontEncoding");
72          ibdo.setFooter("Footer".getBytes(StandardCharsets.UTF_8));
73          ibdo.setHeader("Header".getBytes(StandardCharsets.UTF_8));
74          ibdo.setHeaderEncoding("HeaderEncoding");
75          ibdo.setId("Id");
76          ibdo.setNumChildren(9);
77          ibdo.setNumSiblings(10);
78          ibdo.setOutputable(false);
79          ibdo.setPriority(1);
80          ibdo.addProcessingError("ProcessingError1");
81          ibdo.addProcessingError("ProcessingError2");
82          ibdo.setTransactionId("TransactionId");
83          ibdo.setWorkBundleId("WorkBundleId");
84          ibdo.putParameter("Parameter1Key", "Parameter1Value");
85          ibdo.putParameter("Parameter2Key", Arrays.asList("Parameter2Value1", "Parameter2Value2"));
86          ibdo.putParameter("Parameter3Key", Arrays.asList(10L, 20L));
87          ibdo.addAlternateView("AlternateView1Key", "AlternateView1Value".getBytes(StandardCharsets.UTF_8));
88          ibdo.addAlternateView("AlternateView11Key", "AlternateView11Value".getBytes(StandardCharsets.UTF_8));
89      }
90  
91      private static void setAllFieldsNonPrintable(final IBaseDataObject ibdo, final byte[] bytes) {
92          ibdo.setData(bytes);
93          ibdo.setBirthOrder(5);
94          ibdo.setBroken("\001Broken1");
95          ibdo.setBroken("\001Broken2");
96          ibdo.setClassification("\001Classification");
97          ibdo.pushCurrentForm("Form1");
98          ibdo.pushCurrentForm("Form2");
99          ibdo.setFilename("\001Filename");
100         ibdo.setFileType("\001FileType");
101         ibdo.setFontEncoding("\001FontEncoding");
102         ibdo.setFooter("\001Footer".getBytes(StandardCharsets.UTF_8));
103         ibdo.setHeader("\001Header".getBytes(StandardCharsets.UTF_8));
104         ibdo.setHeaderEncoding("\001HeaderEncoding");
105         ibdo.setId("\001Id");
106         ibdo.setNumChildren(9);
107         ibdo.setNumSiblings(10);
108         ibdo.setOutputable(false);
109         ibdo.setPriority(1);
110         ibdo.addProcessingError("\001ProcessingError1");
111         ibdo.addProcessingError("\001ProcessingError2");
112         ibdo.setTransactionId("\001TransactionId");
113         ibdo.setWorkBundleId("\001WorkBundleId");
114         ibdo.putParameter("\020Parameter1Key", "\020Parameter1Value");
115         ibdo.putParameter("\020Parameter2Key", "\020Parameter2Value");
116         ibdo.addAlternateView("\200AlternateView1Key",
117                 "\200AlternateView1Value".getBytes(StandardCharsets.UTF_8));
118         ibdo.addAlternateView("\200AlternateView11Key",
119                 "\200AlternateView11Value".getBytes(StandardCharsets.UTF_8));
120     }
121 
122     @Test
123     void testParentIbdoAllFieldsChanged() throws Exception {
124         final IBaseDataObject initialIbdo = new BaseDataObject();
125         final IBaseDataObject expectedIbdo = new BaseDataObject();
126         final List<IBaseDataObject> expectedChildren = new ArrayList<>();
127         final List<IBaseDataObject> actualChildren = new ArrayList<>();
128         final byte[] bytes = "Data".getBytes(StandardCharsets.UTF_8);
129 
130         setAllFieldsPrintable(expectedIbdo, bytes);
131 
132         final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
133                 actualChildren, DEFAULT_ELEMENT_ENCODERS);
134         final String diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, actualIbdo, expectedChildren,
135                 actualChildren, "testParentIbdoAllFieldsChanged", DiffCheckConfiguration.onlyCheckData());
136 
137         assertNull(diff);
138 
139         final IBaseDataObject sha256ActualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
140                 actualChildren, SHA256_ELEMENT_ENCODERS);
141         final String sha256Diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, sha256ActualIbdo, expectedChildren,
142                 actualChildren, "testParentIbdoAllFieldsChangedSha256", DiffCheckConfiguration.onlyCheckData());
143 
144         assertNull(sha256Diff);
145     }
146 
147     @Test
148     void testBase64Conversion() throws Exception {
149         final IBaseDataObject initialIbdo = new BaseDataObject();
150         final IBaseDataObject expectedIbdo = new BaseDataObject();
151         final List<IBaseDataObject> expectedChildren = new ArrayList<>();
152         final List<IBaseDataObject> actualChildren = new ArrayList<>();
153         final byte[] bytes = "\001Data".getBytes(StandardCharsets.UTF_8);
154 
155         setAllFieldsNonPrintable(expectedIbdo, bytes);
156 
157         final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
158                 actualChildren, DEFAULT_ELEMENT_ENCODERS);
159         final String diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, actualIbdo, expectedChildren,
160                 actualChildren, "testBase64Conversion", DiffCheckConfiguration.onlyCheckData());
161 
162         assertNull(diff);
163 
164         final IBaseDataObject sha256ActualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
165                 actualChildren, SHA256_ELEMENT_ENCODERS);
166 
167         expectedIbdo.setData(ByteUtil.sha256Bytes(bytes).getBytes(StandardCharsets.UTF_8));
168 
169         for (Entry<String, byte[]> entry : new TreeMap<>(expectedIbdo.getAlternateViews()).entrySet()) {
170             expectedIbdo.addAlternateView(entry.getKey(), ByteUtil.sha256Bytes(entry.getValue()).getBytes(StandardCharsets.UTF_8));
171         }
172 
173         final String sha256Diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, sha256ActualIbdo, expectedChildren,
174                 actualChildren, "testSha256Conversion", DiffCheckConfiguration.onlyCheckData());
175 
176         assertNull(sha256Diff);
177     }
178 
179     @Test
180     void testLengthAttributeDefault() throws Exception {
181         final IBaseDataObject ibdo = new BaseDataObject();
182         final List<IBaseDataObject> children = new ArrayList<>();
183         final byte[] bytes = "Data".getBytes(StandardCharsets.UTF_8);
184 
185         setAllFieldsPrintable(ibdo, bytes);
186 
187         final String xmlString = IBaseDataObjectXmlHelper.xmlFromIbdo(ibdo, children, ibdo, DEFAULT_ELEMENT_ENCODERS);
188 
189         final SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
190         final Document document = builder.build(new StringReader(xmlString));
191         final Element root = document.getRootElement();
192 
193         assertNull(checkLengthElement(root, (int) ibdo.getChannelSize(), "answers", "data"));
194         assertNull(checkLengthElement(root, ibdo.footer().length, "answers", "footer"));
195         assertNull(checkLengthElement(root, ibdo.header().length, "answers", "header"));
196         assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("AlternateView1Key").length, "AlternateView1Key", "answers", "view"));
197         assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("AlternateView11Key").length, "AlternateView11Key", "answers", "view"));
198     }
199 
200     @Test
201     void testLengthAttributeHash() throws Exception {
202         final IBaseDataObject ibdo = new BaseDataObject();
203         final List<IBaseDataObject> children = new ArrayList<>();
204         final byte[] bytes = "Data".getBytes(StandardCharsets.UTF_8);
205 
206         setAllFieldsNonPrintable(ibdo, bytes);
207 
208         final String xmlString = IBaseDataObjectXmlHelper.xmlFromIbdo(ibdo, children, ibdo, DEFAULT_ELEMENT_ENCODERS);
209 
210         final SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
211         final Document document = builder.build(new StringReader(xmlString));
212         final Element root = document.getRootElement();
213 
214         assertNull(checkLengthElement(root, (int) ibdo.getChannelSize(), "answers", "data"));
215         assertNull(checkLengthElement(root, ibdo.footer().length, "answers", "footer"));
216         assertNull(checkLengthElement(root, ibdo.header().length, "answers", "header"));
217         assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("\200AlternateView1Key").length, "\200AlternateView1Key", "answers", "view"));
218         assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("\200AlternateView11Key").length, "\200AlternateView11Key", "answers", "view"));
219     }
220 
221     @Nullable
222     private static String checkLengthElement(Element rootElement, int lengthToCheck, String... xmlPathElementNames) {
223         Element element = rootElement;
224 
225         for (int i = 0; i < xmlPathElementNames.length; i++) {
226             final String xmlPathElementName = xmlPathElementNames[i];
227             final List<Element> elements = element.getChildren(xmlPathElementName);
228 
229             if (elements == null || elements.size() != 1) {
230                 return "Cound not find element " + xmlPathElementName;
231             }
232 
233             element = elements.get(0);
234         }
235 
236         String lengthElement = element.getAttributeValue(IBaseDataObjectXmlCodecs.LENGTH_ATTRIBUTE_NAME);
237 
238         if (lengthElement == null) {
239             return "Could not find length element";
240         }
241 
242         try {
243             long length = Long.parseLong(lengthElement);
244 
245             if (length == lengthToCheck) {
246                 return null;
247             } else {
248                 return "Looking for length " + lengthToCheck + ", but found " + length;
249             }
250         } catch (NumberFormatException e) {
251             return "NumberFormatException: " + lengthElement;
252         }
253     }
254 
255     @Nullable
256     private static String checkLengthKeyValue(Element rootElement, int lengthToCheck, String key, String... xmlPathElementNames) {
257         Element element = rootElement;
258 
259         for (int i = 0; i < xmlPathElementNames.length - 1; i++) {
260             final String xmlPathElementName = xmlPathElementNames[i];
261             final List<Element> elements = element.getChildren(xmlPathElementName);
262 
263             if (elements == null || elements.isEmpty()) {
264                 return "Cound not find element " + xmlPathElementName;
265             }
266 
267             element = elements.get(0);
268         }
269 
270         List<Element> keyValueElements = element.getChildren(xmlPathElementNames[xmlPathElementNames.length - 1]);
271 
272         for (int j = 0; j < keyValueElements.size(); j++) {
273             final Element e = keyValueElements.get(j);
274             final Element nameElement = e.getChild(IbdoXmlElementNames.NAME);
275             final String name = nameElement.getValue();
276             final String nameEncoding = nameElement.getAttributeValue(IBaseDataObjectXmlCodecs.ENCODING_ATTRIBUTE_NAME);
277             final String nameDecoded = new String(extractBytes(nameEncoding, name), StandardCharsets.UTF_8);
278 
279             if (nameDecoded.equals(key)) {
280                 element = e.getChild(IbdoXmlElementNames.VALUE);
281             }
282         }
283 
284         String lengthElement = element.getAttributeValue(IBaseDataObjectXmlCodecs.LENGTH_ATTRIBUTE_NAME);
285 
286         if (lengthElement == null) {
287             return "Could not find length element";
288         }
289 
290         try {
291             long length = Long.parseLong(lengthElement);
292 
293             if (length == lengthToCheck) {
294                 return null;
295             } else {
296                 return "Looking for length " + lengthToCheck + ", but found " + length;
297             }
298         } catch (NumberFormatException e) {
299             return "NumberFormatException: " + lengthElement;
300         }
301     }
302 
303     @Test
304     void testAttachmentsAndExtractedRecords() throws Exception {
305         final IBaseDataObject initialIbdo = new BaseDataObject();
306         final IBaseDataObject expectedIbdo = new BaseDataObject();
307         final IBaseDataObject childIbdo1 = new BaseDataObject();
308         final IBaseDataObject childIbdo2 = new BaseDataObject();
309         final IBaseDataObject extractedIbdo1 = new BaseDataObject();
310         final IBaseDataObject extractedIbdo2 = new BaseDataObject();
311         final List<IBaseDataObject> expectedChildren = new ArrayList<>();
312         final List<IBaseDataObject> actualChildren = new ArrayList<>();
313 
314         childIbdo1.setFilename("Child1");
315         childIbdo2.setFilename("Child2");
316         extractedIbdo1.setFilename("ExtractedRecord1");
317         extractedIbdo2.setFilename("ExtractedRecord2");
318 
319         expectedIbdo.setFilename("Parent");
320         expectedIbdo.addExtractedRecord(extractedIbdo1);
321         expectedIbdo.addExtractedRecord(extractedIbdo2);
322 
323         expectedChildren.add(childIbdo1);
324         expectedChildren.add(childIbdo2);
325 
326         final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
327                 actualChildren, DEFAULT_ELEMENT_ENCODERS);
328         final String diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, actualIbdo, expectedChildren,
329                 actualChildren, "testAttachmentsAndExtractedRecords", DiffCheckConfiguration.onlyCheckData());
330 
331         assertNull(diff);
332     }
333 
334     @Test
335     void testBadChannelFactory() throws Exception {
336         final IBaseDataObject initialIbdo = new BaseDataObject();
337         final IBaseDataObject expectedIbdo = new BaseDataObject();
338         final IBaseDataObject dataExceptionIbdo = new BaseDataObject();
339         final List<IBaseDataObject> expectedChildren = new ArrayList<>();
340         final List<IBaseDataObject> actualChildren = new ArrayList<>();
341 
342         dataExceptionIbdo.setChannelFactory(new ExceptionChannelFactory());
343 
344         final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(dataExceptionIbdo, expectedChildren, initialIbdo,
345                 actualChildren, DEFAULT_ELEMENT_ENCODERS);
346         final String diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, actualIbdo, expectedChildren,
347                 actualChildren, "testBadChannelFactory", DiffCheckConfiguration.onlyCheckData());
348 
349         assertNull(diff);
350 
351         final IBaseDataObject sha256ActualIbdo = ibdoFromXmlFromIbdo(dataExceptionIbdo, expectedChildren, initialIbdo,
352                 actualChildren, SHA256_ELEMENT_ENCODERS);
353         final String sha256Diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, sha256ActualIbdo, expectedChildren,
354                 actualChildren, "testBadChannelFactory", DiffCheckConfiguration.onlyCheckData());
355 
356         assertNull(sha256Diff);
357     }
358 
359     @Test
360     void testBadIntegerDecoding() throws Exception {
361         final IBaseDataObject initialIbdo = new BaseDataObject();
362         final IBaseDataObject expectedIbdo = new BaseDataObject();
363         final IBaseDataObject priorityIbdo = new BaseDataObject();
364         final List<IBaseDataObject> expectedChildren = new ArrayList<>();
365         final List<IBaseDataObject> actualChildren = new ArrayList<>();
366 
367         priorityIbdo.setPriority(100);
368 
369         final String xmlString = IBaseDataObjectXmlHelper.xmlFromIbdo(priorityIbdo, expectedChildren, initialIbdo, DEFAULT_ELEMENT_ENCODERS);
370         final String newXmlString = xmlString.replace("100", "100A");
371 
372         final SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
373         final Document document = builder.build(new StringReader(newXmlString));
374         final IBaseDataObject actualIbdo = IBaseDataObjectXmlHelper.ibdoFromXml(document, actualChildren, DEFAULT_ELEMENT_DECODERS);
375 
376         final String diff = PlaceComparisonHelper.checkDifferences(expectedIbdo, actualIbdo, expectedChildren,
377                 actualChildren, "testBadChannelFactory", DiffCheckConfiguration.onlyCheckData());
378 
379         assertNull(diff);
380     }
381 
382     @Test
383     void testCreateStandardInitialIbdo() {
384         final byte[] bytes = new byte[20];
385 
386         new Random().nextBytes(bytes);
387 
388         final SeekableByteChannelFactory sbcf = InMemoryChannelFactory.create(bytes);
389         final String classification = "Classification";
390         final String formAndFileType = "FormAndFiletype";
391         final KffDataObjectHandler kff = new KffDataObjectHandler(KffDataObjectHandler.TRUNCATE_KNOWN_DATA,
392                 KffDataObjectHandler.SET_FORM_WHEN_KNOWN, KffDataObjectHandler.SET_FILE_TYPE);
393         final List<String> differences = new ArrayList<>();
394         final IBaseDataObject expectedIbdo = new BaseDataObject();
395         final IBaseDataObject tempIbdo = new BaseDataObject();
396 
397         tempIbdo.setChannelFactory(sbcf);
398         kff.hash(tempIbdo);
399         expectedIbdo.setParameters(tempIbdo.getParameters());
400 
401         expectedIbdo.setCurrentForm(formAndFileType);
402         expectedIbdo.setFileType(formAndFileType);
403         expectedIbdo.setClassification(classification);
404 
405         final IBaseDataObject actualIbdo = IBaseDataObjectXmlHelper.createStandardInitialIbdo(new BaseDataObject(), sbcf, classification,
406                 formAndFileType, kff);
407 
408         IBaseDataObjectDiffHelper.diff(expectedIbdo, actualIbdo, differences, DiffCheckConfiguration.onlyCheckData());
409 
410         assertEquals(0, differences.size());
411     }
412 
413     private static IBaseDataObject ibdoFromXmlFromIbdo(final IBaseDataObject ibdo, final List<IBaseDataObject> children,
414             final IBaseDataObject initialIbdo, final List<IBaseDataObject> outputChildren, final ElementEncoders elementEncoders) throws Exception {
415         final String xmlString = IBaseDataObjectXmlHelper.xmlFromIbdo(ibdo, children, initialIbdo, elementEncoders);
416 
417         final SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
418         final Document document = builder.build(new StringReader(xmlString));
419 
420         return IBaseDataObjectXmlHelper.ibdoFromXml(document, outputChildren, DEFAULT_ELEMENT_DECODERS);
421     }
422 
423     private static class ExceptionChannelFactory implements SeekableByteChannelFactory {
424         @Override
425         public SeekableByteChannel create() {
426             return new SeekableByteChannel() {
427                 @Override
428                 public boolean isOpen() {
429                     return true;
430                 }
431 
432                 @Override
433                 public void close() throws IOException {
434                     throw new IOException("This SBC only throws Exceptions");
435                 }
436 
437                 @Override
438                 public int read(final ByteBuffer dst) throws IOException {
439                     throw new IOException("This SBC only throws Exceptions");
440                 }
441 
442                 @Override
443                 public int write(final ByteBuffer src) throws IOException {
444                     throw new IOException("This SBC only throws Exceptions");
445                 }
446 
447                 @Override
448                 public long position() throws IOException {
449                     throw new IOException("This SBC only throws Exceptions");
450                 }
451 
452                 @Override
453                 public SeekableByteChannel position(final long newPosition) throws IOException {
454                     throw new IOException("This SBC only throws Exceptions");
455                 }
456 
457                 @Override
458                 public long size() throws IOException {
459                     throw new IOException("This SBC only throws Exceptions");
460                 }
461 
462                 @Override
463                 public SeekableByteChannel truncate(final long size) throws IOException {
464                     throw new IOException("This SBC only throws Exceptions");
465                 }
466             };
467         }
468     }
469 }