keycloak-memoizeit

Merge pull request #3707 from hmlnarik/KEYCLOAK-4148-AbstractParser-should-do-the-same KEYCLOAK-4148

1/6/2017 11:44:41 AM

Details

diff --git a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 3a5867b..7e7daa5 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -21,6 +21,7 @@ import org.keycloak.saml.common.PicketLinkLogger;
 import org.keycloak.saml.common.PicketLinkLoggerFactory;
 import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.util.SecurityActions;
 import org.keycloak.saml.common.util.StaxParserUtil;
 import org.keycloak.saml.common.util.SystemPropertiesUtil;
 
@@ -32,8 +33,9 @@ import javax.xml.stream.events.Characters;
 import javax.xml.stream.events.XMLEvent;
 import javax.xml.stream.util.EventReaderDelegate;
 import java.io.InputStream;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMSource;
+import org.w3c.dom.Node;
 
 /**
  * Base class for parsers
@@ -45,26 +47,33 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
 
     protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
 
-    /**
-     * Get the JAXP {@link XMLInputFactory}
-     *
-     * @return
-     */
-    protected XMLInputFactory getXMLInputFactory() {
-        boolean tccl_jaxp = SystemPropertiesUtil.getSystemProperty(GeneralConstants.TCCL_JAXP, "false")
-                .equalsIgnoreCase("true");
-        ClassLoader prevTCCL = getTCCL();
-        try {
-            if (tccl_jaxp) {
-                setTCCL(getClass().getClassLoader());
-            }
-            return XMLInputFactory.newInstance();
-        } finally {
-            if (tccl_jaxp) {
-                setTCCL(prevTCCL);
+    private static final ThreadLocal<XMLInputFactory> XML_INPUT_FACTORY = new ThreadLocal<XMLInputFactory>() {
+        @Override
+        protected XMLInputFactory initialValue() {
+            return getXMLInputFactory();
+        }
+
+        /**
+         * Get the JAXP {@link XMLInputFactory}
+         *
+         * @return
+         */
+        private XMLInputFactory getXMLInputFactory() {
+            boolean tccl_jaxp = SystemPropertiesUtil.getSystemProperty(GeneralConstants.TCCL_JAXP, "false")
+                    .equalsIgnoreCase("true");
+            ClassLoader prevTCCL = SecurityActions.getTCCL();
+            try {
+                if (tccl_jaxp) {
+                    SecurityActions.setTCCL(AbstractParser.class.getClassLoader());
+                }
+                return XMLInputFactory.newInstance();
+            } finally {
+                if (tccl_jaxp) {
+                    SecurityActions.setTCCL(prevTCCL);
+                }
             }
         }
-    }
+    };
 
     /**
      * Parse an InputStream for payload
@@ -81,6 +90,15 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
         return parse(xmlEventReader);
     }
 
+    public Object parse(Source source) throws ParsingException {
+        XMLEventReader xmlEventReader = createEventReader(source);
+        return parse(xmlEventReader);
+    }
+
+    public Object parse(Node node) throws ParsingException {
+        return parse(new DOMSource(node));
+    }
+
     public XMLEventReader createEventReader(InputStream configStream) throws ParsingException {
         if (configStream == null)
             throw logger.nullArgumentError("InputStream");
@@ -96,33 +114,23 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
         return xmlEventReader;
     }
 
-    private ClassLoader getTCCL() {
-        if (System.getSecurityManager() != null) {
-            return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
-                public ClassLoader run() {
-                    return Thread.currentThread().getContextClassLoader();
-                }
-            });
-        } else {
-            return Thread.currentThread().getContextClassLoader();
-        }
-    }
+    public XMLEventReader createEventReader(Source source) throws ParsingException {
+        if (source == null)
+            throw logger.nullArgumentError("Source");
 
-    private void setTCCL(final ClassLoader paramCl) {
-        if (System.getSecurityManager() != null) {
-            AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    Thread.currentThread().setContextClassLoader(paramCl);
-                    return null;
-                }
-            });
-        } else {
-            Thread.currentThread().setContextClassLoader(paramCl);
+        XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(source);
+
+        try {
+            xmlEventReader = filterWhitespaces(xmlEventReader);
+        } catch (XMLStreamException e) {
+            throw logger.parserException(e);
         }
+
+        return xmlEventReader;
     }
 
     protected XMLEventReader filterWhitespaces(XMLEventReader xmlEventReader) throws XMLStreamException {
-        XMLInputFactory xmlInputFactory = getXMLInputFactory();
+        XMLInputFactory xmlInputFactory = XML_INPUT_FACTORY.get();
 
         xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
             public boolean accept(XMLEvent xmlEvent) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
index d9dd4e0..b5f2232 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
@@ -97,10 +97,9 @@ public class DocumentUtil {
      * @throws ParserConfigurationException
      */
     public static Document createDocument() throws ConfigurationException {
-        DocumentBuilderFactory factory = getDocumentBuilderFactory();
         DocumentBuilder builder;
         try {
-            builder = factory.newDocumentBuilder();
+            builder = getDocumentBuilder();
         } catch (ParserConfigurationException e) {
             throw new ConfigurationException(e);
         }
@@ -118,8 +117,7 @@ public class DocumentUtil {
      */
     public static Document createDocumentWithBaseNamespace(String baseNamespace, String localPart) throws ProcessingException {
         try {
-            DocumentBuilderFactory factory = getDocumentBuilderFactory();
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.getDOMImplementation().createDocument(baseNamespace, localPart, null);
         } catch (DOMException e) {
             throw logger.processingError(e);
@@ -157,8 +155,7 @@ public class DocumentUtil {
      */
     public static Document getDocument(Reader reader) throws ConfigurationException, ProcessingException, ParsingException {
         try {
-            DocumentBuilderFactory factory = getDocumentBuilderFactory();
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.parse(new InputSource(reader));
         } catch (ParserConfigurationException e) {
             throw logger.configurationError(e);
@@ -181,9 +178,8 @@ public class DocumentUtil {
      * @throws SAXException
      */
     public static Document getDocument(File file) throws ConfigurationException, ProcessingException, ParsingException {
-        DocumentBuilderFactory factory = getDocumentBuilderFactory();
         try {
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.parse(file);
         } catch (ParserConfigurationException e) {
             throw logger.configurationError(e);
@@ -206,9 +202,8 @@ public class DocumentUtil {
      * @throws SAXException
      */
     public static Document getDocument(InputStream is) throws ConfigurationException, ProcessingException, ParsingException {
-        DocumentBuilderFactory factory = getDocumentBuilderFactory();
         try {
-            DocumentBuilder builder = factory.newDocumentBuilder();
+            DocumentBuilder builder = getDocumentBuilder();
             return builder.parse(is);
         } catch (ParserConfigurationException e) {
             throw logger.configurationError(e);
@@ -502,6 +497,25 @@ public class DocumentUtil {
         }
     }
 
+    private static final ThreadLocal<DocumentBuilder> XML_DOCUMENT_BUILDER = new ThreadLocal<DocumentBuilder>() {
+        @Override
+        protected DocumentBuilder initialValue() {
+            DocumentBuilderFactory factory = getDocumentBuilderFactory();
+            try {
+                return factory.newDocumentBuilder();
+            } catch (ParserConfigurationException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+    };
+
+    private static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilder res = XML_DOCUMENT_BUILDER.get();
+        res.reset();
+        return res;
+    }
+
     /**
      * <p> Creates a namespace aware {@link DocumentBuilderFactory}. The returned instance is cached and shared between
      * different threads. </p>
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java b/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java
index c9c18a4..b36adb4 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/SecurityActions.java
@@ -26,7 +26,7 @@ import java.security.PrivilegedAction;
  * @author Anil.Saldhana@redhat.com
  * @since Dec 9, 2008
  */
-class SecurityActions {
+public class SecurityActions {
 
     /**
      * <p> Loads a {@link Class} using the <code>fullQualifiedName</code> supplied. This method tries first to load from
@@ -186,7 +186,7 @@ class SecurityActions {
      *
      * @return
      */
-    static ClassLoader getTCCL() {
+    public static ClassLoader getTCCL() {
         if (System.getSecurityManager() != null) {
             return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                 public ClassLoader run() {
@@ -203,7 +203,7 @@ class SecurityActions {
      *
      * @param paramCl
      */
-    static void setTCCL(final ClassLoader paramCl) {
+    public static void setTCCL(final ClassLoader paramCl) {
         if (System.getSecurityManager() != null) {
             AccessController.doPrivileged(new PrivilegedAction<Void>() {
                 public Void run() {
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
index 1b06e9d..f0f6e2b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
@@ -24,6 +24,8 @@ import org.keycloak.saml.common.constants.JBossSAMLConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -47,6 +49,7 @@ import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
 import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Utility for the stax based parser
@@ -250,6 +253,34 @@ public class StaxParserUtil {
         return xmlEventReader;
     }
 
+    private static AtomicBoolean XML_EVENT_READER_ON_SOURCE_SUPPORTED = new AtomicBoolean(true);
+
+    /**
+     * Get the XML event reader
+     *
+     * @param source
+     *
+     * @return
+     */
+    public static XMLEventReader getXMLEventReader(Source source) {
+        XMLInputFactory xmlInputFactory = XML_INPUT_FACTORY.get();
+        try {
+            if (XML_EVENT_READER_ON_SOURCE_SUPPORTED.get()) {
+                try {
+                    // The following method is optional per specification
+                    return xmlInputFactory.createXMLEventReader(source);
+                } catch (UnsupportedOperationException ex) {
+                    XML_EVENT_READER_ON_SOURCE_SUPPORTED.set(false);
+                    return getXMLEventReader(source);
+                }
+            } else {
+                return getXMLEventReader(DocumentUtil.getSourceAsStream(source));
+            }
+        } catch (ConfigurationException | ProcessingException | XMLStreamException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     /**
      * Given a {@code Location}, return a formatted string [lineNum,colNum]
      *
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
index f582ae0..23fb45f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
@@ -59,6 +59,9 @@ public class StringUtil {
         return str == null || str.isEmpty();
     }
 
+    private static final Pattern PROPERTY_REPLACEMENT = Pattern.compile("(.*?)"    + "\\$\\{(.*?)"   + "(?:::(.*?))?\\}");
+                                                                      // 1: PREFIX | START  2: NAME  |       3: OPTIONAL DEFAULT VALUE
+
     /**
      * <p>
      * Get the system property value if the string is of the format ${sysproperty}
@@ -84,37 +87,25 @@ public class StringUtil {
     public static String getSystemPropertyAsString(String str) {
         if (str == null)
             throw logger.nullArgumentError("str");
-        if (str.contains("${")) {
-            Pattern pattern = Pattern.compile("\\$\\{([^}]+)}");
-            Matcher matcher = pattern.matcher(str);
-
-            StringBuffer buffer = new StringBuffer();
-            String sysPropertyValue = null;
-
-            while (matcher.find()) {
-                String subString = matcher.group(1);
-                String defaultValue = "";
-
-                // Look for default value
-                if (subString.contains("::")) {
-                    int index = subString.indexOf("::");
-                    defaultValue = subString.substring(index + 2);
-                    subString = subString.substring(0, index);
-                }
-                sysPropertyValue = SecurityActions.getSystemProperty(subString, defaultValue);
-                if (sysPropertyValue.isEmpty()) {
-                    throw logger.systemPropertyMissingError(matcher.group(1));
-                }else{
-                    // sanitize the value before we use append-and-replace
-                    sysPropertyValue = Matcher.quoteReplacement(sysPropertyValue);
-                }
-                matcher.appendReplacement(buffer, sysPropertyValue);
+
+        Matcher m = PROPERTY_REPLACEMENT.matcher(str);
+        StringBuilder sb = new StringBuilder();
+        int lastPosition = 0;
+        while (m.find()) {
+            String propertyName = m.group(2);
+            String defaultValue = m.group(3);
+
+            String sysPropertyValue = SecurityActions.getSystemProperty(propertyName, defaultValue);
+            if (sysPropertyValue.isEmpty()) {
+                throw logger.systemPropertyMissingError(propertyName);
             }
 
-            matcher.appendTail(buffer);
-            str = buffer.toString();
+            sb.append(m.group(1)).append(sysPropertyValue);
+
+            lastPosition = m.end();
         }
-        return str;
+
+        return sb.append(str.substring(lastPosition)).toString();
     }
 
     /**
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
index 9830482..2bfa41f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
@@ -165,7 +165,7 @@ public class SAML2Request {
 
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
-        SAML2Object requestType = (SAML2Object) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        SAML2Object requestType = (SAML2Object) samlParser.parse(samlDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(requestType, samlDocument);
         return requestType;
@@ -192,7 +192,7 @@ public class SAML2Request {
 
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
-        RequestAbstractType requestType = (RequestAbstractType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        RequestAbstractType requestType = (RequestAbstractType) samlParser.parse(samlDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(requestType, samlDocument);
         return requestType;
@@ -220,7 +220,7 @@ public class SAML2Request {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
 
-        AuthnRequestType requestType = (AuthnRequestType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        AuthnRequestType requestType = (AuthnRequestType) samlParser.parse(samlDocument);
         samlDocumentHolder = new SAMLDocumentHolder(requestType, samlDocument);
         return requestType;
     }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
index 76155fe..a650c7a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
@@ -376,7 +376,7 @@ public class SAML2Response {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
 
-        return (EncryptedAssertionType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        return (EncryptedAssertionType) samlParser.parse(samlDocument);
 
     }
 
@@ -398,7 +398,7 @@ public class SAML2Response {
 
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlDocument);
-        return (AssertionType) samlParser.parse(DocumentUtil.getNodeAsStream(samlDocument));
+        return (AssertionType) samlParser.parse(samlDocument);
     }
 
     /**
@@ -429,7 +429,7 @@ public class SAML2Response {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlResponseDocument);
 
-        ResponseType responseType = (ResponseType) samlParser.parse(DocumentUtil.getNodeAsStream(samlResponseDocument));
+        ResponseType responseType = (ResponseType) samlParser.parse(samlResponseDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(responseType, samlResponseDocument);
         return responseType;
@@ -460,8 +460,7 @@ public class SAML2Response {
         SAMLParser samlParser = new SAMLParser();
         JAXPValidationUtil.checkSchemaValidation(samlResponseDocument);
 
-        InputStream responseStream = DocumentUtil.getNodeAsStream(samlResponseDocument);
-        SAML2Object responseType = (SAML2Object) samlParser.parse(responseStream);
+        SAML2Object responseType = (SAML2Object) samlParser.parse(samlResponseDocument);
 
         samlDocumentHolder = new SAMLDocumentHolder(responseType, samlResponseDocument);
         return responseType;
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java
index 237d6a5..31916ff 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java
@@ -51,15 +51,11 @@ public class XMLTimeUtil {
      *
      * @throws org.keycloak.saml.common.exceptions.ConfigurationException
      */
-    public static XMLGregorianCalendar add(XMLGregorianCalendar value, long milis) throws ConfigurationException {
+    public static XMLGregorianCalendar add(XMLGregorianCalendar value, long milis) {
         XMLGregorianCalendar newVal = (XMLGregorianCalendar) value.clone();
 
         Duration duration;
-        try {
-            duration = newDatatypeFactory().newDuration(milis);
-        } catch (DatatypeConfigurationException e) {
-            throw logger.configurationError(e);
-        }
+        duration = DATATYPE_FACTORY.get().newDuration(milis);
         newVal.add(duration);
         return newVal;
     }
@@ -74,7 +70,7 @@ public class XMLTimeUtil {
      *
      * @throws ConfigurationException
      */
-    public static XMLGregorianCalendar subtract(XMLGregorianCalendar value, long milis) throws ConfigurationException {
+    public static XMLGregorianCalendar subtract(XMLGregorianCalendar value, long milis) {
         if (milis < 0)
             throw logger.invalidArgumentError("milis should be a positive value");
         return add(value, -1 * milis);
@@ -91,14 +87,10 @@ public class XMLTimeUtil {
      *
      * @throws ConfigurationException
      */
-    public static XMLGregorianCalendar getIssueInstant(String timezone) throws ConfigurationException {
+    public static XMLGregorianCalendar getIssueInstant(String timezone) {
         TimeZone tz = TimeZone.getTimeZone(timezone);
         DatatypeFactory dtf;
-        try {
-            dtf = newDatatypeFactory();
-        } catch (DatatypeConfigurationException e) {
-            throw logger.configurationError(e);
-        }
+        dtf = DATATYPE_FACTORY.get();
 
         GregorianCalendar gc = new GregorianCalendar(tz);
         XMLGregorianCalendar xgc = dtf.newXMLGregorianCalendar(gc);
@@ -188,13 +180,7 @@ public class XMLTimeUtil {
             PicketLinkLoggerFactory.getLogger().nullArgumentError("duration time");
         }
 
-        DatatypeFactory factory = null;
-
-        try {
-            factory = newDatatypeFactory();
-        } catch (DatatypeConfigurationException e) {
-            throw logger.parserError(e);
-        }
+        DatatypeFactory factory = DATATYPE_FACTORY.get();
 
         try {
             // checks if it is a ISO 8601 period. If not it must be a numeric value.
@@ -218,15 +204,20 @@ public class XMLTimeUtil {
      * @throws ParsingException
      */
     public static XMLGregorianCalendar parse(String timeString) throws ParsingException {
-        DatatypeFactory factory = null;
-        try {
-            factory = newDatatypeFactory();
-        } catch (DatatypeConfigurationException e) {
-            throw logger.parserError(e);
-        }
+        DatatypeFactory factory = DATATYPE_FACTORY.get();
         return factory.newXMLGregorianCalendar(timeString);
     }
 
+    private static final ThreadLocal<DatatypeFactory> DATATYPE_FACTORY = new ThreadLocal<DatatypeFactory>() {
+        @Override
+        protected DatatypeFactory initialValue() {
+            try {
+                return newDatatypeFactory();
+            } catch (DatatypeConfigurationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    };
 
     /**
      * Create a new {@link DatatypeFactory}
@@ -235,7 +226,7 @@ public class XMLTimeUtil {
      *
      * @throws DatatypeConfigurationException
      */
-    public static DatatypeFactory newDatatypeFactory() throws DatatypeConfigurationException {
+    private static DatatypeFactory newDatatypeFactory() throws DatatypeConfigurationException {
         boolean tccl_jaxp = SystemPropertiesUtil.getSystemProperty(GeneralConstants.TCCL_JAXP, "false")
                 .equalsIgnoreCase("true");
         ClassLoader prevTCCL = SecurityActions.getTCCL();
diff --git a/saml-core/src/test/java/org/keycloak/saml/common/util/StringUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/common/util/StringUtilTest.java
new file mode 100644
index 0000000..dff97b7
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/common/util/StringUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.keycloak.saml.common.util;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class StringUtilTest {
+
+    public StringUtilTest() {
+    }
+
+    @Test
+    public void testGetSystemPropertyAsString() {
+        System.setProperty("StringUtilTest.prop1", "value1");
+        System.setProperty("StringUtilTest.prop2", "value2");
+
+        assertThat(StringUtil.getSystemPropertyAsString("a"), is("a"));
+        assertThat(StringUtil.getSystemPropertyAsString("a ${StringUtilTest.prop1}"), is("a value1"));
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1"),
+          is("a" + "value1" + "StringUtilTest.prop1")
+        );
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1" + "${StringUtilTest.prop2}"),
+          is("a" + "value1" + "StringUtilTest.prop1" + "value2")
+        );
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1" + "${StringUtilTest.prop2}" + "${StringUtilTest.prop3::abc}"),
+          is("a" + "value1" + "StringUtilTest.prop1" + "value2" + "abc")
+        );
+        assertThat(
+          StringUtil.getSystemPropertyAsString("a" + "${StringUtilTest.prop1}" + "StringUtilTest.prop1" + "${StringUtilTest.prop2}" + "${StringUtilTest.prop3::abc}" + "end"),
+          is("a" + "value1" + "StringUtilTest.prop1" + "value2" + "abc" + "end")
+        );
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
index 778085c..bc6d316 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlEcpProfileTest.java
@@ -146,7 +146,7 @@ public class SamlEcpProfileTest {
 
         assertNotNull(samlResponse);
 
-        ResponseType responseType = (ResponseType) new SAMLParser().parse(DocumentUtil.getNodeAsStream(samlResponse));
+        ResponseType responseType = (ResponseType) new SAMLParser().parse(samlResponse);
         StatusCodeType statusCode = responseType.getStatus().getStatusCode();
 
         assertEquals(statusCode.getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
@@ -229,7 +229,7 @@ public class SamlEcpProfileTest {
 
         assertNotNull(samlResponse);
 
-        StatusResponseType responseType = (StatusResponseType) new SAMLParser().parse(DocumentUtil.getNodeAsStream(samlResponse));
+        StatusResponseType responseType = (StatusResponseType) new SAMLParser().parse(samlResponse);
         StatusCodeType statusCode = responseType.getStatus().getStatusCode();
 
         assertNotEquals(statusCode.getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());