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,74 +1509,6 @@ public class XmlUtil {
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
}
/**
* Parses an XML string into a set of StAX events
*/
public static List<XMLEvent> parse(String theValue) {
if (isBlank(theValue)) {
return Collections.emptyList();
}
String val = theValue.trim();
if (!val.startsWith("<")) {
val = XhtmlDt.DIV_OPEN_FIRST + val + "</div>";
}
boolean hasProcessingInstruction = val.startsWith("<?");
if (hasProcessingInstruction && val.endsWith("?>")) {
return null;
}
try {
ArrayList<XMLEvent> value = new ArrayList<>();
StringReader reader = new StringReader(val);
XMLEventReader er = XmlUtil.createXmlReader(reader);
boolean first = true;
while (er.hasNext()) {
XMLEvent next = er.nextEvent();
if (first) {
first = false;
continue;
}
if (er.hasNext()) {
// don't add the last event
value.add(next);
}
}
return value;
} catch (XMLStreamException e) {
throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e);
} catch (FactoryConfigurationError e) {
throw new ConfigurationException(e);
}
}
/**
* 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
@ -1638,6 +1569,30 @@ public class XmlUtil {
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) {
@ -1723,6 +1678,7 @@ public class XmlUtil {
return ourOutputFactory;
}
private static void logStaxImplementation(Class<?> theClass) {
IDependencyLog logger = DependencyLogFactory.createJarLogger();
if (logger != null) {
@ -1754,6 +1710,49 @@ public class XmlUtil {
return outputFactory;
}
/**
* Parses an XML string into a set of StAX events
*/
public static List<XMLEvent> parse(String theValue) {
if (isBlank(theValue)) {
return Collections.emptyList();
}
String val = theValue.trim();
if (!val.startsWith("<")) {
val = XhtmlDt.DIV_OPEN_FIRST + val + "</div>";
}
boolean hasProcessingInstruction = val.startsWith("<?");
if (hasProcessingInstruction && val.endsWith("?>")) {
return null;
}
try {
ArrayList<XMLEvent> value = new ArrayList<>();
StringReader reader = new StringReader(val);
XMLEventReader er = XmlUtil.createXmlReader(reader);
boolean first = true;
while (er.hasNext()) {
XMLEvent next = er.nextEvent();
if (first) {
first = false;
continue;
}
if (er.hasNext()) {
// don't add the last event
value.add(next);
}
}
return value;
} catch (XMLStreamException e) {
throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e);
} catch (FactoryConfigurationError e) {
throw new ConfigurationException(e);
}
}
/**
* FOR UNIT TESTS ONLY - Throw this exception for the next operation
*/
@ -1783,26 +1782,6 @@ public class XmlUtil {
}
}
/**
* 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 {
@Override

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