From 4acf90edaa8c53ea7706b8547d4cccc9d226f635 Mon Sep 17 00:00:00 2001 From: Aleksei Efimov Date: Fri, 21 Oct 2016 02:53:22 +0300 Subject: [PATCH] 8167179: Make XSL generated namespace prefixes local to transformation process Reviewed-by: joehw --- .../internal/xsltc/compiler/Stylesheet.java | 7 +- .../internal/xsltc/runtime/BasisLibrary.java | 24 ++- .../transform/NamespacePrefixTest.java | 193 ++++++++++++++++++ 3 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 jaxp/test/javax/xml/jaxp/unittest/transform/NamespacePrefixTest.java diff --git a/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/Stylesheet.java b/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/Stylesheet.java index 539e17fdfa4..a7205cb7a99 100644 --- a/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/Stylesheet.java +++ b/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/Stylesheet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -31,6 +31,7 @@ import com.sun.org.apache.bcel.internal.generic.GETFIELD; import com.sun.org.apache.bcel.internal.generic.GETSTATIC; import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE; import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL; +import com.sun.org.apache.bcel.internal.generic.INVOKESTATIC; import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL; import com.sun.org.apache.bcel.internal.generic.ISTORE; import com.sun.org.apache.bcel.internal.generic.InstructionHandle; @@ -1252,6 +1253,10 @@ public final class Stylesheet extends SyntaxTreeNode { classGen.getConstantPool()); transf.addException("com.sun.org.apache.xalan.internal.xsltc.TransletException"); + // call resetPrefixIndex at the beginning of transform + final int check = cpg.addMethodref(BASIS_LIBRARY_CLASS, "resetPrefixIndex", "()V"); + il.append(new INVOKESTATIC(check)); + // Define and initialize current with the root node final LocalVariableGen current = transf.addLocalVariable("current", diff --git a/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/runtime/BasisLibrary.java b/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/runtime/BasisLibrary.java index c799a47949e..1ef07039c8f 100644 --- a/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/runtime/BasisLibrary.java +++ b/jaxp/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/runtime/BasisLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -31,6 +31,7 @@ import java.text.MessageFormat; import java.text.NumberFormat; import java.util.Locale; import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; import javax.xml.transform.dom.DOMSource; import com.sun.org.apache.xalan.internal.xsltc.DOM; @@ -1533,16 +1534,25 @@ public final class BasisLibrary { } /** - * This function is used in the execution of xsl:element + * These functions are used in the execution of xsl:element to generate + * and reset namespace prefix index local to current transformation process */ - private static int prefixIndex = 0; - public static String generatePrefix() { - synchronized (BasisLibrary.class) { - return ("ns" + prefixIndex++); - } + return ("ns" + threadLocalPrefixIndex.get().getAndIncrement()); } + public static void resetPrefixIndex() { + threadLocalPrefixIndex.get().set(0); + } + + private static final ThreadLocal threadLocalPrefixIndex = + new ThreadLocal() { + @Override + protected AtomicInteger initialValue() { + return new AtomicInteger(); + } + }; + public static final String RUN_TIME_INTERNAL_ERR = "RUN_TIME_INTERNAL_ERR"; public static final String RUN_TIME_COPY_ERR = diff --git a/jaxp/test/javax/xml/jaxp/unittest/transform/NamespacePrefixTest.java b/jaxp/test/javax/xml/jaxp/unittest/transform/NamespacePrefixTest.java new file mode 100644 index 00000000000..6602286fe90 --- /dev/null +++ b/jaxp/test/javax/xml/jaxp/unittest/transform/NamespacePrefixTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package transform; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.testng.annotations.Test; +import static org.testng.Assert.assertTrue; +import static jaxp.library.JAXPTestUtilities.runWithAllPerm; + +/* + * @test + * @bug 8167179 + * @library /javax/xml/jaxp/libs + * @run testng/othervm -DrunSecMngr=true transform.NamespacePrefixTest + * @run testng/othervm transform.NamespacePrefixTest + * @summary This class tests the generation of namespace prefixes + */ +public class NamespacePrefixTest { + + @Test + public void testReuseTemplates() throws Exception { + final TransformerFactory tf = TransformerFactory.newInstance(); + final Source xslsrc = new StreamSource(new StringReader(XSL)); + final Templates tmpl = tf.newTemplates(xslsrc); + for (int i = 0; i < TRANSF_COUNT; i++) { + checkResult(doTransformation(tmpl.newTransformer())); + } + } + + @Test + public void testReuseTransformer() throws Exception { + final TransformerFactory tf = TransformerFactory.newInstance(); + final Source xslsrc = new StreamSource(new StringReader(XSL)); + final Transformer t = tf.newTransformer(xslsrc); + for (int i = 0; i < TRANSF_COUNT; i++) { + checkResult(doTransformation(t)); + } + } + + @Test + public void testConcurrentTransformations() throws Exception { + final TransformerFactory tf = TransformerFactory.newInstance(); + final Source xslsrc = new StreamSource(new StringReader(XSL)); + final Templates tmpl = tf.newTemplates(xslsrc); + concurrentTestPassed.set(true); + + // Execute multiple TestWorker tasks + for (int id = 0; id < THREADS_COUNT; id++) { + EXECUTOR.execute(new TransformerThread(tmpl.newTransformer(), id)); + } + // Initiate shutdown of previously submitted task + runWithAllPerm(EXECUTOR::shutdown); + // Wait for termination of submitted tasks + if (!EXECUTOR.awaitTermination(THREADS_COUNT, TimeUnit.SECONDS)) { + // If not all tasks terminates during the time out force them to shutdown + runWithAllPerm(EXECUTOR::shutdownNow); + } + // Check if all transformation threads generated the correct namespace prefix + assertTrue(concurrentTestPassed.get()); + } + + // Do one transformation with the provided transformer + private static String doTransformation(Transformer t) throws Exception { + StringWriter resWriter = new StringWriter(); + Source xmlSrc = new StreamSource(new StringReader(XML)); + t.transform(xmlSrc, new StreamResult(resWriter)); + return resWriter.toString(); + } + + // Check if the transformation result string contains the + // element with the exact namespace prefix generated. + private static void checkResult(String result) { + // Check prefix of 'Element2' element, it should always be the same + assertTrue(result.contains(EXPECTED_CONTENT)); + } + + // Check if the transformation result string contains the element with + // the exact namespace prefix generated by current thread. + // If the expected prefix is not found and there was no failures observed by + // other test threads then mark concurrent test as failed. + private static void checkThreadResult(String result, int id) { + boolean res = result.contains(EXPECTED_CONTENT); + System.out.printf("%d: transformation result: %s%n", id, res ? "Pass" : "Fail"); + if (!res) { + System.out.printf("%d result:%s%n", id, result); + } + concurrentTestPassed.compareAndSet(true, res); + } + + // TransformerThread task that does the transformation similar + // to testReuseTransformer test method + private class TransformerThread implements Runnable { + + private final Transformer transformer; + private final int id; + + TransformerThread(Transformer transformer, int id) { + this.transformer = transformer; + this.id = id; + } + + @Override + public void run() { + try { + System.out.printf("%d: waiting for barrier%n", id); + //Synchronize startup of all tasks + BARRIER.await(); + System.out.printf("%d: starting transformation%n", id); + checkThreadResult(doTransformation(transformer), id); + } catch (Exception ex) { + throw new RuntimeException("TransformerThread " + id + " failed", ex); + } + } + } + + // Number of subsequent transformations + private static final int TRANSF_COUNT = 10; + + // Number of transformer threads running concurently + private static final int THREADS_COUNT = 10; + + // Variable for storing the concurrent transformation test result. It is + // updated by transformer threads + private static final AtomicBoolean concurrentTestPassed = new AtomicBoolean(true); + + // Cyclic barrier for threads startup synchronization + private static final CyclicBarrier BARRIER = new CyclicBarrier(THREADS_COUNT); + + // Thread pool + private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); + + // XSL that transforms XML and produces unique namespace prefixes for each element + private final static String XSL = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + // Simple XML content with root and two child elements + private final static String XML = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + // With thread local namespace prefix index each transformation result should + // be the same and contain the same prefix for Element2 + private final static String EXPECTED_CONTENT = ""; + +}