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 }