/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.vdb; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.teiid.core.designer.EclipseMock; import org.teiid.core.designer.util.FileUtils; import org.teiid.core.designer.util.StringConstants; import org.teiid.core.util.SmartTestDesignerSuite; import org.teiid.designer.core.ModelWorkspaceMock; import org.teiid.designer.core.workspace.MockFileBuilder; import org.teiid.designer.core.workspace.ModelResource; import org.teiid.designer.roles.DataRole; import org.teiid.designer.roles.Permission; import org.teiid.designer.vdb.dynamic.DynamicModel; import org.teiid.designer.vdb.dynamic.DynamicVdb; import org.teiid.designer.vdb.dynamic.Metadata; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.InputSource; /** * */ @SuppressWarnings( "javadoc" ) public class VdbTestUtils implements StringConstants { public static final String BOOKS_MODEL = "Books"; public static final String BOOKS_MODEL_DDL = NEW_LINE + "CREATE FOREIGN TABLE AUTHORS (" + NEW_LINE + TAB + "AUTHOR_ID long NOT NULL OPTIONS(NAMEINSOURCE 'AUTHOR_ID', NATIVE_TYPE 'BIGINT', CASE_SENSITIVE 'FALSE', FIXED_LENGTH 'TRUE', SEARCHABLE 'ALL_EXCEPT_LIKE')," + NEW_LINE + TAB + "FIRSTNAME string(255) OPTIONS(NAMEINSOURCE 'FIRSTNAME', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "LASTNAME string(255) OPTIONS(NAMEINSOURCE 'LASTNAME', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "MIDDLEINIT string(255) OPTIONS(NAMEINSOURCE 'MIDDLEINIT', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "CONSTRAINT PK_AUTHORS PRIMARY KEY(AUTHOR_ID)" + NEW_LINE + ") OPTIONS(NAMEINSOURCE '\"BOOKS\".\"AUTHORS\"', UPDATABLE 'TRUE')" + NEW_LINE + NEW_LINE + "CREATE FOREIGN TABLE BOOK_AUTHORS (" + NEW_LINE + TAB + "ISBN string(255) NOT NULL OPTIONS(NAMEINSOURCE 'ISBN', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "AUTHOR_ID long NOT NULL OPTIONS(NAMEINSOURCE 'AUTHOR_ID', NATIVE_TYPE 'BIGINT', CASE_SENSITIVE 'FALSE', FIXED_LENGTH 'TRUE', SEARCHABLE 'ALL_EXCEPT_LIKE')," + NEW_LINE + TAB + "CONSTRAINT PK_BOOK_AUTHORS PRIMARY KEY(ISBN, AUTHOR_ID)," + NEW_LINE + TAB + "CONSTRAINT FK_ISBN FOREIGN KEY(ISBN) REFERENCES BOOKS(ISBN)," + NEW_LINE + TAB + "CONSTRAINT FK_AUTHOR FOREIGN KEY(AUTHOR_ID) REFERENCES AUTHORS(AUTHOR_ID)" + NEW_LINE + ") OPTIONS(NAMEINSOURCE '\"BOOKS\".\"BOOK_AUTHORS\"', UPDATABLE 'TRUE')" + NEW_LINE + NEW_LINE + "CREATE FOREIGN TABLE BOOKS (" + NEW_LINE + TAB + "ISBN string(255) NOT NULL OPTIONS(NAMEINSOURCE 'ISBN', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "TITLE string(255) OPTIONS(NAMEINSOURCE 'TITLE', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "SUBTITLE string(255) OPTIONS(NAMEINSOURCE 'SUBTITLE', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "PUBLISHER long OPTIONS(NAMEINSOURCE 'PUBLISHER', NATIVE_TYPE 'BIGINT', CASE_SENSITIVE 'FALSE', FIXED_LENGTH 'TRUE', SEARCHABLE 'ALL_EXCEPT_LIKE')," + NEW_LINE + TAB + "PUBLISH_YEAR long OPTIONS(NAMEINSOURCE 'PUBLISH_YEAR', NATIVE_TYPE 'BIGINT', CASE_SENSITIVE 'FALSE', FIXED_LENGTH 'TRUE', SEARCHABLE 'ALL_EXCEPT_LIKE')," + NEW_LINE + TAB + "EDITION long OPTIONS(NAMEINSOURCE 'EDITION', NATIVE_TYPE 'BIGINT', CASE_SENSITIVE 'FALSE', FIXED_LENGTH 'TRUE', SEARCHABLE 'ALL_EXCEPT_LIKE')," + NEW_LINE + TAB + "TYPE string(255) OPTIONS(NAMEINSOURCE 'TYPE', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "CONSTRAINT PK_BOOKS PRIMARY KEY(ISBN)," + NEW_LINE + TAB + "CONSTRAINT FK_PUBLISHER FOREIGN KEY(PUBLISHER) REFERENCES PUBLISHERS(PUBLISHER_ID)" + NEW_LINE + ") OPTIONS(NAMEINSOURCE '\"BOOKS\".\"BOOKS\"', UPDATABLE 'TRUE')" + NEW_LINE + NEW_LINE + "CREATE FOREIGN TABLE PUBLISHERS (" + NEW_LINE + TAB + "PUBLISHER_ID long NOT NULL OPTIONS(NAMEINSOURCE 'PUBLISHER_ID', NATIVE_TYPE 'BIGINT', CASE_SENSITIVE 'FALSE', FIXED_LENGTH 'TRUE', SEARCHABLE 'ALL_EXCEPT_LIKE')," + NEW_LINE + TAB + "NAME string(255) OPTIONS(NAMEINSOURCE 'NAME', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "LOCATION string(255) OPTIONS(NAMEINSOURCE 'LOCATION', NATIVE_TYPE 'VARCHAR')," + NEW_LINE + TAB + "CONSTRAINT PK_PUBLISHERS PRIMARY KEY(PUBLISHER_ID)" + NEW_LINE + ") OPTIONS(NAMEINSOURCE '\"BOOKS\".\"PUBLISHERS\"', UPDATABLE 'TRUE')" + NEW_LINE + NEW_LINE; public static final String BOOKS_VDB_PROJECT = "books.vdb.project"; public static final String RUNTIME_INF = "runtime-inf"; public static final String TEST_2120 = "Test_TEIIDDES_2120"; public static final String TEST_UDF = "TestUDF"; public static final String DYNAMIC_VDBS = "dynamic_vdbs"; public static final String TEST_PROJECT = BOOKS_VDB_PROJECT + File.separator + TEST_2120 + File.separator; public static final File TEST_DATA_DIR = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, EMPTY_STRING); public static final File PORTFOLIO_VDB_FILE = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, DYNAMIC_VDBS + File.separator + "portfolio-vdb.xml"); public static final File BOOKS_VDB_FILE = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, "books.vdb"); public static final File BOOKS_77_VDB_FILE = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, "books-7.7.x.vdb"); public static final File BOOKS_84_VDB_FILE = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, "books-8.4.x.vdb"); public static final File UDF_VDB_FILE = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, "TestUDF.vdb"); public static final File BOOK_DATATYPES_XSD = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, TEST_PROJECT + "BookDatatypes.xsd"); public static final File BOOKS_XMI = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, TEST_PROJECT + "Books.xmi"); public static final File BOOKS_XSD = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, TEST_PROJECT + "Books.xsd"); public static final File BOOKSXML_XMI = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, TEST_PROJECT + "BooksXML.xmi"); public static final String CUSTOMERS_VDB_PROJECT = "customers.vdb.project"; public static final String CUSTOMER_ACCOUNTS = "Customer_Accounts"; public static final String CUSTOMER_ACCOUNTS_MODEL = "CustomerAccounts"; public static final String CUSTOMER_TEST_PROJECT = CUSTOMERS_VDB_PROJECT + File.separator + CUSTOMER_ACCOUNTS + File.separator; public static final File CUSTOMERS_VDB_FILE = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, "CustomersVDB.vdb"); public static final File CUSTOMER_ACCOUNTS_XMI = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, CUSTOMER_TEST_PROJECT + "CustomerAccounts.xmi"); public static final File CUSTOMER_VIEWS_XMI = SmartTestDesignerSuite.getTestDataFile(VdbTestUtils.class, CUSTOMER_TEST_PROJECT + "CustomerViews.xmi"); public static final String CUSTOMER_SUPPORT_DATA_ROLE = "CustomerSupport"; public static final String CUSTOMER_SUPPORT_DATA_ROLE_DESCRIPTION = "Customer support data role"; public static ModelResource mockModelResource(IPath path) { ModelResource parent = mock(ModelResource.class); when(parent.getPath()).thenReturn(path.removeLastSegments(1)); final ModelResource modelResource = mock(ModelResource.class); when(modelResource.getItemName()).thenReturn(path.lastSegment()); when(modelResource.getParent()).thenReturn(parent); return modelResource; } /** * @return a mocked books vdb based on the testdata * @throws Exception */ public static Vdb mockBooksVdb(ModelWorkspaceMock modelWksp) throws Exception { List<MockFileBuilder> builders = new ArrayList<MockFileBuilder>(); MockFileBuilder booksDatatypesXSD = new MockFileBuilder(BOOK_DATATYPES_XSD); builders.add(booksDatatypesXSD); MockFileBuilder booksXMI = new MockFileBuilder(BOOKS_XMI); builders.add(booksXMI); MockFileBuilder booksXSD = new MockFileBuilder(BOOKS_XSD); builders.add(booksXSD); MockFileBuilder booksXMLXMI = new MockFileBuilder(BOOKSXML_XMI); builders.add(booksXMLXMI); /* * Need to ensure that the paths provided by the vdb point to the same file in the workspace * so need to mock the workspace finder and point the vdb paths to the testdata files. */ for (MockFileBuilder builder : builders) { IPath path = new Path(File.separator + TEST_2120 + File.separator + builder.getName()); when(modelWksp.getEclipseMock().workspaceRoot().findMember(path)).thenReturn(builder.getResourceFile()); } MockFileBuilder booksVdbBuilder = new MockFileBuilder(BOOKS_VDB_FILE); Vdb booksVdb = new XmiVdb(booksVdbBuilder.getResourceFile()); return booksVdb; } /** * @return a mocked books vdb based on the testdata * @throws Exception */ public static Vdb mockCustomersVdb(ModelWorkspaceMock modelWksp) throws Exception { // MockFileBuilder testData = new MockFileBuilder(TEST_DATA_DIR); List<MockFileBuilder> builders = new ArrayList<MockFileBuilder>(); MockFileBuilder customerAccountsXMI = new MockFileBuilder(CUSTOMER_ACCOUNTS_XMI); builders.add(customerAccountsXMI); MockFileBuilder customerViewsXMI = new MockFileBuilder(CUSTOMER_VIEWS_XMI); builders.add(customerViewsXMI); /* * Need to ensure that the paths provided by the vdb point to the same file in the workspace * so need to mock the workspace finder and point the vdb paths to the testdata files. */ for (MockFileBuilder builder : builders) { IPath path = new Path(File.separator + CUSTOMER_ACCOUNTS + File.separator + builder.getName()); when(modelWksp.getEclipseMock().workspaceRoot().findMember(path)).thenReturn(builder.getResourceFile()); } MockFileBuilder vdbBuilder = new MockFileBuilder(CUSTOMERS_VDB_FILE); Vdb customerVdb = new XmiVdb(vdbBuilder.getResourceFile()); return customerVdb; } public static MockFileBuilder mockPortfolioVdbXmlFile() throws IOException, Exception, FileNotFoundException { File portfolioCopy = File.createTempFile("Portfolio", DOT_XML); portfolioCopy.deleteOnExit(); // // Avoid using the test data original // FileUtils.copy(new FileInputStream(PORTFOLIO_VDB_FILE), portfolioCopy); assertTrue(portfolioCopy.exists() && portfolioCopy.length() > 0); final MockFileBuilder portfolio = new MockFileBuilder(portfolioCopy); portfolio.enableExtensionRegistry(); return portfolio; } public static DynamicVdb mockPortfolioDynamicVdb(ModelWorkspaceMock modelWksp) throws Exception { // // Required to avoid DDL Importer throwing NPE when validating name of model // EclipseMock eclipseMock = modelWksp.getEclipseMock(); when(eclipseMock.workspace().validateName(isA(String.class), anyInt())).thenReturn(Status.OK_STATUS); final MockFileBuilder portfolio = mockPortfolioVdbXmlFile(); // // Override workspaceRoot.findMember since essential when creating index files // IWorkspaceRoot wkspRoot = modelWksp.getEclipseMock().workspaceRoot(); when(wkspRoot.findMember(isA(IPath.class))).thenAnswer(new Answer<IResource>() { @Override public IResource answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); IPath path = (IPath) args[0]; IProject project = portfolio.getProject(); IPath projectPath = project.getFullPath(); IResource resource = null; if (path.isEmpty()) { // // Workspace root path // resource = mock(IResource.class); when(resource.exists()).thenReturn(true); } else if (projectPath.equals(path) || projectPath.makeRelative().equals(path)) { // // project name // resource = portfolio.getProject(); } else if (projectPath.isPrefixOf(path)) { // // Path could be inside mocked project so defer to project // resource = project.getFile(path.makeRelativeTo(projectPath)); } return resource; } }); DynamicVdb vdb = new DynamicVdb(); vdb.setSourceFile(portfolio.getResourceFile()); vdb.setName("Portfolio"); vdb.setVersion("1"); vdb.setDescription("The Portfolio Dynamic VDB"); vdb.setProperty("UseConnectorMetadata", "true"); DynamicModel marketData = new DynamicModel("MarketData"); marketData.addSource(new VdbSource(vdb, "text-connector", "java:/marketdata-file" , "file")); vdb.addDynamicModel(marketData); DynamicModel accounts = new DynamicModel("Accounts"); accounts.setProperty("importer.useFullSchemaName", "false"); accounts.addSource(new VdbSource(vdb, "h2-connector", "java:/accounts-ds" , "h2")); vdb.addDynamicModel(accounts); DynamicModel valuations = new DynamicModel("PersonalValuations"); valuations.setProperty("importer.headerRowNumber", "1"); valuations.setProperty("importer.ExcelFileName", "otherholdings.xls"); valuations.addSource(new VdbSource(vdb, "excelconnector", "java:/excel-file" , "excel")); String metadataText = EMPTY_STRING + "SET NAMESPACE 'http://www.teiid.org/translator/excel/2014' AS teiid_excel;" + NEW_LINE + NEW_LINE + "CREATE FOREIGN TABLE Sheet1 (" + NEW_LINE + "ROW_ID integer OPTIONS (SEARCHABLE 'All_Except_Like', \"teiid_excel:CELL_NUMBER\" 'ROW_ID')," + NEW_LINE + "ACCOUNT_ID integer OPTIONS (SEARCHABLE 'Unsearchable', \"teiid_excel:CELL_NUMBER\" '1')," + NEW_LINE + "PRODUCT_TYPE string OPTIONS (SEARCHABLE 'Unsearchable', \"teiid_excel:CELL_NUMBER\" '2')," + NEW_LINE + "PRODUCT_VALUE string OPTIONS (SEARCHABLE 'Unsearchable', \"teiid_excel:CELL_NUMBER\" '3')," + NEW_LINE + "CONSTRAINT PK0 PRIMARY KEY(ROW_ID)" + NEW_LINE + ") OPTIONS (\"teiid_excel:FILE\" 'otherholdings.xls', \"teiid_excel:FIRST_DATA_ROW_NUMBER\" '2');"; valuations.setMetadata(new Metadata(metadataText, Metadata.Type.DDL)); vdb.addDynamicModel(valuations); DynamicModel stocks = new DynamicModel("Stocks"); stocks.setModelType(DynamicModel.Type.VIRTUAL); metadataText = EMPTY_STRING + "CREATE VIEW StockPrices (" + NEW_LINE + "symbol string," + NEW_LINE + "price bigdecimal" + NEW_LINE + ")" + NEW_LINE + "AS" + NEW_LINE + "SELECT SP.symbol, SP.price" + NEW_LINE + "FROM (EXEC MarketData.getTextFiles('*.txt')) AS f," + NEW_LINE + "TEXTTABLE(f.file COLUMNS symbol string, price bigdecimal HEADER) AS SP;" + NEW_LINE + NEW_LINE + NEW_LINE + "CREATE VIEW Stock (" + NEW_LINE + "product_id integer," + NEW_LINE + "symbol string," + NEW_LINE + "price bigdecimal," + NEW_LINE + "company_name varchar(256)" + NEW_LINE + ")" + NEW_LINE + "AS" + NEW_LINE + "SELECT A.ID, S.symbol, S.price, A.COMPANY_NAME" + NEW_LINE + "FROM StockPrices AS S, Accounts.PRODUCT AS A" + NEW_LINE + "WHERE S.symbol = A.SYMBOL;"; stocks.setMetadata(new Metadata(metadataText, Metadata.Type.DDL)); vdb.addDynamicModel(stocks); DynamicModel stocksMatModel = new DynamicModel("StocksMatModel"); stocksMatModel.setModelType(DynamicModel.Type.VIRTUAL); metadataText = EMPTY_STRING + "CREATE view stockPricesMatView" + NEW_LINE + "(" + NEW_LINE + "product_id integer," + NEW_LINE + "symbol string," + NEW_LINE + "price bigdecimal," + NEW_LINE + "company_name varchar(256)" + NEW_LINE + ") OPTIONS (MATERIALIZED 'TRUE', UPDATABLE 'TRUE'," + NEW_LINE + "MATERIALIZED_TABLE 'Accounts.h2_stock_mat')" + NEW_LINE + "AS SELECT A.ID, S.symbol, S.price, A.COMPANY_NAME" + NEW_LINE + "FROM Stocks.StockPrices AS S, Accounts.PRODUCT AS A" + NEW_LINE + "WHERE S.symbol = A.SYMBOL;"; stocksMatModel.setMetadata(new Metadata(metadataText, Metadata.Type.DDL)); vdb.addDynamicModel(stocksMatModel); List<Permission> permissions = new ArrayList<Permission>(); Permission permission1 = new Permission("Accounts", false, true, true, false, false, false); permissions.add(permission1); Permission permission2 = new Permission("Accounts.Customer"); permission2.setCondition("state <> 'New York'"); permissions.add(permission2); Permission permission3 = new Permission("Accounts.Customer.SSN"); permission3.setMask("null"); permissions.add(permission3); String[] roleNames = {"supervisor", "dept-supervisor" }; DataRole dataRole = new DataRole("ReadWrite", "Allow Reads and Writes to tables and procedures", null, null, null, Arrays.asList(roleNames), permissions); vdb.addDataRole(dataRole); return vdb; } public static Document readDocument(Reader reader) throws Exception { return readDocument(reader, true); } public static Document readDocument(Reader reader, boolean ignoreComments) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringComments(ignoreComments); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new InputSource(reader)); doc.setXmlStandalone(true); doc.normalizeDocument(); return doc; } public static Document readDocument(File file) throws Exception { return readDocument(new FileReader(file)); } public static Document readDocument(String xml) throws Exception { return readDocument(new StringReader(xml), false); } public static Document readDocument(String xml, boolean ignoreComments) throws Exception { return readDocument(new StringReader(xml), ignoreComments); } private static boolean compareAttributes(Element expected, Element actual) { NamedNodeMap expectedAttrs = expected.getAttributes(); NamedNodeMap actualAttrs = actual.getAttributes(); if (expectedAttrs.getLength() != actualAttrs.getLength()) return false; for (int i = 0; i < expectedAttrs.getLength(); i++) { Attr expectedAttr = (Attr)expectedAttrs.item(i); Attr actualAttr = null; if (expectedAttr.getNamespaceURI() == null) actualAttr = (Attr)actualAttrs.getNamedItem(expectedAttr.getName()); else actualAttr = (Attr)actualAttrs.getNamedItemNS(expectedAttr.getNamespaceURI(), expectedAttr.getLocalName()); if (actualAttr == null) { return false; } if (! expectedAttr.getValue().equals(actualAttr.getValue())) { return false; } } return true; } private static boolean compareElements(org.w3c.dom.Element expected, org.w3c.dom.Element actual, StringBuilder log) { // compare element names if (expected.getLocalName() != null) { if (! expected.getLocalName().equals(actual.getLocalName())) return false; } // compare element ns if (expected.getNamespaceURI() != null) { if (! expected.getNamespaceURI().equals(actual.getNamespaceURI())) return false; } // compare attributes if (! compareAttributes(expected, actual)) { return false; } // compare children NodeList expectedChildren = expected.getChildNodes(); NodeList actualChildren = actual.getChildNodes(); // Same number but could be in a different order for (int i = 0; i < expectedChildren.getLength(); ++i) { org.w3c.dom.Node expectedChild = expectedChildren.item(i); if (expectedChild instanceof Text && expectedChild.getTextContent().trim().length() == 0) continue; // Ignore these text formatting nodes boolean matchMade = false; for (int j = 0; j < actualChildren.getLength(); ++j) { org.w3c.dom.Node actualChild = actualChildren.item(j); if (! expectedChild.getNodeName().equals(actualChild.getNodeName())) continue; if (expectedChild.getNodeType() != actualChild.getNodeType()) continue; if (expectedChild instanceof Element) { matchMade = compareElements((Element) expectedChild, (Element) actualChild, log); } else if (expectedChild instanceof Text) { matchMade = compareTextNode((Text) expectedChild, (Text) actualChild, log); } if (matchMade) break; } if (! matchMade) { log.append("Failed to find: " + NEW_LINE); logNode(log, expectedChild); return false; } } return true; } private static void logNode(StringBuilder log, org.w3c.dom.Node node) { if (node.getParentNode() != null) logNode(log, node.getParentNode()); log.append(TAB + OPEN_ANGLE_BRACKET + SPACE + node.getNodeName() + SPACE); NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { for (int j = 0; j < attributes.getLength(); j++) { Attr attr = (Attr)attributes.item(j); log.append(attr.getName() + EQUALS + attr.getValue() + SPACE); } } log.append(CLOSE_ANGLE_BRACKET + NEW_LINE); } /** * Should reduce a spread out block of text to a single line * with single spaces between tokens * * @param data * @return single space line of text from block */ private static String normalizeSpacing(String data) { data = data.trim(); data = data.replaceAll(NEW_LINE, SPACE); data = data.replaceAll(">[\\s]+<", CLOSE_ANGLE_BRACKET + OPEN_ANGLE_BRACKET); data = data.replaceAll("[\\s]+", SPACE); data = data.replaceAll("CDATA\\[[\\s]+", "CDATA["); data = data.replaceAll("; \\]\\]", ";]]"); return data; } private static boolean compareTextNode(org.w3c.dom.Text expected, org.w3c.dom.Text actual, StringBuilder errorMessages) { String expectedData = normalizeSpacing(expected.getData()); String actualData = normalizeSpacing(actual.getData()); if (expectedData.equalsIgnoreCase(actualData)) return true; errorMessages.append(expected.getData() + NEW_LINE + " does not match " + actual.getData() + NEW_LINE); return false; } public static String printDocument(Document document) throws Exception { StringWriter writer = new StringWriter(); try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ transformer.transform(new DOMSource(document), new StreamResult(writer)); return writer.toString(); } finally { writer.close(); } } /** * Compare two documents * * @param expected expected document * @param actual actual document * @throws Exception */ public static void compareDocuments(Document expected, Document actual) throws Exception { assertNotNull(expected); assertNotNull(actual); assertEquals(expected.getNodeType(), actual.getNodeType()); StringBuilder log = new StringBuilder(); if (! compareElements(expected.getDocumentElement(), actual.getDocumentElement(), log)) { log.append(NEW_LINE + "=== EXPECTED ===" + NEW_LINE); log.append(printDocument(expected)); log.append(NEW_LINE); log.append(NEW_LINE + "=== ACTUAL ===" + NEW_LINE); log.append(printDocument(actual)); fail(log.toString()); } } }