Java tutorial
/* * #%L * Nazgul Project: mithlond-services-shared-entity-test * %% * Copyright (C) 2015 Mithlond * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package se.mithlond.services.shared.test.entity; import org.eclipse.persistence.internal.oxm.record.namespaces.MapNamespacePrefixMapper; import org.eclipse.persistence.jaxb.JAXBContextProperties; import org.eclipse.persistence.jaxb.MarshallerProperties; import org.eclipse.persistence.jaxb.UnmarshallerProperties; import org.junit.rules.TestWatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.SAXException; import se.jguru.nazgul.core.algorithms.api.collections.predicate.Tuple; import se.jguru.nazgul.core.xmlbinding.spi.jaxb.helper.JaxbNamespacePrefixResolver; import se.jguru.nazgul.core.xmlbinding.spi.jaxb.helper.MappedSchemaResourceResolver; import se.jguru.nazgul.core.xmlbinding.spi.jaxb.transport.EntityTransporter; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.PropertyException; import javax.xml.bind.SchemaOutputResolver; import javax.xml.bind.Unmarshaller; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.Predicate; import java.util.stream.Collectors; /** * <p>jUnit rule simplifying working with JAXB marshalling and unmarshalling during tests. * Typically, this rule is invoked in 3 steps for marshalling and 2 steps for unmarshalling.</p> * <h2>Marshalling Objects to XML Strings</h2> * <ol> * <li>Call <code>add(class1, class2, ...);</code> to add any classes that should be bound into the * JAXBContext for marshalling or unmarshalling.</li> * <li>(Optional): Call <code>mapXmlNamespacePrefix(anXmlURI, marshalledXmlPrefix)</code> * to control the XML namespace prefix in the marshalled structure.</li> * <li>Call <code>marshalToXML(classLoader, anObject)</code> to marshalToXML the objects into XML</li> * </ol> * <h2>Unmarshalling Objects from XML Strings</h2> * <ol> * <li>Call <code>add(class1, class2, ...);</code> to add any classes that should be bound into the * JAXBContext for marshalling or unmarshalling.</li> * <li>(Optional): Call <code>mapXmlNamespacePrefix(anXmlURI, marshalledXmlPrefix)</code> * to control the XML namespace prefix in the marshalled structure.</li> * <li>Call <code>unmarshal(classLoader, ResultClass.class, xmlString);</code> to unmarshal the XML String into * Java Objects.</li> * </ol> * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public class PlainJaxbContextRule extends TestWatcher { // Our Log private static final Logger log = LoggerFactory.getLogger(PlainJaxbContextRule.class); private static final Comparator<Class<?>> CLASS_COMPARATOR = (class1, class2) -> { // Deal with nulls. final String className1 = class1 == null ? "" : class1.getName(); final String className2 = class2 == null ? "" : class2.getName(); // All done return className1.compareTo(className2); }; /** * The JAXBContextFactory implementation within EclipseLink (i.e. the MOXy implementation). */ public static final String ECLIPSELINK_JAXB_CONTEXT_FACTORY = "org.eclipse.persistence.jaxb.JAXBContextFactory"; private static final String JAXB_CONTEXTFACTORY_PROPERTY = "javax.xml.bind.context.factory"; private static final String JSON_CONTENT_TYPE = "application/json"; private static final String ECLIPSELINK_MEDIA_TYPE = "eclipselink.media-type"; private static final String ECLIPSELINK_JSON_MARSHAL_EMPTY_COLLECTIONS = "eclipselink.json.marshal-empty-collections"; private static final SortedSet<String> STD_IGNORED_CLASSPATTERNS; private static final String RI_NAMESPACE_PREFIX_MAPPER_PROPERTY = "com.sun.xml.bind.namespacePrefixMapper"; private static final String ECLIPSELINK_NAMESPACE_PREFIX_MAPPER_PROPERTY = JAXBContextProperties.NAMESPACE_PREFIX_MAPPER; static { STD_IGNORED_CLASSPATTERNS = new TreeSet<>(); STD_IGNORED_CLASSPATTERNS.add("org.aspectj"); STD_IGNORED_CLASSPATTERNS.add("ch."); STD_IGNORED_CLASSPATTERNS.add("org.slf4j"); } // Internal state private SortedSet<Class<?>> jaxbAnnotatedClasses; private SortedSet<String> classPatternsToIgnore; private JAXBContext jaxbContext; private JaxbNamespacePrefixResolver namespacePrefixResolver; private boolean performXsdValidation = true; private boolean useEclipseLinkMOXyIfAvailable = true; private SortedMap<String, Object> marshallerProperties; private SortedMap<String, Object> unMarshallerProperties; private Predicate<Class<?>> ignoredClassFilter = aClass -> { // Don't accept nulls. if (aClass == null) { return false; } // Don't accept classes whose names contain any of the classPatternsToIgnore. final String className = aClass.getName(); for (String current : classPatternsToIgnore) { if (className.contains(current)) { return false; } } // Accept the aClass. return true; }; /** * Default constructor, setting up a clean internal state. */ public PlainJaxbContextRule() { this.jaxbAnnotatedClasses = new TreeSet<>(CLASS_COMPARATOR); this.namespacePrefixResolver = new JaxbNamespacePrefixResolver(); this.classPatternsToIgnore = new TreeSet<>(); this.classPatternsToIgnore.addAll(STD_IGNORED_CLASSPATTERNS); // Assign standard properties for the Marshaller marshallerProperties = new TreeMap<>(); marshallerProperties.put(Marshaller.JAXB_ENCODING, "UTF-8"); marshallerProperties.put(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshallerProperties.put(RI_NAMESPACE_PREFIX_MAPPER_PROPERTY, namespacePrefixResolver); marshallerProperties.put(MarshallerProperties.JSON_INCLUDE_ROOT, false); marshallerProperties.put(MarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true); marshallerProperties.put(MarshallerProperties.JSON_MARSHAL_EMPTY_COLLECTIONS, true); // Assign standard properties for the Unmarshaller unMarshallerProperties = new TreeMap<>(); unMarshallerProperties.put(RI_NAMESPACE_PREFIX_MAPPER_PROPERTY, namespacePrefixResolver); unMarshallerProperties.put(UnmarshallerProperties.JSON_INCLUDE_ROOT, false); unMarshallerProperties.put(UnmarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true); } /** * If {@code false}, the JAXB reference implementation will be used for JAXB operations, * and otherwise the MOXy implementation from EclipseLink. * * @param useEclipseLinkMOXyIfAvailable if {@code false}, the JAXB reference implementation is used. * @see #useEclipseLinkMOXyIfAvailable */ public void setUseEclipseLinkMOXyIfAvailable(final boolean useEclipseLinkMOXyIfAvailable) { this.useEclipseLinkMOXyIfAvailable = useEclipseLinkMOXyIfAvailable; } /** * Assigns the perform XSD validation flag. By default, the value of this flag is {@code true}, implying that * validation is always done before marshalling and after unmarshalling. * * @param performXsdValidation if {@code false}, XSD validation will not be performed before marshalling and * after unmarshalling data. */ public void setPerformXsdValidation(final boolean performXsdValidation) { this.performXsdValidation = performXsdValidation; } /** * <p>Adds a set of classes to be used within the JAXBContext for marshalling or unmarshalling. * Normally, these classes would need to be annotated with JAXB annotations, and would be injected into the * construction of the JAXBContext normally:</p> * <pre> * <code> * // Create an array of all added classes. * final Class[] classesToBeBound = ... all add-ed Classes ... * * // Create the JAXBContext containing/binding all added classes. * JAXBContext jaxbContext = JAXBContext.newInstance(classesToBeBound); * </code> * </pre> * * @param jaxbAnnotatedClasses The classes to add to any JAXBContext used within this PlainJaxbContextRule, for * marshalling or unmarshalling. The supplied classes are given to the JAXBContext * during creation. */ public void add(final Class<?>... jaxbAnnotatedClasses) { if (jaxbAnnotatedClasses != null) { Collections.addAll(this.jaxbAnnotatedClasses, jaxbAnnotatedClasses); } } /** * Adds patterns for classes to ignore in creating a JAXBContext. * * @param clearExistingPatterns if {@code true}, any existing patterns are cleared before adding the supplied * ignore patterns. Typically something like {@code org.aspectj}. * @param ignorePattern A set of patterns to ignore if present within JAXContext classes. */ public void addIgnoreClassPatterns(final boolean clearExistingPatterns, final String... ignorePattern) { // Handle clearing existing patterns if (clearExistingPatterns) { classPatternsToIgnore.clear(); } // ... and add the supplied ones. if (ignorePattern != null && ignorePattern.length > 0) { Collections.addAll(classPatternsToIgnore, ignorePattern); } } /** * Retrieves the set of properties used within the Marshaller. * * @return the properties assigned to the Marshaller before use. */ public SortedMap<String, Object> getMarshallerProperties() { return marshallerProperties; } /** * Retrieves the set of properties used within the Unmarshaller. * * @return the properties assigned to the Unmarshaller before use. */ public SortedMap<String, Object> getUnMarshallerProperties() { return unMarshallerProperties; } /** * Adds the supplied patterns for classes to ignore in creating a JAXBContext, without clearing any existing * patterns. * * @param ignorePattern A set of patterns to ignore if present within JAXContext classes. * @see #addIgnoreClassPatterns(boolean, String...) */ public void addIgnoreClassPatterns(final String... ignorePattern) { addIgnoreClassPatterns(false, ignorePattern); } /** * Marshals the supplied objects into an XML String, or throws an IllegalArgumentException * containing a wrapped JAXBException indicating why the marshalling was unsuccessful. * * @param loader The ClassLoader to use in order to load all classes previously added * by calls to the {@code add} method. * @param emitJSON if {@code true}, the method will attempt to output JSON instead of XML. * This requires the EclipseLink MOXy implementation as the JAXBContextFactory. * @param objects The objects to Marshal into XML. * @return An XML-formatted String containing * @throws IllegalArgumentException if the marshalling operation failed. * The {@code cause} field in the IllegalArgumentException contains * the JAXBException thrown by the JAXB framework. * @see #add(Class[]) */ @SuppressWarnings("all") public String marshal(final ClassLoader loader, final boolean emitJSON, final Object... objects) throws IllegalArgumentException { // Create an EntityTransporter, to extract the types as required by the plain JAXBContext. final EntityTransporter<Object> transporter = new EntityTransporter<>(); for (Object current : objects) { transporter.addItem(current); } // Use EclipseLink? if (emitJSON) { setUseEclipseLinkMOXyIfAvailable(true); } if (useEclipseLinkMOXyIfAvailable) { System.setProperty(JAXB_CONTEXTFACTORY_PROPERTY, ECLIPSELINK_JAXB_CONTEXT_FACTORY); } else { System.clearProperty(JAXB_CONTEXTFACTORY_PROPERTY); } // Extract class info as required by the JAXBContext. final SortedSet<String> clsInfo = transporter.getClassInformation(); try { jaxbContext = JAXBContext.newInstance(getClasses(loader, clsInfo), marshallerProperties); log.info("Got JAXBContext of type " + jaxbContext.getClass().getName() + ", with classes"); } catch (JAXBException e) { throw new IllegalArgumentException("Could not create JAXB context.", e); } // Handle the namespace mapper handleNamespacePrefixMapper(); Marshaller marshaller = null; try { marshaller = jaxbContext.createMarshaller(); // Should we validate what we write? if (performXsdValidation) { if ("org.eclipse.persistence.jaxb.JAXBContext".equals(jaxbContext.getClass().getName())) { // Cast to the appropriate JAXBContext org.eclipse.persistence.jaxb.JAXBContext eclipseLinkJaxbContext = ((org.eclipse.persistence.jaxb.JAXBContext) jaxbContext); if (emitJSON) { final SimpleSchemaOutputResolver simpleResolver = new SimpleSchemaOutputResolver(); Arrays.stream(objects).filter(c -> c != null).forEach(c -> { final Class<?> currentClass = c.getClass(); if (log.isDebugEnabled()) { log.debug("Generating JSON schema for " + currentClass.getName()); } try { eclipseLinkJaxbContext.generateJsonSchema(simpleResolver, currentClass); } catch (Exception e) { log.error("Could not generate JSON schema", e); } }); } else { final Tuple<Schema, LSResourceResolver> schema2LSResolver = generateTransientXSD( jaxbContext); marshaller.setSchema(schema2LSResolver.getKey()); } } } } catch (Exception e) { try { marshaller = jaxbContext.createMarshaller(); } catch (JAXBException e1) { throw new IllegalStateException("Could not create non-validating JAXB Marshaller", e); } } // Should we emit JSON instead of XML? if (emitJSON) { try { marshaller.setProperty(ECLIPSELINK_MEDIA_TYPE, JSON_CONTENT_TYPE); marshaller.setProperty(ECLIPSELINK_JSON_MARSHAL_EMPTY_COLLECTIONS, Boolean.FALSE); } catch (PropertyException e) { // This is likely not the EclipseLink Marshaller. log.error( "Could not assign EclipseLink properties to Marshaller of type " + marshaller.getClass().getName() + "]. Proceeding, but results may be unexpected.", e); } } // Assign all other Marshaller properties. try { for (Map.Entry<String, Object> current : marshallerProperties.entrySet()) { marshaller.setProperty(current.getKey(), current.getValue()); } } catch (PropertyException e) { final StringBuilder builder = new StringBuilder("Could not assign Marshaller properties."); marshallerProperties.entrySet().stream() .forEach(c -> builder.append("\n [" + c.getKey() + "]: " + c.getValue())); throw new IllegalStateException(builder.toString(), e); } // Marshal the objects final StringWriter result = new StringWriter(); for (int i = 0; i < objects.length; i++) { final StringWriter tmp = new StringWriter(); try { marshaller.marshal(objects[i], tmp); result.write(tmp.toString()); } catch (JAXBException e) { final String currentTypeName = objects[i] == null ? "<null>" : objects[i].getClass().getName(); throw new IllegalArgumentException( "Could not marshalToXML object [" + i + "] of type [" + currentTypeName + "].", e); } catch (Exception e) { throw new IllegalArgumentException("Could not marshalToXML object [" + i + "]: " + objects[i], e); } } // All done. return result.toString(); } /** * <p>Unmarshals the supplied xmlToUnmarshal into a result of the supplied type, using the given * ClassLoader to load all relevant types into the JAXBContext. Typical unarshalling use case involves * 2 calls on this PlainJaxbContextRule:</p> * <pre> * <code> * // 1) add all types you expect to unmarshal * rule.add(Foo.class, Bar.class, Gnat.class); * * // 2) unmarshal your XML string * Foo unmarshalled = rule.unmarshal(getClass().getClassLoader(), Foo.class, aFooXml); * </code> * </pre> * * @param loader The ClassLoader to use in order to load all classes previously added * by calls to the {@code add} method. * @param assumeJSonInput If {@code true}, the input is assumed to be JSON. * This requires the EclipseLink MOXy JAXBContextFactory to succeed. * @param resultType The type of the resulting object. * @param toUnmarshal The XML string to unmarshal into a T object. * @param <T> The expected type to unmarshal into. * @return The resulting, unmarshalled object. * @see #add(Class[]) */ public <T> T unmarshal(final ClassLoader loader, final boolean assumeJSonInput, final Class<T> resultType, final String toUnmarshal) { // Check sanity org.apache.commons.lang3.Validate.notNull(resultType, "Cannot handle null 'resultType' argument."); org.apache.commons.lang3.Validate.notEmpty(toUnmarshal, "Cannot handle null or empty 'xmlToUnmarshal' argument."); final Source source = new StreamSource(new StringReader(toUnmarshal)); // Use EclipseLink? if (assumeJSonInput || useEclipseLinkMOXyIfAvailable) { System.setProperty(JAXB_CONTEXTFACTORY_PROPERTY, ECLIPSELINK_JAXB_CONTEXT_FACTORY); } else { System.clearProperty(JAXB_CONTEXTFACTORY_PROPERTY); } try { jaxbContext = JAXBContext.newInstance(getClasses(loader, null), unMarshallerProperties); handleNamespacePrefixMapper(); } catch (JAXBException e) { throw new IllegalArgumentException("Could not create JAXB context.", e); } try { final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); // Assign all unMarshallerProperties to the Unmarshaller unMarshallerProperties.entrySet().forEach(c -> { try { unmarshaller.setProperty(c.getKey(), c.getValue()); } catch (PropertyException e) { throw new IllegalStateException("Could not assign Unmarshaller property [" + c.getKey() + "] with value [" + c.getValue() + "]", e); } }); if (assumeJSonInput) { try { unmarshaller.setProperty(ECLIPSELINK_MEDIA_TYPE, JSON_CONTENT_TYPE); // unmarshaller.setProperty(ECLIPSELINK_JSON_MARSHAL_EMPTY_COLLECTIONS, Boolean.FALSE); } catch (PropertyException e) { // This is likely not the EclipseLink Marshaller. } } // All Done. return unmarshaller.unmarshal(source, resultType).getValue(); } catch (JAXBException e) { final String dataType = assumeJSonInput ? "json" : "xml"; throw new IllegalArgumentException( "Could not unmarshal " + dataType + " into [" + resultType.getName() + "]", e); } } /** * Unmarshals without type information resulting in an Object, when no resulting type information * has been (or can be) given. * * @param loader The ClassLoader to use in order to load all classes previously added * by calls to the {@code add} method. * @param assumeJSonInput If {@code true}, assume that the input to the unmarshaller is provided in JSON - rather * than XML - form. * @param xmlToUnmarshal The XML string to unmarshal into a T object. * @return The resulting, unmarshalled object. * @see #unmarshal(ClassLoader, boolean, Class, String) */ public Object unmarshal(final ClassLoader loader, final boolean assumeJSonInput, final String xmlToUnmarshal) { return unmarshal(loader, assumeJSonInput, Object.class, xmlToUnmarshal); } /** * Maps an XML URI to a given XML namespace prefix, to yield a better/more user-friendly * marshalling of an XML structure. * * @param uri The XML URI to map, such as "http://jguru.se/some/url" or "urn:mithlond:data". * @param xmlPrefix The XML prefix to use when marshalling types using the uri for namespace. */ public void mapXmlNamespacePrefix(final String uri, final String xmlPrefix) { this.namespacePrefixResolver.put(uri, xmlPrefix); } // // Private helpers // private <C extends Collection<String>> Class[] getClasses(final ClassLoader loader, final C input) { final ClassLoader effectiveClassLoader = loader == null ? PlainJaxbContextRule.class.getClassLoader() : loader; final SortedMap<String, Class<?>> name2ClassMap = new TreeMap<>(); if (input != null) { for (String current : input) { try { final Class<?> aClass = effectiveClassLoader.loadClass(current); if (aClass != null) { name2ClassMap.put(aClass.getName(), aClass); } } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Could not load class for [" + current + "]", e); } } } // Add any explicitly added classes to the JAXBContext for (Class<?> current : this.jaxbAnnotatedClasses) { name2ClassMap.put(current.getName(), current); } // Remove any ignored classes. final List<Class<?>> classList = name2ClassMap.values().stream().filter(ignoredClassFilter) .collect(Collectors.toList()); // All done. return classList.toArray(new Class<?>[classList.size()]); } /** * Simple {@link SchemaOutputResolver} implementation intended for JSON Schema generation using EclipseLink's * JAXBContext implementation ("Moxy"). */ public static class SimpleSchemaOutputResolver extends SchemaOutputResolver { // Internal state private StringWriter stringWriter = new StringWriter(); /** * {@inheritDoc} */ @Override public Result createOutput(final String namespaceURI, final String suggestedFileName) throws IOException { // Delegate to a StreamResult. final StreamResult result = new StreamResult(stringWriter); result.setSystemId(suggestedFileName); return result; } /** * Retrieves the Schema source in String form. * * @return the Schema source in String form. */ public String getSchema() { return stringWriter.toString(); } } private void handleNamespacePrefixMapper() { if (jaxbContext instanceof org.eclipse.persistence.jaxb.JAXBContext) { // Create an EclipseLink-compliant NamespacePrefix mapper. final SortedMap<String, String> uri2PrefixMap = new TreeMap<>(); final MapNamespacePrefixMapper eclipseLinkMapper = new MapNamespacePrefixMapper(uri2PrefixMap); // Copy each URI to Prefix entry. namespacePrefixResolver.getRegisteredNamespaceURIs().forEach(c -> { uri2PrefixMap.put(c, namespacePrefixResolver.getXmlPrefix(c)); }); // Replace the RI namespace mapping properties with the EclipseLink equivalents. marshallerProperties.remove(RI_NAMESPACE_PREFIX_MAPPER_PROPERTY); marshallerProperties.put(ECLIPSELINK_NAMESPACE_PREFIX_MAPPER_PROPERTY, eclipseLinkMapper); unMarshallerProperties.remove(RI_NAMESPACE_PREFIX_MAPPER_PROPERTY); unMarshallerProperties.put(ECLIPSELINK_NAMESPACE_PREFIX_MAPPER_PROPERTY, eclipseLinkMapper); } } /** * Acquires a JAXB Schema from the provided JAXBContext. * * @param ctx The context for which am XSD should be constructed. * @return A tuple holding the constructed XSD from the provided JAXBContext, and * the LSResourceResolver synthesized during the way. * @throws NullPointerException if ctx was {@code null}. * @throws IllegalArgumentException if a JAXB-related exception occurred while extracting the schema. */ public static Tuple<Schema, LSResourceResolver> generateTransientXSD(final JAXBContext ctx) throws NullPointerException, IllegalArgumentException { // Check sanity org.apache.commons.lang3.Validate.notNull(ctx, "Cannot handle null ctx argument."); final SortedMap<String, ByteArrayOutputStream> namespace2SchemaMap = new TreeMap<>(); try { ctx.generateSchema(new SchemaOutputResolver() { /** * {@inheritDoc} */ @Override public Result createOutput(final String namespaceUri, final String suggestedFileName) throws IOException { // The types should really be annotated with @XmlType(namespace = "... something ...") // to avoid using the default ("") namespace. if (namespaceUri.isEmpty()) { log.warn("Received empty namespaceUri while resolving a generated schema. " + "Did you forget to add a @XmlType(namespace = \"... something ...\") annotation " + "to your class?"); } // Create the result ByteArrayOutputStream final ByteArrayOutputStream out = new ByteArrayOutputStream(); final StreamResult toReturn = new StreamResult(out); toReturn.setSystemId(""); // Map the namespaceUri to the schemaResult. namespace2SchemaMap.put(namespaceUri, out); // All done. return toReturn; } }); } catch (IOException e) { throw new IllegalArgumentException("Could not acquire Schema snippets.", e); } // Convert to an array of StreamSource. final MappedSchemaResourceResolver resourceResolver = new MappedSchemaResourceResolver(); final StreamSource[] schemaSources = new StreamSource[namespace2SchemaMap.size()]; int counter = 0; for (Map.Entry<String, ByteArrayOutputStream> current : namespace2SchemaMap.entrySet()) { final byte[] schemaSnippetAsBytes = current.getValue().toByteArray(); resourceResolver.addNamespace2SchemaEntry(current.getKey(), new String(schemaSnippetAsBytes)); if (log.isDebugEnabled()) { log.info("Generated schema [" + (counter + 1) + "/" + schemaSources.length + "]:\n " + new String(schemaSnippetAsBytes)); } // Copy the schema source to the schemaSources array. schemaSources[counter] = new StreamSource(new ByteArrayInputStream(schemaSnippetAsBytes), ""); // Increase the counter counter++; } try { // All done. final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); schemaFactory.setResourceResolver(resourceResolver); final Schema transientSchema = schemaFactory.newSchema(schemaSources); // All done. return new Tuple<>(transientSchema, resourceResolver); } catch (final SAXException e) { throw new IllegalArgumentException("Could not create Schema from snippets.", e); } } }