/*
* Copyright (c) 2014 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package io.werval.modules.xml;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.validation.SchemaFactory;
import javax.xml.xpath.XPathFactory;
import io.werval.api.Application;
import io.werval.api.Config;
import io.werval.api.Plugin;
import io.werval.api.exceptions.ActivationException;
import io.werval.api.exceptions.WervalException;
import io.werval.modules.xml.internal.CatalogResolver;
import io.werval.modules.xml.internal.EmptyResolver;
import io.werval.modules.xml.internal.Internal;
import io.werval.modules.xml.internal.ThrowingResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import static java.util.Collections.EMPTY_LIST;
import static io.werval.util.Strings.NEWLINE;
/**
* XML Plugin.
*/
// TODO http://apache.org/xml/features/xinclude
public final class XMLPlugin
implements Plugin<XML>
{
private static final Logger LOG = LoggerFactory.getLogger( XMLPlugin.class );
static
{
System.out.println( System.getProperty( "javax.xml.stream.allocator" ) );
if( LOG.isTraceEnabled() )
{
System.setProperty( "jaxp.debug", "yes" );
}
if( false )
{
// Woodstox for stream pull parsing (StAX)
System.setProperty(
"javax.xml.stream.XMLEventFactory",
io.werval.modules.xml.internal.XMLEventFactoryImpl.class.getName()
);
System.setProperty(
"javax.xml.stream.XMLInputFactory",
io.werval.modules.xml.internal.XMLInputFactoryImpl.class.getName()
);
System.setProperty(
"javax.xml.stream.XMLOutputFactory",
io.werval.modules.xml.internal.XMLOutputFactoryImpl.class.getName()
);
// Xerces for stream push parsing (SAX), DOM and Schema validation
System.setProperty(
"javax.xml.parsers.SAXParserFactory",
io.werval.modules.xml.internal.SAXParserFactoryImpl.class.getName()
);
System.setProperty(
"javax.xml.datatype.DatatypeFactory",
io.werval.modules.xml.internal.DatatypeFactoryImpl.class.getName()
);
System.setProperty(
"javax.xml.parsers.DocumentBuilderFactory",
io.werval.modules.xml.internal.DocumentBuilderFactoryImpl.class.getName()
);
System.setProperty(
"javax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema",
io.werval.modules.xml.internal.SchemaFactoryXSD.class.getName()
);
System.setProperty(
"javax.xml.validation.SchemaFactory:http://www.w3.org/XML/XMLSchema/v1.1",
io.werval.modules.xml.internal.SchemaFactoryXSD.class.getName()
);
System.setProperty(
"javax.xml.validation.SchemaFactory:http://relaxng.org/ns/structure/1.",
io.werval.modules.xml.internal.SchemaFactoryRelaxNG.class.getName()
);
// Saxon for XSLT, XSLT2, XPath and XQuery
System.setProperty(
"javax.xml.transform.TransformerFactory",
io.werval.modules.xml.internal.TransformerFactoryImpl.class.getName()
);
System.setProperty(
"javax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom",
io.werval.modules.xml.internal.XPathFactoryImpl.class.getName()
);
}
}
private XML xml;
@Override
public Class<XML> apiType()
{
return XML.class;
}
@Override
public XML api()
{
return xml;
}
@Override
public void onActivate( Application application )
throws ActivationException
{
Config config = application.config().atKey( "xml" );
List<String> catalogs = new ArrayList<>();
for( String catalog : config.stringList( "external_entities.catalogs" ) )
{
try
{
catalogs.add( new URL( catalog ).toExternalForm() );
}
catch( MalformedURLException ex )
{
catalogs.add( application.classLoader().getResource( catalog ).toExternalForm() );
}
}
boolean catalogsPreferPublic = config.bool( "external_entities.catalogs_prefer_public" );
switch( config.string( "external_entities.resolver" ) )
{
case "throw":
Internal.EXTERNAL_ENTITIES.set( false );
Internal.RESOLVER.set( ThrowingResolver.INSTANCE );
break;
case "empty":
Internal.EXTERNAL_ENTITIES.set( true );
Internal.RESOLVER.set( EmptyResolver.INSTANCE );
break;
case "catalogs-only":
Internal.EXTERNAL_ENTITIES.set( true );
Internal.RESOLVER.set( new CatalogResolver( true, catalogs, catalogsPreferPublic ) );
break;
case "catalogs-first-unsafe":
Internal.EXTERNAL_ENTITIES.set( true );
Internal.RESOLVER.set( new CatalogResolver( false, catalogs, catalogsPreferPublic ) );
break;
case "no-catalog-unsafe":
Internal.EXTERNAL_ENTITIES.set( true );
Internal.RESOLVER.set( new CatalogResolver( false, EMPTY_LIST, catalogsPreferPublic ) );
break;
default:
throw new WervalException(
String.format(
"Uknown value for 'xml.external_entities' config property: %s",
config.string( "external_entities" )
)
);
}
ensureXmlApisImplementations( true );
xml = new XML( application.defaultCharset() );
}
@Override
public void onPassivate( Application application )
{
xml = null;
Internal.EXTERNAL_ENTITIES.set( false );
Internal.RESOLVER.set( ThrowingResolver.INSTANCE );
}
private static void ensureXmlApisImplementations( boolean failOnError )
throws ActivationException
{
try
{
XMLEventFactory xmlEventFactory = XMLEventFactory.newInstance();
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
TransformerFactory transformerFactory = TransformerFactory.newInstance();
XPathFactory xPathFactory = XPathFactory.newInstance();
boolean wrong = Arrays.asList(
xmlEventFactory, xmlInputFactory, xmlOutputFactory, saxParserFactory,
datatypeFactory, docBuilderFactory, schemaFactory,
transformerFactory, xPathFactory
).stream().anyMatch( c -> !c.getClass().getPackage().equals( Internal.class.getPackage() ) );
if( LOG.isTraceEnabled() || wrong )
{
StringBuilder sb = new StringBuilder();
sb.append( " Support for XML 1.0" );
if( saxParserFactory.getFeature( SAX.Features.XML_1_1 ) )
{
sb.append( " & XML 1.1" );
}
sb.append( NEWLINE ).append( NEWLINE );
sb.append( " StAX" ).append( NEWLINE );
sb.append( " XMLEventFactory " ).append( xmlEventFactory.getClass() ).append( NEWLINE );
sb.append( " XMLInputFactory " ).append( xmlInputFactory.getClass() ).append( NEWLINE );
sb.append( " XMLOutputFactory " ).append( xmlOutputFactory.getClass() ).append( NEWLINE );
sb.append( " SAX, DOM & Schema" ).append( NEWLINE );
sb.append( " SAXParserFactory " ).append( saxParserFactory.getClass() ).append( NEWLINE );
sb.append( " DatatypeFactory " ).append( datatypeFactory.getClass() ).append( NEWLINE );
sb.append( " DocumentBuilderFactory " ).append( docBuilderFactory.getClass() ).append( NEWLINE );
sb.append( " SchemaFactory " ).append( schemaFactory.getClass() ).append( NEWLINE );
sb.append( " XSLT & XPath/XQuery" ).append( NEWLINE );
sb.append( " TransformerFactory " ).append( transformerFactory.getClass() ).append( NEWLINE );
sb.append( " XPathFactory " ).append( xPathFactory.getClass() ).append( NEWLINE );
sb.append( " Entity Resolving" ).append( NEWLINE );
sb.append( " XML.Resolver " ).append( Internal.RESOLVER.get().getClass() );
sb.append( NEWLINE ).append( NEWLINE );
if( wrong )
{
sb.append( NEWLINE );
sb.append( " All implementations should be under the " )
.append( XMLPlugin.class.getPackage().getName() )
.append( " package." ).append( NEWLINE );
sb.append( " Watch out for superfluous XML libraries in your classpath!" )
.append( NEWLINE );
}
String report = sb.toString();
if( wrong && failOnError )
{
throw new WervalException( "XML Plugin JAXP setup failure!\n\n" + report );
}
if( wrong )
{
LOG.warn( "XML Plugin JAXP setup failure!\n\n{}", report );
}
else
{
LOG.trace( "XML Plugin Runtime Summary\n\n{}", report );
}
}
}
catch( DatatypeConfigurationException | ParserConfigurationException | SAXException ex )
{
throw new ActivationException( ex );
}
}
}