package org.jibx.schema; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.StringReader; import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; import java.util.Map; import junit.framework.TestCase; import org.jibx.binding.Loader; import org.jibx.binding.Utility; import org.jibx.binding.classes.BoundClass; import org.jibx.binding.classes.ClassCache; import org.jibx.binding.classes.ClassFile; import org.jibx.binding.classes.MungedClass; import org.jibx.binding.def.BindingDefinition; import org.jibx.extras.DocumentComparator; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallingContext; import org.jibx.runtime.IUnmarshallable; import org.jibx.runtime.IUnmarshallingContext; import org.jibx.runtime.JiBXException; import org.jibx.schema.codegen.custom.SchemasetCustom; import org.jibx.schema.elements.SchemaElement; import org.jibx.schema.validation.PrevalidationVisitor; import org.jibx.schema.validation.RegistrationVisitor; import org.jibx.schema.validation.ValidationContext; import org.jibx.schema.validation.ValidationProblem; import org.jibx.schema.validation.ValidationVisitor; /** * Base class for all schema tests. This binds and loads the schema classes during initialization, and provides access * methods for working with the schema classes. */ public class SchemaTestBase extends TestCase { private static final String SCHEMA_CLASS = "org.jibx.schema.elements.SchemaElement"; private static final IBindingFactory m_bindingFactory; static { try { // set paths to be used for loading referenced classes URL[] urls = Loader.getClassPaths(); String[] paths = new String[urls.length]; for (int i = 0; i < urls.length; i++) { paths[i] = urls[i].getFile(); } ClassCache.setPaths(paths); ClassFile.setPaths(paths); // find the binding definition ClassLoader loader = SchemaTestBase.class.getClassLoader(); InputStream is = loader.getResourceAsStream("org/jibx/schema/binding.xml"); if (is == null) { throw new RuntimeException("Schema binding definition not found"); } // process the binding BoundClass.reset(); MungedClass.reset(); BindingDefinition.reset(); BindingDefinition def = Utility.loadBinding("binding.xml", "binding", is, null, true); def.generateCode(false); // output the modified class files MungedClass.fixChanges(true); // look up the mapped class and associated binding factory Class mclas = Class.forName(SCHEMA_CLASS); m_bindingFactory = BindingDirectory.getFactory(mclas); } catch (JiBXException e) { throw new RuntimeException("JiBXException: " + e.getMessage()); } catch (IOException e) { throw new RuntimeException("IOException: " + e.getMessage()); } catch (ClassNotFoundException e) { throw new RuntimeException("ClassNotFoundException: " + e.getMessage()); } } protected ValidationContext m_validationContext; protected NameRegister m_nameRegister; protected void setUp() throws Exception { m_validationContext = new ValidationContext(); } /** * Read a schema definition into model. * * @param is schema input stream * @param vctx validation context * @return schema element * @throws Exception */ protected SchemaElement readSchema(InputStream is, ValidationContext vctx) throws Exception { final IUnmarshallingContext ictx = m_bindingFactory.createUnmarshallingContext(); ictx.setDocument(is, null); ictx.setUserContext(vctx); SchemaElement schema = (SchemaElement)ictx.unmarshalElement(); m_nameRegister = schema.getRegister(); return schema; } /** * Read a schema definition into model. * * @param text schema text * @param vctx validation context * @return schema element * @throws Exception */ protected SchemaElement readSchema(String text, ValidationContext vctx) throws Exception { return readSchema(new ByteArrayInputStream(text.getBytes("utf-8")), vctx); } /** * Validate a schema definition. * * @param schema schema element * @param vctx validation context */ protected void validateSchema(SchemaElement schema, ValidationContext vctx) { TreeWalker tctx = new TreeWalker(vctx, vctx); tctx.walkSchema(schema, new PrevalidationVisitor(vctx)); vctx.clearTraversed(); tctx.walkSchema(schema, new RegistrationVisitor(vctx)); vctx.clearTraversed(); tctx.walkSchema(schema, new ValidationVisitor(vctx)); } /** * Check for validation problem. * * @param vctx * @return true if any problem */ protected boolean hasProblem(ValidationContext vctx) { return vctx.getProblems().size() > 0; } /** * Get validation problem report. * * @param vctx * @return problem text */ protected String getProblemText(ValidationContext vctx) { StringWriter writer = new StringWriter(); writer.append("Problems found in schema definition"); ArrayList problems = vctx.getProblems(); for (int i = 0; i < problems.size(); i++) { writer.append('\n'); ValidationProblem prob = (ValidationProblem)problems.get(i); writer.append(prob.getDescription()); } return writer.toString(); } /** * Write schema to text string. * * @param schema schema element * @return output schema text * @throws Exception */ protected String writeSchema(SchemaElement schema) throws Exception { StringWriter writer = new StringWriter(); IMarshallingContext ictx = m_bindingFactory.createMarshallingContext(); ictx.setOutput(writer); ictx.setIndent(2); ictx.marshalDocument(schema); return writer.toString(); } /** * Verify that output schema matches original input. Fails the test if there's any difference between the two * versions of the schema. * * @param text1 original schema text * @param text2 output schema text * @throws Exception */ protected void verifySchema(String text1, String text2) throws Exception { StringReader rdr1 = new StringReader(text1); StringReader rdr2 = new StringReader(text2); ByteArrayOutputStream bos = new ByteArrayOutputStream(); PrintStream pstr = new PrintStream(bos); DocumentComparator comp = new DocumentComparator(pstr); boolean match = comp.compare(rdr1, rdr2); if (!match) { pstr.close(); fail("Error roundtripping schema:\n" + new String(bos.toByteArray()) + "\nOriginal schema:\n" + text1 + "\nOutput schema:\n" + text2); } } /** * Read and validate schema, with one or more validation errors expected. * * @param text schema text * @param count number of errors expected * @param msg text for wrong number of errors found * @throws Exception on unexpected error */ protected void runErrors(String text, int count, String msg) throws Exception { SchemaElement schema = readSchema(text, m_validationContext); validateSchema(schema, m_validationContext); assertEquals(msg, count, m_validationContext.getProblems().size()); } /** * Read and validate schema, with a particular validation error expected. * * @param text schema text * @param msg text for wrong number of errors found * @throws Exception on unexpected error */ protected void runOneError(String text, String msg) throws Exception { SchemaElement schema = readSchema(text, m_validationContext); validateSchema(schema, m_validationContext); assertTrue(msg, hasProblem(m_validationContext)); } /** * Prepare schema model for use. * * @param text schema text * @return schema * @throws Exception on unexpected error */ protected SchemaElement prepareSchema(String text) throws Exception { SchemaElement schema = readSchema(text, m_validationContext); validateSchema(schema, m_validationContext); assertFalse(getProblemText(m_validationContext), hasProblem(m_validationContext)); return schema; } /** * Run schema round-trip with validation, with no validation errors. * * @param text schema text * @return schema * @throws Exception on unexpected error */ protected SchemaElement runNoErrors(String text) throws Exception { SchemaElement schema = prepareSchema(text); verifySchema(text, writeSchema(schema)); return schema; } /** * Load and validate schema directly. This takes the root schema resolver as input, and loads the schema along with * all associated schemas. Only the main schema document is actually validated, though. * * @param resolve resolver for all schema texts * @return root schema * @throws Exception */ protected SchemaElement loadSchema(TestResolver resolve) throws Exception { SchemaElement schema = readSchema(resolve.getContent(), m_validationContext); schema.setResolver(resolve); m_validationContext.setSchema(resolve.getName(), schema); validateSchema(schema, m_validationContext); assertFalse(getProblemText(m_validationContext), hasProblem(m_validationContext)); verifySchema(resolve.getText(), writeSchema(schema)); return schema; } /** * Load a code generation customization. * * @param text customizations document text * @throws Exception */ protected SchemasetCustom loadCustomization(String text) throws Exception { SchemasetCustom custom = new SchemasetCustom(null); IBindingFactory fact = BindingDirectory.getFactory("binding", "org.jibx.schema.codegen.custom"); IUnmarshallingContext ictx = fact.createUnmarshallingContext(); ictx.setDocument(new StringReader(text)); ictx.setUserContext(m_validationContext); ((IUnmarshallable)custom).unmarshal(ictx); return custom; } /** * Resolver used for handling schema text strings directly. */ protected static class TestResolver implements ISchemaResolver { /** Schema document. */ private final String m_document; /** Schema name. */ private final String m_name; /** Map from schema name to resolver. */ private final Map m_schemaMap; public TestResolver(String document, String name, Map map) { m_document = document; m_name = name; m_schemaMap = map; } public InputStream getContent() throws IOException { return new ByteArrayInputStream(m_document.getBytes("UTF-8")); } public String getId() { return m_name; } public String getName() { return m_name; } public ISchemaResolver resolve(String loc) throws IOException { return (ISchemaResolver)m_schemaMap.get(loc); } public String getText() { return m_document; } } }