Remove dependencies on StAX on Android

This commit is contained in:
James 2017-10-30 16:41:31 -04:00
parent 7c1ab11b02
commit 5a5933f232
7 changed files with 275 additions and 230 deletions

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.XmlDetectionUtil;
import ca.uhn.fhir.util.XmlUtil;
import java.util.List;
@ -77,7 +78,7 @@ public class XhtmlDt extends BasePrimitive<String> {
@Override
protected String parse(String theValue) {
if (XmlUtil.isStaxPresent()) {
if (XmlDetectionUtil.isStaxPresent()) {
// for validation
XmlUtil.parse(theValue);
}

View File

@ -0,0 +1,57 @@
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* 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.
* #L%
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Reader;
import java.io.StringReader;
public class XmlDetectionUtil {
private static final Logger ourLog = LoggerFactory.getLogger(XmlDetectionUtil.class);
private static Boolean ourStaxPresent;
/**
* This method will return <code>true</code> if a StAX XML parsing library is present
* on the classpath
*/
public static boolean isStaxPresent() {
Boolean retVal = ourStaxPresent;
if (retVal == null) {
try {
Class.forName("javax.xml.stream.events.XMLEvent");
Class<?> xmlUtilClazz = Class.forName("ca.uhn.fhir.util.XmlUtil");
xmlUtilClazz.getMethod("createXmlReader", Reader.class).invoke(xmlUtilClazz, new StringReader(""));
ourStaxPresent = Boolean.TRUE;
retVal = Boolean.TRUE;
} catch (Throwable t) {
ourLog.info("StAX not detected on classpath, XML processing will be disabled");
ourStaxPresent = Boolean.FALSE;
retVal = Boolean.FALSE;
}
}
return retVal;
}
}

View File

@ -46,15 +46,14 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* This class contains code adapted from the Apache Axiom project.
*/
public class XmlUtil {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class);
private static final Map<String, Integer> VALID_ENTITY_NAMES;
private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver();
private static XMLOutputFactory ourFragmentOutputFactory;
private static volatile boolean ourHaveLoggedStaxImplementation;
private static volatile XMLInputFactory ourInputFactory;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class);
private static Throwable ourNextException;
private static volatile XMLOutputFactory ourOutputFactory;
private static Boolean ourStaxPresent;
private static final Map<String, Integer> VALID_ENTITY_NAMES;
private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver();
static {
HashMap<String, Integer> validEntityNames = new HashMap<String, Integer>(1448);
@ -1510,6 +1509,207 @@ public class XmlUtil {
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
}
private static XMLOutputFactory createOutputFactory() throws FactoryConfigurationError {
try {
// Detect if we're running with the Android lib, and force repackaged Woodstox to be used
Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLOutputFactory");
System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory");
} catch (ClassNotFoundException e) {
// ok
}
XMLOutputFactory outputFactory = newOutputFactory();
if (!ourHaveLoggedStaxImplementation) {
logStaxImplementation(outputFactory.getClass());
}
/*
* Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is
* being used (e.g. glassfish) so we don't set them there.
*/
try {
Class.forName("com.ctc.wstx.stax.WstxOutputFactory");
if (outputFactory instanceof WstxOutputFactory) {
outputFactory.setProperty(XMLOutputFactory2.P_TEXT_ESCAPER, new MyEscaper());
}
} catch (ClassNotFoundException e) {
ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath");
}
return outputFactory;
}
public static XMLEventWriter createXmlFragmentWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
XMLOutputFactory outputFactory = getOrCreateFragmentOutputFactory();
XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter);
return retVal;
}
public static XMLEventReader createXmlReader(Reader reader) throws FactoryConfigurationError, XMLStreamException {
throwUnitTestExceptionIfConfiguredToDoSo();
XMLInputFactory inputFactory = getOrCreateInputFactory();
// Now.. create the reader and return it
XMLEventReader er = inputFactory.createXMLEventReader(reader);
return er;
}
public static XMLStreamWriter createXmlStreamWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
throwUnitTestExceptionIfConfiguredToDoSo();
XMLOutputFactory outputFactory = getOrCreateOutputFactory();
XMLStreamWriter retVal = outputFactory.createXMLStreamWriter(theWriter);
return retVal;
}
public static XMLEventWriter createXmlWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
XMLOutputFactory outputFactory = getOrCreateOutputFactory();
XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter);
return retVal;
}
/**
* Encode a set of StAX events into a String
*/
public static String encode(List<XMLEvent> theEvents) {
try {
StringWriter w = new StringWriter();
XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w);
for (XMLEvent next : theEvents) {
if (next.isCharacters()) {
ew.add(next);
} else {
ew.add(next);
}
}
ew.close();
return w.toString();
} catch (XMLStreamException e) {
throw new DataFormatException("Problem with the contained XML events", e);
} catch (FactoryConfigurationError e) {
throw new ConfigurationException(e);
}
}
private static XMLOutputFactory getOrCreateFragmentOutputFactory() throws FactoryConfigurationError {
XMLOutputFactory retVal = ourFragmentOutputFactory;
if (retVal == null) {
retVal = createOutputFactory();
retVal.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
ourFragmentOutputFactory = retVal;
return retVal;
}
return retVal;
}
private static XMLInputFactory getOrCreateInputFactory() throws FactoryConfigurationError {
if (ourInputFactory == null) {
try {
// Detect if we're running with the Android lib, and force repackaged Woodstox to be used
Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLInputFactory");
System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory");
} catch (ClassNotFoundException e) {
// ok
}
XMLInputFactory inputFactory = newInputFactory();
if (!ourHaveLoggedStaxImplementation) {
logStaxImplementation(inputFactory.getClass());
}
/*
* These two properties disable external entity processing, which can
* be a security vulnerability.
*
* See https://github.com/jamesagnew/hapi-fhir/issues/339
* https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing
*/
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
/*
* In the following few lines, you can uncomment the first and comment the second to disable automatic
* parsing of extended entities, e.g. &sect;
*
* Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is
* being used (e.g. glassfish) so we don't set them there.
*/
try {
Class.forName("com.ctc.wstx.stax.WstxInputFactory");
boolean isWoodstox = inputFactory instanceof com.ctc.wstx.stax.WstxInputFactory;
if ( !isWoodstox )
{
// Check if implementation is woodstox by property since instanceof check does not work if running in JBoss
try
{
isWoodstox = inputFactory.getProperty( "org.codehaus.stax2.implVersion" ) != null;
}
catch ( Exception e )
{
// ignore
}
}
if (isWoodstox) {
// inputFactory.setProperty(WstxInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
inputFactory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, XML_RESOLVER);
try {
inputFactory.setProperty(WstxInputProperties.P_MAX_ATTRIBUTE_SIZE, "100000000");
} catch (IllegalArgumentException e) {
// ignore
}
}
} catch (ClassNotFoundException e) {
ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath");
}
ourInputFactory = inputFactory;
}
return ourInputFactory;
}
private static XMLOutputFactory getOrCreateOutputFactory() throws FactoryConfigurationError {
if (ourOutputFactory == null) {
ourOutputFactory = createOutputFactory();
}
return ourOutputFactory;
}
private static void logStaxImplementation(Class<?> theClass) {
IDependencyLog logger = DependencyLogFactory.createJarLogger();
if (logger != null) {
logger.logStaxImplementation(theClass);
}
ourHaveLoggedStaxImplementation = true;
}
static XMLInputFactory newInputFactory() throws FactoryConfigurationError {
XMLInputFactory inputFactory;
try {
inputFactory = XMLInputFactory.newInstance();
throwUnitTestExceptionIfConfiguredToDoSo();
} catch (Throwable e) {
throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e);
}
return inputFactory;
}
static XMLOutputFactory newOutputFactory() throws FactoryConfigurationError {
XMLOutputFactory outputFactory;
try {
outputFactory = XMLOutputFactory.newInstance();
throwUnitTestExceptionIfConfiguredToDoSo();
} catch (Throwable e) {
throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e);
}
return outputFactory;
}
/**
* Parses an XML string into a set of StAX events
*/
@ -1553,207 +1753,6 @@ public class XmlUtil {
}
}
/**
* Encode a set of StAX events into a String
*/
public static String encode(List<XMLEvent> theEvents) {
try {
StringWriter w = new StringWriter();
XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w);
for (XMLEvent next : theEvents) {
if (next.isCharacters()) {
ew.add(next);
} else {
ew.add(next);
}
}
ew.close();
return w.toString();
} catch (XMLStreamException e) {
throw new DataFormatException("Problem with the contained XML events", e);
} catch (FactoryConfigurationError e) {
throw new ConfigurationException(e);
}
}
private static XMLOutputFactory createOutputFactory() throws FactoryConfigurationError {
try {
// Detect if we're running with the Android lib, and force repackaged Woodstox to be used
Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLOutputFactory");
System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory");
} catch (ClassNotFoundException e) {
// ok
}
XMLOutputFactory outputFactory = newOutputFactory();
if (!ourHaveLoggedStaxImplementation) {
logStaxImplementation(outputFactory.getClass());
}
/*
* Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is
* being used (e.g. glassfish) so we don't set them there.
*/
try {
Class.forName("com.ctc.wstx.stax.WstxOutputFactory");
if (outputFactory instanceof WstxOutputFactory) {
outputFactory.setProperty(XMLOutputFactory2.P_TEXT_ESCAPER, new MyEscaper());
}
} catch (ClassNotFoundException e) {
ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath");
}
return outputFactory;
}
public static XMLEventWriter createXmlFragmentWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
XMLOutputFactory outputFactory = getOrCreateFragmentOutputFactory();
XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter);
return retVal;
}
public static XMLEventReader createXmlReader(Reader reader) throws FactoryConfigurationError, XMLStreamException {
throwUnitTestExceptionIfConfiguredToDoSo();
XMLInputFactory inputFactory = getOrCreateInputFactory();
// Now.. create the reader and return it
XMLEventReader er = inputFactory.createXMLEventReader(reader);
return er;
}
public static XMLStreamWriter createXmlStreamWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
throwUnitTestExceptionIfConfiguredToDoSo();
XMLOutputFactory outputFactory = getOrCreateOutputFactory();
XMLStreamWriter retVal = outputFactory.createXMLStreamWriter(theWriter);
return retVal;
}
public static XMLEventWriter createXmlWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
XMLOutputFactory outputFactory = getOrCreateOutputFactory();
XMLEventWriter retVal = outputFactory.createXMLEventWriter(theWriter);
return retVal;
}
private static XMLOutputFactory getOrCreateFragmentOutputFactory() throws FactoryConfigurationError {
XMLOutputFactory retVal = ourFragmentOutputFactory;
if (retVal == null) {
retVal = createOutputFactory();
retVal.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
ourFragmentOutputFactory = retVal;
return retVal;
}
return retVal;
}
private static XMLInputFactory getOrCreateInputFactory() throws FactoryConfigurationError {
if (ourInputFactory == null) {
try {
// Detect if we're running with the Android lib, and force repackaged Woodstox to be used
Class.forName("ca.uhn.fhir.repackage.javax.xml.stream.XMLInputFactory");
System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory");
} catch (ClassNotFoundException e) {
// ok
}
XMLInputFactory inputFactory = newInputFactory();
if (!ourHaveLoggedStaxImplementation) {
logStaxImplementation(inputFactory.getClass());
}
/*
* These two properties disable external entity processing, which can
* be a security vulnerability.
*
* See https://github.com/jamesagnew/hapi-fhir/issues/339
* https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing
*/
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
/*
* In the following few lines, you can uncomment the first and comment the second to disable automatic
* parsing of extended entities, e.g. &sect;
*
* Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is
* being used (e.g. glassfish) so we don't set them there.
*/
try {
Class.forName("com.ctc.wstx.stax.WstxInputFactory");
boolean isWoodstox = inputFactory instanceof com.ctc.wstx.stax.WstxInputFactory;
if ( !isWoodstox )
{
// Check if implementation is woodstox by property since instanceof check does not work if running in JBoss
try
{
isWoodstox = inputFactory.getProperty( "org.codehaus.stax2.implVersion" ) != null;
}
catch ( Exception e )
{
// ignore
}
}
if (isWoodstox) {
// inputFactory.setProperty(WstxInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
inputFactory.setProperty(WstxInputProperties.P_UNDECLARED_ENTITY_RESOLVER, XML_RESOLVER);
try {
inputFactory.setProperty(WstxInputProperties.P_MAX_ATTRIBUTE_SIZE, "100000000");
} catch (IllegalArgumentException e) {
// ignore
}
}
} catch (ClassNotFoundException e) {
ourLog.debug("WstxOutputFactory (Woodstox) not found on classpath");
}
ourInputFactory = inputFactory;
}
return ourInputFactory;
}
private static XMLOutputFactory getOrCreateOutputFactory() throws FactoryConfigurationError {
if (ourOutputFactory == null) {
ourOutputFactory = createOutputFactory();
}
return ourOutputFactory;
}
private static void logStaxImplementation(Class<?> theClass) {
IDependencyLog logger = DependencyLogFactory.createJarLogger();
if (logger != null) {
logger.logStaxImplementation(theClass);
}
ourHaveLoggedStaxImplementation = true;
}
static XMLInputFactory newInputFactory() throws FactoryConfigurationError {
XMLInputFactory inputFactory;
try {
inputFactory = XMLInputFactory.newInstance();
throwUnitTestExceptionIfConfiguredToDoSo();
} catch (Throwable e) {
throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e);
}
return inputFactory;
}
static XMLOutputFactory newOutputFactory() throws FactoryConfigurationError {
XMLOutputFactory outputFactory;
try {
outputFactory = XMLOutputFactory.newInstance();
throwUnitTestExceptionIfConfiguredToDoSo();
} catch (Throwable e) {
throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e);
}
return outputFactory;
}
/**
* FOR UNIT TESTS ONLY - Throw this exception for the next operation
*/
@ -1782,26 +1781,6 @@ public class XmlUtil {
return null;
}
}
/**
* This method will return <code>true</code> if a StAX XML parsing library is present
* on the classpath
*/
public static boolean isStaxPresent() {
Boolean retVal = ourStaxPresent;
if (retVal == null) {
try {
newInputFactory();
ourStaxPresent = Boolean.TRUE;
retVal = Boolean.TRUE;
} catch (ConfigurationException e) {
ourLog.info("StAX not detected on classpath, XML processing will be disabled");
ourStaxPresent = Boolean.FALSE;
retVal = Boolean.FALSE;
}
}
return retVal;
}
public static class MyEscaper implements EscapingWriterFactory {

View File

@ -35,6 +35,7 @@ import java.util.Map;
import java.util.Set;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.util.XmlDetectionUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
@ -110,7 +111,7 @@ public abstract class BaseClient implements IRestfulClient {
setKeepResponses(true);
}
if (XmlUtil.isStaxPresent() == false) {
if (XmlDetectionUtil.isStaxPresent() == false) {
myEncoding = EncodingEnum.JSON;
}

View File

@ -576,11 +576,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
} catch (InterruptedException theE) {
// ignore
}
} else {
ourLog.info("Proceeding, as we have {} results", mySyncedPids.size());
}
} while (keepWaiting);
ourLog.info("Proceeding, as we have {} results", mySyncedPids.size());
ArrayList<Long> retVal = new ArrayList<>();
synchronized (mySyncedPids) {
verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
@ -594,6 +594,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
}
}
ourLog.info("Done syncing results", mySyncedPids.size());
return retVal;
}

View File

@ -189,6 +189,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp));
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain();

View File

@ -26,7 +26,11 @@
<![CDATA[<br/><br/>]]>
See the
<![CDATA[<a href="http://hapifhir.io/doc_android.html">HAPI FHIR Android Documentation</a>]]>
for more information.
for more information. As a part of this fix, all dependencies on
the StAX API have been removed in environments where StAX is not
present (such as Android). The client will now detect this case, and
explicitly request JSON payloads from servers, meaning that Android clients
no longer need to include two parser stacks
</action>
<action type="add">
A performance to the JPA server has been made which reduces the number