package org.dcache.services.info.serialisation; import com.thaiopensource.util.PropertyMapBuilder; import com.thaiopensource.validate.IncorrectSchemaException; import com.thaiopensource.validate.Schema; import com.thaiopensource.validate.SchemaReader; import com.thaiopensource.validate.ValidateProperty; import com.thaiopensource.validate.Validator; import com.thaiopensource.validate.rng.CompactSchemaReader; import org.junit.Before; import org.junit.Test; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; import java.io.IOException; import java.io.StringReader; import org.dcache.services.info.base.StatePath; import org.dcache.services.info.base.TestStateExhibitor; import static org.junit.Assert.fail; public class XmlSerialiserTests { private static final String RNC_DEFAULT_NAMESPACE = "default namespace='http://www.dcache.org/2008/01/Info'\n"; // Unfortunately, because of our XML structure, we must enumerate the possible options private static final String CELLS_ANY_ORDER = "cell-1 = element cell { attribute id {'cell-1'}} \n" + "cell-2 = element cell { attribute id {'cell-2'}} \n" + "cell-3 = element cell { attribute id {'cell-3'}} \n" + "cells-123 = element cells { cell-1, cell-2, cell-3} \n" + "cells-132 = element cells { cell-1, cell-3, cell-2} \n" + "cells-213 = element cells { cell-2, cell-1, cell-3} \n" + "cells-231 = element cells { cell-2, cell-3, cell-1} \n" + "cells-312 = element cells { cell-3, cell-1, cell-2} \n" + "cells-321 = element cells { cell-3, cell-2, cell-1} \n" + "cells-any-order = (cells-123 | cells-132 | cells-213 | cells-231 | cells-312 | cells-321) \n"; private static final String RNC_ONLY_DCACHE = RNC_DEFAULT_NAMESPACE + "element dCache { empty }"; private static final String RNC_DCACHE_DOMAIN = RNC_DEFAULT_NAMESPACE + CELLS_ANY_ORDER + "dCacheDomain = element domain { attribute name { 'dCacheDomain'}, \n" + " ( cells-any-order \n" + " &element routine-info { empty })} \n" + "domains = element domains { dCacheDomain } \n" + "start = element dCache { domains }\n"; private static final String RNC_ALL = RNC_DEFAULT_NAMESPACE + CELLS_ANY_ORDER + "infoDomain = element domain { attribute name {'infoDomain'}, \n" + " cells-any-order} \n" + "dCacheDomain = element domain { attribute name {'dCacheDomain'}, \n" + " ( cells-any-order\n" + " &element routine-info { empty })}\n" + "awkwardDomain = element domain {attribute name {'''\"bothersome\"&annoying<Cells>'sDomain'''}, \n" + " ( element cells {\n" + " element cell { attribute id {'''\"Foo & Bar\" <foobar@example.org>'s cell'''}} \n" + " })}\n" + "domains = element domains { (infoDomain, dCacheDomain, awkwardDomain) | \n" + " (infoDomain, awkwardDomain, dCacheDomain) | \n" + " (awkwardDomain, infoDomain, dCacheDomain) | \n" + " (awkwardDomain, dCacheDomain, infoDomain) | \n" + " (dCacheDomain, infoDomain, awkwardDomain) | \n" + " (dCacheDomain, awkwardDomain, infoDomain)} \n" + "start = element dCache { domains }\n"; TestStateExhibitor _exhibitor; StateSerialiser _serialiser; @Before public void setUp() { _exhibitor = new TestStateExhibitor(); XmlSerialiser serialiser = new XmlSerialiser(); serialiser.setStateExhibitor(_exhibitor); _serialiser = serialiser; _exhibitor.addListItem( StatePath.parsePath( "domains.dCacheDomain"), "domain", "name"); _exhibitor.addListItem( StatePath .parsePath( "domains.dCacheDomain.cells.cell-1"), "cell", "id"); _exhibitor.addListItem( StatePath .parsePath( "domains.dCacheDomain.cells.cell-2"), "cell", "id"); _exhibitor.addListItem( StatePath .parsePath( "domains.dCacheDomain.cells.cell-3"), "cell", "id"); _exhibitor.addBranch( StatePath .parsePath( "domains.dCacheDomain.routine-info")); _exhibitor.addListItem( StatePath.parsePath( "domains.infoDomain"), "domain", "name"); _exhibitor.addListItem( StatePath .parsePath( "domains.infoDomain.cells.cell-1"), "cell", "id"); _exhibitor.addListItem( StatePath .parsePath( "domains.infoDomain.cells.cell-2"), "cell", "id"); _exhibitor.addListItem( StatePath .parsePath( "domains.infoDomain.cells.cell-3"), "cell", "id"); // The "bothersome" domain contains characters that must be marked-up StatePath domainPath = StatePath.parsePath( "domains.\"bothersome\"&annoying<Cells>'sDomain"); _exhibitor.addListItem( domainPath, "domain", "name"); StatePath cellsPath = domainPath.newChild( "cells"); _exhibitor.addListItem( cellsPath.newChild( "\"Foo & Bar\" <foobar@example.org>'s cell"), "cell", "id"); } @Test public void testEmptyState() throws IOException, SAXException, IncorrectSchemaException { _exhibitor = new TestStateExhibitor(); XmlSerialiser serialiser = new XmlSerialiser(); serialiser.setStateExhibitor(_exhibitor); _serialiser = serialiser; String result = _serialiser.serialise(); assertXmlValidates( RNC_ONLY_DCACHE, result); } @Test public void testFullVisit() { String result = _serialiser.serialise(); assertXmlValidates( RNC_ALL, result); } @Test public void testDomainDCacheDomainVisit() { _serialiser.serialise(); String result = _serialiser.serialise( StatePath .parsePath( "domains.dCacheDomain")); assertXmlValidates( RNC_DCACHE_DOMAIN, result); } private void assertXmlValidates( String rncGrammar, String xmlData) { Validator validator = createValidator( rncGrammar); XMLReader reader = createValidatingXmlReader( validator); parseXmlWithReader( reader, xmlData); } private Validator createValidator( String rndData) { SchemaReader reader = CompactSchemaReader.getInstance(); InputSource source = createInputSource( rndData); PropertyMapBuilder schemaPmb = new PropertyMapBuilder(); Schema schema = null; try { schema = reader.createSchema( source, schemaPmb.toPropertyMap()); } catch (IOException | IncorrectSchemaException | SAXException e) { fail(e.toString()); } ErrorHandler eh = new AssertingErrorHandler(); PropertyMapBuilder validatorPmb = new PropertyMapBuilder(); validatorPmb.put( ValidateProperty.ERROR_HANDLER, eh); return schema.createValidator( validatorPmb.toPropertyMap()); } private XMLReader createValidatingXmlReader( Validator validator) { XMLReader myReader = null; try { myReader = XMLReaderFactory.createXMLReader(); } catch (SAXException e) { fail( e.toString()); } myReader.setContentHandler( validator.getContentHandler()); myReader.setDTDHandler( validator.getDTDHandler()); return myReader; } private InputSource createInputSource( String data) { return new InputSource( new StringReader( data)); } private void parseXmlWithReader( XMLReader reader, String xmlData) { InputSource input = createInputSource( xmlData); try { reader.parse( input); } catch (IOException | SAXException e) { fail( e.toString()); } } private static class AssertingErrorHandler implements ErrorHandler { @Override public void error( SAXParseException exception) throws SAXException { fail(buildMessage(exception)); } @Override public void fatalError( SAXParseException exception) throws SAXException { fail(buildMessage(exception)); } @Override public void warning( SAXParseException exception) throws SAXException { fail(buildMessage(exception)); } private String buildMessage(SAXParseException exception) { return "[RELAX-NG] " + exception.getLineNumber() + "," + exception.getColumnNumber() + ": " + exception.getMessage(); } } }