### Eclipse Workspace Patch 1.0 #P xwiki-core-rendering-macro-script Index: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/DefaultAttachmentClassLoaderFactory.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/classloader/DefaultAttachmentClassLoaderFactory.java (revision 0) +++ src/main/java/org/xwiki/rendering/internal/macro/script/classloader/DefaultAttachmentClassLoaderFactory.java (revision 0) @@ -0,0 +1,102 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.internal.macro.script.classloader; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.net.URLStreamHandler; +import java.util.StringTokenizer; + +import org.apache.commons.lang.StringUtils; +import org.xwiki.bridge.AttachmentNameFactory; +import org.xwiki.bridge.DocumentAccessBridge; +import org.xwiki.component.annotation.Component; +import org.xwiki.component.annotation.Requirement; + +@Component +public class DefaultAttachmentClassLoaderFactory implements AttachmentClassLoaderFactory +{ + private static final String ATTACHMENT_PREFIX = "attach:"; + + /** + * Create attachment name from a string reference. + */ + @Requirement + private AttachmentNameFactory attachmentNameFactory; + + /** + * Used to get the current document name and document URLs. + */ + @Requirement + private DocumentAccessBridge documentAccessBridge; + + /** + * {@inheritDoc} + * @see AttachmentClassLoaderFactory#createAttachmentClassLoader(String, ClassLoader) + */ + public ClassLoader createAttachmentClassLoader(String jarURLs, ClassLoader parent) throws Exception + { + XWikiURLClassLoader cl = new XWikiURLClassLoader(parent, new AttachmentURLStreamHandlerFactory( + this.attachmentNameFactory, this.documentAccessBridge)); + + // Parse the passed JAR URLs to tokenize it. + if (!StringUtils.isEmpty(jarURLs)) { + StringTokenizer tokenizer = new StringTokenizer(jarURLs, ","); + while (tokenizer.hasMoreElements()) { + String token = tokenizer.nextToken().trim(); + if (token.startsWith(ATTACHMENT_PREFIX)) { + cl.addURL(createURL(token)); + } else { + cl.addURL(new URL(token)); + } + } + } + + return cl; + } + + // Create a URL of the form attach://:.@ + private URL createURL(String attachmentReference) throws Exception + { + String urlBody = attachmentReference.substring(ATTACHMENT_PREFIX.length()); + try { + // Note: we encode using UTF8 since it's the W3C recommendation. + // See http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars + // TODO: Once the xwiki-url module is usable, refactor this code to use it and remove the need to + // perform explicit encoding here. + urlBody = URLEncoder.encode(urlBody, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Not supporting UTF-8 as a valid encoding for some reasons. We consider XWiki cannot work + // without that encoding. + throw new RuntimeException("Failed to URL encode [" + urlBody + "] using UTF-8.", e); + } + + return new URL(null, AttachmentURLStreamHandlerFactory.ATTACHMENT_JAR_PREFIX + urlBody, new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) throws IOException + { + throw new RuntimeException("Should not have been called for URL [" + url + "]"); + } + }); + } +} Property changes on: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/DefaultAttachmentClassLoaderFactory.java ___________________________________________________________________ Added: svn:eol-style + native Index: pom.xml =================================================================== --- pom.xml (revision 24157) +++ pom.xml (working copy) @@ -43,6 +43,12 @@ xwiki-core-bridge ${pom.version} + + commons-io + commons-io + 1.4 + test + @@ -73,10 +79,10 @@ - + \ No newline at end of file Index: src/main/java/org/xwiki/rendering/internal/macro/script/DefaultScriptJARURLFactory.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/DefaultScriptJARURLFactory.java (revision 24157) +++ src/main/java/org/xwiki/rendering/internal/macro/script/DefaultScriptJARURLFactory.java (working copy) @@ -1,152 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.xwiki.rendering.internal.macro.script; - -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.StringTokenizer; - -import org.apache.commons.lang.StringUtils; -import org.xwiki.bridge.AttachmentName; -import org.xwiki.bridge.AttachmentNameFactory; -import org.xwiki.bridge.DocumentAccessBridge; -import org.xwiki.bridge.DocumentName; -import org.xwiki.bridge.DocumentNameFactory; -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; -import org.xwiki.rendering.macro.script.ScriptJARURLFactory; - -/** - * Default implementation supporting the syntax defined in {@link #createJARURLs(String)}. - * - * @version $Id$ - * @since 2.0RC1 - */ -@Component -public class DefaultScriptJARURLFactory implements ScriptJARURLFactory -{ - /** - * Character to separate document name from JAR name, see {@link #createClassLoader(String)}. - * Note that we cannot rely completely on {@link AttachmentNameFactory} since we need to handle one - * special case: when the format {@code attach:wiki:space.page} is used to signify that all jar - * attachments from the specified page must be added. - */ - private static final String FILENAME_SEPARATOR = "@"; - - /** - * Since we want to allow the format {@code attach:wiki:space.page} to add all jars located on that document, - * we need to be able to differentiate if a reference is specifying a document name or a jar. We check this - * by verifying the suffix. - */ - private static final String JAR_SUFFIX = "jar"; - - /** - * Create attachment name from a string reference. - */ - @Requirement - private AttachmentNameFactory attachmentNameFactory; - - /** - * Used to get the current document name and document URLs. - */ - @Requirement - private DocumentAccessBridge documentAccessBridge; - - /** - * Used to get the current document name from a string reference. - */ - @Requirement("current") - private DocumentNameFactory documentNameFactory; - - /** - * {@inheritDoc} - * - * Parse a string pointing to JARs locations, either a known URL protocol such as "http" or a special "attach" - * protocol to generate JAR URLs from JARs attached to wiki pages. - *
    - *
  • {@code attach:wiki:space.page@somefile.jar}
  • - *
  • {@code attach:wiki:space.page}: all jars located on the passed page
  • - *
- * - * @see ScriptJARURLFactory#createURLs(String) - */ - public List createJARURLs(String scriptJars) throws Exception - { - List urls = new ArrayList(); - - if (!StringUtils.isEmpty(scriptJars)) { - List urlsAsString = new ArrayList(); - StringTokenizer tokenizer = new StringTokenizer(scriptJars, ","); - while (tokenizer.hasMoreElements()) { - String token = tokenizer.nextToken(); - - if (token.startsWith("attach:")) { - urlsAsString.addAll(parseAttachmentSyntax(token.substring(7))); - } else { - // Assume we have a URL and use it as is - urlsAsString.add(token); - } - } - - for (String urlAsString : urlsAsString) { - urls.add(new URL(urlAsString)); - } - } - - return urls; - } - - /** - * @param attachmentReference the reference to parse - * @return the list of URLs (as strings) to add to the returned Class Loader - * @throws Exception in case of an error retrieving the full list of attachments of the referenced document - */ - private List parseAttachmentSyntax(String attachmentReference) throws Exception - { - List urls = new ArrayList(); - - int pos = attachmentReference.lastIndexOf(FILENAME_SEPARATOR); - // If there's a "@" symbol specified use an attachment parser. - if (pos > -1) { - AttachmentName attachmentName = this.attachmentNameFactory.createAttachmentName(attachmentReference); - urls.add(this.documentAccessBridge.getAttachmentURL(attachmentName, true)); - } else { - // If the reference ends with "jar" then it's a filename reference, otherwise it's a document name - if (attachmentReference.toLowerCase().endsWith(JAR_SUFFIX)) { - // TODO: Do we need to check if current document name is null? - AttachmentName attachmentName = new AttachmentName( - this.documentAccessBridge.getCurrentDocumentName(), attachmentReference); - urls.add(this.documentAccessBridge.getAttachmentURL(attachmentName, true)); - } else { - // Add all jars attached to the specified document - DocumentName documentName = this.documentNameFactory.createDocumentName(attachmentReference); - // Only add attachments ending with the jar suffix - for (String attachmentURL : this.documentAccessBridge.getAttachmentURLs(documentName, true)) { - if (attachmentURL.endsWith(JAR_SUFFIX)) { - urls.add(attachmentURL); - } - } - } - } - - return urls; - } -} Index: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLConnection.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLConnection.java (revision 0) +++ src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLConnection.java (revision 0) @@ -0,0 +1,73 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.internal.macro.script.classloader; + +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; + +import org.xwiki.bridge.AttachmentName; +import org.xwiki.bridge.DocumentAccessBridge; + +/** + * URL Connection that takes its content from a document attachment. + * + * @version $Id$ + * @since 2.0.1 + */ +public class AttachmentURLConnection extends URLConnection +{ + private DocumentAccessBridge documentAccessBridge; + + private AttachmentName attachmentName; + + /** + * @param url the URL to connect to + */ + public AttachmentURLConnection(URL url, AttachmentName attachmentName, DocumentAccessBridge documentAccessBridge) + { + super(url); + this.attachmentName = attachmentName; + this.documentAccessBridge = documentAccessBridge; + } + + /** + * {@inheritDoc} + * @see JarURLConnection#connect() + */ + public void connect() + { + // Don't do anything since we don't need to connect to get the data... + } + + /** + * {@inheritDoc} + * @see JarURLConnection#getInputStream() + */ + public InputStream getInputStream() + { + try { + return this.documentAccessBridge.getAttachmentContent(this.attachmentName); + } catch (Exception e) { + throw new RuntimeException("Failed to get Attachment content for [" + this.attachmentName + "]", e); + } + } +} Index: src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerTest.java =================================================================== --- src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerTest.java (revision 0) +++ src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerTest.java (revision 0) @@ -0,0 +1,111 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.macro.script.classloader; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +import org.apache.commons.io.IOUtils; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.xwiki.bridge.AttachmentName; +import org.xwiki.bridge.AttachmentNameFactory; +import org.xwiki.bridge.DocumentAccessBridge; +import org.xwiki.rendering.internal.macro.script.classloader.AttachmentURLStreamHandler; + +/** + * Unit tests for {@link AttachmentURLStreamHandler}. + * + * @version $Id$ + * @since 2.0.1 + */ +public class AttachmentURLStreamHandlerTest +{ + private Mockery mockery = new Mockery(); + + private AttachmentNameFactory anf; + private DocumentAccessBridge dab; + + @Before + public void setUp() + { + this.anf = mockery.mock(AttachmentNameFactory.class); + this.dab = mockery.mock(DocumentAccessBridge.class); + } + + @Test + public void testInvalidAttachmentJarURL() throws Exception + { + URL url = new URL(null, "http://invalid/url", new AttachmentURLStreamHandler(anf, dab)); + + try { + url.openConnection(); + Assert.fail("Should have thrown an exception here"); + } catch (RuntimeException expected) { + Assert.assertEquals("An attachment JAR URL should start with [attachmentjar://], got [http://invalid/url]", + expected.getMessage()); + } + } + + @Test + public void testAttachmentJarURL() throws Exception + { + URL url = new URL(null, "attachmentjar://Space.Page@filename", new AttachmentURLStreamHandler(anf, dab)); + + final AttachmentName attachmentName = new AttachmentName("wiki", "space", "page", "filename"); + mockery.checking(new Expectations() {{ + oneOf(anf).createAttachmentName("Space.Page@filename"); will(returnValue(attachmentName)); + oneOf(dab).getAttachmentContent(attachmentName); + will(returnValue(new ByteArrayInputStream("content".getBytes()))); + }}); + + URLConnection connection = url.openConnection(); + InputStream input = null; + try { + connection.connect(); + input = connection.getInputStream(); + Assert.assertEquals("content", IOUtils.toString(input)); + } finally { + if (input != null) { + input.close(); + } + } + } + + /** + * Verify that URL-encoded chars are decoded. + */ + @Test + public void testAttachmentJarURLWithEncodedChars() throws Exception + { + URL url = new URL(null, "attachmentjar://some%20page", new AttachmentURLStreamHandler(anf, dab)); + + mockery.checking(new Expectations() {{ + oneOf(anf).createAttachmentName("some page"); + }}); + + url.openConnection(); + } +} Property changes on: src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerTest.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/XWikiURLClassLoader.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/classloader/XWikiURLClassLoader.java (revision 0) +++ src/main/java/org/xwiki/rendering/internal/macro/script/classloader/XWikiURLClassLoader.java (working copy) @@ -17,7 +17,7 @@ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ -package org.xwiki.rendering.internal.macro.script; +package org.xwiki.rendering.internal.macro.script.classloader; import java.net.URL; import java.net.URLClassLoader; @@ -65,6 +65,11 @@ { super(urls); } + + public XWikiURLClassLoader(ClassLoader parent, URLStreamHandlerFactory factory) + { + this(new URL[0], parent, factory); + } /** * @param url the JAR URL to add Property changes on: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/XWikiURLClassLoader.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/test/java/org/xwiki/rendering/macro/script/DefaultScriptJARURLFactoryTest.java =================================================================== --- src/test/java/org/xwiki/rendering/macro/script/DefaultScriptJARURLFactoryTest.java (revision 24157) +++ src/test/java/org/xwiki/rendering/macro/script/DefaultScriptJARURLFactoryTest.java (working copy) @@ -1,123 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.xwiki.rendering.macro.script; - -import java.net.URL; -import java.util.Arrays; -import java.util.List; - -import junit.framework.Assert; - -import org.jmock.Expectations; -import org.junit.Test; -import org.xwiki.bridge.AttachmentName; -import org.xwiki.bridge.DocumentName; -import org.xwiki.test.AbstractComponentTestCase; - -/** - * Unit tests for {@link DefaultScriptJARURLFactoryTest}. - * - * @version $Id$ - * @since 2.0RC1 - */ -public class DefaultScriptJARURLFactoryTest extends AbstractComponentTestCase -{ - private ScriptMockSetup mockSetup; - - private ScriptJARURLFactory factory; - - @Override - protected void registerComponents() throws Exception - { - super.registerComponents(); - this.mockSetup = new ScriptMockSetup(getComponentManager()); - - this.factory = getComponentManager().lookup(ScriptJARURLFactory.class); - } - - @Test - public void testExtraJarLocatedAtURL() throws Exception - { - List urls = this.factory.createJARURLs("http://path/to/some.jar"); - Assert.assertEquals(1, urls.size()); - Assert.assertTrue(urls.contains(new URL("http://path/to/some.jar"))); - } - - @Test - public void testExtraJarsLocatedAtURL() throws Exception - { - // Note: we test with spaces between urls too below. - List urls = this.factory.createJARURLs( - "http://path1/to/some.jar,http://path2/to/some.jar, http://path3/to/some.jar"); - Assert.assertEquals(3, urls.size()); - Assert.assertTrue(urls.contains(new URL("http://path1/to/some.jar"))); - Assert.assertTrue(urls.contains(new URL("http://path2/to/some.jar"))); - Assert.assertTrue(urls.contains(new URL("http://path3/to/some.jar"))); - } - - @Test - public void testExtraJarLocatedInSpecifiedDocument() throws Exception - { - final AttachmentName attachmentName = new AttachmentName( - new DocumentName("wiki", "space", "page"), "some.jar"); - - this.mockSetup.mockery.checking(new Expectations() {{ - oneOf(mockSetup.attachmentNameFactory).createAttachmentName("wiki:space.page@some.jar"); - will(returnValue(attachmentName)); - oneOf(mockSetup.bridge).getAttachmentURL(with(same(attachmentName)), with(equal(true))); - will(returnValue("http://path/to/some.jar")); - }}); - - List urls = this.factory.createJARURLs("attach:wiki:space.page@some.jar"); - Assert.assertTrue(urls.contains(new URL("http://path/to/some.jar"))); - } - - @Test - public void testExtraJarLocatedInCurrentDocument() throws Exception - { - final DocumentName documentName = new DocumentName("wiki", "space", "page"); - this.mockSetup.mockery.checking(new Expectations() {{ - oneOf(mockSetup.bridge).getCurrentDocumentName(); will(returnValue(documentName)); - oneOf(mockSetup.bridge).getAttachmentURL(new AttachmentName(documentName, "some.jar"), true); - will(returnValue("http://path/to/some.jar")); - }}); - - List urls = this.factory.createJARURLs("attach:some.jar"); - Assert.assertTrue(urls.contains(new URL("http://path/to/some.jar"))); - } - - @Test - public void testAllJarsLocatedInSpecifiedDocument() throws Exception - { - final DocumentName documentName = new DocumentName("wiki", "space", "page"); - this.mockSetup.mockery.checking(new Expectations() {{ - oneOf(mockSetup.documentNameFactory).createDocumentName("wiki:space.page"); - will(returnValue(documentName)); - oneOf(mockSetup.bridge).getAttachmentURLs(documentName, true); - will(returnValue(Arrays.asList( - "http://path/to/some1.jar", "http://path/to/notajar.txt", "http://path/to/some2.jar"))); - }}); - - List urls = this.factory.createJARURLs("attach:wiki:space.page"); - Assert.assertEquals(2, urls.size()); - Assert.assertTrue(urls.contains(new URL("http://path/to/some1.jar"))); - Assert.assertTrue(urls.contains(new URL("http://path/to/some2.jar"))); - } -} Index: src/main/java/org/xwiki/rendering/macro/script/ScriptJARURLFactory.java =================================================================== --- src/main/java/org/xwiki/rendering/macro/script/ScriptJARURLFactory.java (revision 24157) +++ src/main/java/org/xwiki/rendering/macro/script/ScriptJARURLFactory.java (working copy) @@ -1,47 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.xwiki.rendering.macro.script; - -import java.net.URL; -import java.util.List; - -import org.xwiki.component.annotation.ComponentRole; - -/** - * Create JAR URLs to be used in the script Class Loader when executing scripts, see {@link #createJARURLs(String)} - * for more details. - * - * @version $Id$ - * @since 2.0RC1 - */ -@ComponentRole -public interface ScriptJARURLFactory -{ - /** - * Create a list of JAR URLs to be put in the Class Loader used when executing scripts. The URLs are created based - * on some passed reference specifying a list of JARs to be made available in the created class loader. The format - * is not defined and left to implementation classes. - * - * @param scriptJars the list of JARs to make available in the returned class loader - * @return the JAR URLs - * @throws Exception if the URLs fail to be created for any reason (eg some invalid URL reference passed) - */ - List createJARURLs(String scriptJars) throws Exception; -} Index: src/main/java/org/xwiki/rendering/macro/script/AbstractScriptMacro.java =================================================================== --- src/main/java/org/xwiki/rendering/macro/script/AbstractScriptMacro.java (revision 24157) +++ src/main/java/org/xwiki/rendering/macro/script/AbstractScriptMacro.java (working copy) @@ -32,7 +32,8 @@ import org.xwiki.context.Execution; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.block.XDOM; -import org.xwiki.rendering.internal.macro.script.XWikiURLClassLoader; +import org.xwiki.rendering.internal.macro.script.classloader.AttachmentClassLoaderFactory; +import org.xwiki.rendering.internal.macro.script.classloader.XWikiURLClassLoader; import org.xwiki.rendering.macro.AbstractMacro; import org.xwiki.rendering.macro.MacroExecutionException; import org.xwiki.rendering.macro.descriptor.ContentDescriptor; @@ -61,6 +62,8 @@ */ private static final String EXECUTION_CONTEXT_CLASSLOADER_KEY = "scriptClassLoader"; + public static final String EXECUTION_CONTEXT_JARPARAMS_KEY = "scriptJarParams"; + /** * Used to find if the current document's author has programming rights. */ @@ -88,10 +91,10 @@ private Parser plainTextParser; /** - * Used to create JAR URLs from the {@link ScriptMacroParameters#getJars()} parameter value. + * Used to create a custom class loader that knows how to support JARs attached to wiki page. */ @Requirement - private ScriptJARURLFactory scriptJARURLFactory; + private AttachmentClassLoaderFactory attachmentClassLoaderFactory; /** * Used to clean result of the parser syntax. @@ -182,8 +185,9 @@ // Set the context class loader to the script CL to ensure that any script engine using the context // classloader will work just fine. ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader newClassLoader = getClassLoader(parameters.getJars(), originalClassLoader); try { - Thread.currentThread().setContextClassLoader(getClassLoader(parameters.getJars())); + Thread.currentThread().setContextClassLoader(newClassLoader); // 1) Run script engine on macro block content String scriptResult = evaluate(parameters, content, context); @@ -193,6 +197,16 @@ result = parseScriptResult(scriptResult, parameters, context); } } finally { + // Save the jar parameters here only and not in getClassLoader since the evaluate() method needs to + // check if the jar parameters have been modified since the last execution. + // Also we only need to save them if a new classloader has been created. + // We also save the new classloader at this stage so that both the params and the class loader are + // saved together. + if (newClassLoader != originalClassLoader) { + this.execution.getContext().setProperty(EXECUTION_CONTEXT_CLASSLOADER_KEY, newClassLoader); + this.execution.getContext().setProperty(EXECUTION_CONTEXT_JARPARAMS_KEY, parameters.getJars()); + } + // Restore original class loader Thread.currentThread().setContextClassLoader(originalClassLoader); } @@ -207,30 +221,35 @@ * @return the class loader to use for executing the script * @throws MacroExecutionException in case of an error in building the class loader */ - protected ClassLoader getClassLoader(String jarsParameterValue) throws MacroExecutionException + protected ClassLoader getClassLoader(String jarsParameterValue, ClassLoader parentClassLoader) + throws MacroExecutionException { - // Set a class loader for script execution in the EC if not set. - XWikiURLClassLoader cl = - (XWikiURLClassLoader) this.execution.getContext().getProperty(EXECUTION_CONTEXT_CLASSLOADER_KEY); - if (cl == null) { - cl = new XWikiURLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); - this.execution.getContext().setProperty(EXECUTION_CONTEXT_CLASSLOADER_KEY, cl); - } + // We cache the Class Loader for improved performances and we check if the saved class loader had the same + // jar parameters value as the current execution. If not, we compute a new class loader. + ClassLoader cl = (ClassLoader) this.execution.getContext().getProperty(EXECUTION_CONTEXT_CLASSLOADER_KEY); + String cachedJarsParameterValue = + (String) this.execution.getContext().getProperty(EXECUTION_CONTEXT_JARPARAMS_KEY); + if (cl == null || cachedJarsParameterValue != jarsParameterValue) { - // Add any specified jar URLs to the CL - if (!StringUtils.isEmpty(jarsParameterValue)) { - if (canHaveJarsParameters()) { - try { - cl.addURLs(this.scriptJARURLFactory.createJARURLs(jarsParameterValue)); - } catch (Exception e) { - throw new MacroExecutionException("Failed to add JAR URLs to the current class loader for [" - + jarsParameterValue + "]", e); + // Add any specified jar URLs to the CL + if (!StringUtils.isEmpty(jarsParameterValue)) { + if (canHaveJarsParameters()) { + try { + cl = this.attachmentClassLoaderFactory.createAttachmentClassLoader(jarsParameterValue, + parentClassLoader); + } catch (Exception e) { + throw new MacroExecutionException("Failed to add JAR URLs to the current class loader for [" + + jarsParameterValue + "]", e); + } + } else { + throw new MacroExecutionException( + "You cannot pass additional jars since you don't have programming rights"); } } else { - throw new MacroExecutionException( - "You cannot pass additional jars since you don't have programming rights"); + cl = parentClassLoader; } } + return cl; } Index: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentClassLoaderFactory.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentClassLoaderFactory.java (revision 0) +++ src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentClassLoaderFactory.java (revision 0) @@ -0,0 +1,28 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.internal.macro.script.classloader; + +import org.xwiki.component.annotation.ComponentRole; + +@ComponentRole +public interface AttachmentClassLoaderFactory +{ + ClassLoader createAttachmentClassLoader(String jarURLs, ClassLoader parent) throws Exception; +} Property changes on: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentClassLoaderFactory.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/main/resources/META-INF/components.txt =================================================================== --- src/main/resources/META-INF/components.txt (revision 24157) +++ src/main/resources/META-INF/components.txt (working copy) @@ -1,2 +1,2 @@ org.xwiki.rendering.internal.macro.script.DefaultScriptMacro -org.xwiki.rendering.internal.macro.script.DefaultScriptJARURLFactory \ No newline at end of file +org.xwiki.rendering.internal.macro.script.classloader.DefaultAttachmentClassLoaderFactory \ No newline at end of file Index: src/main/java/org/xwiki/rendering/internal/macro/script/XWikiURLClassLoader.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/XWikiURLClassLoader.java (revision 24157) +++ src/main/java/org/xwiki/rendering/internal/macro/script/XWikiURLClassLoader.java (working copy) @@ -1,87 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.xwiki.rendering.internal.macro.script; - -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLStreamHandlerFactory; -import java.util.List; - -/** - * Implementation that allows adding URLs on demand (the default {@link URLClassLoader} only allows - * adding URLs in the constructor). - * - * @version $Id$ - * @since 2.0RC1 - */ -public class XWikiURLClassLoader extends URLClassLoader -{ - /** - * See {@link URLClassLoader#URLClassLoader(URL[], ClassLoader, URLStreamHandlerFactory)}. - * - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - * @param factory the URLStreamHandlerFactory to use when creating URLs - */ - public XWikiURLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) - { - super(urls, parent, factory); - } - - /** - * See {@link URLClassLoader#URLClassLoader(URL[], ClassLoader)}. - * - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - */ - public XWikiURLClassLoader(URL[] urls, ClassLoader parent) - { - super(urls, parent); - } - - /** - * See {@link URLClassLoader#URLClassLoader(URL[])}. - * - * @param urls the URLs from which to load classes and resources - */ - public XWikiURLClassLoader(URL[] urls) - { - super(urls); - } - - /** - * @param url the JAR URL to add - */ - @Override - public void addURL(URL url) - { - super.addURL(url); - } - - /** - * @param urls the JAR URLs to add - */ - public void addURLs(List urls) - { - for (URL url : urls) { - addURL(url); - } - } -} Index: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandlerFactory.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandlerFactory.java (revision 0) +++ src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandlerFactory.java (revision 0) @@ -0,0 +1,64 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.internal.macro.script.classloader; + +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +import org.xwiki.bridge.AttachmentNameFactory; +import org.xwiki.bridge.DocumentAccessBridge; + +public class AttachmentURLStreamHandlerFactory implements URLStreamHandlerFactory +{ + public static final String ATTACHMENT_JAR_SCHEME = "attachmentjar"; + + public static final String ATTACHMENT_JAR_PREFIX = ATTACHMENT_JAR_SCHEME + "://"; + + /** + * Create attachment name from a string reference. + */ + private AttachmentNameFactory attachmentNameFactory; + + /** + * Used to get the current document name and document URLs. + */ + private DocumentAccessBridge documentAccessBridge; + + public AttachmentURLStreamHandlerFactory(AttachmentNameFactory attachmentNameFactory, + DocumentAccessBridge documentAccessBridge) + { + this.attachmentNameFactory = attachmentNameFactory; + this.documentAccessBridge = documentAccessBridge; + } + + /** + * {@inheritDoc} + * @see URLStreamHandlerFactory#createURLStreamHandler(String) + */ + public URLStreamHandler createURLStreamHandler(String protocol) + { + // Returning null means using the default stream handler + URLStreamHandler result = null; + if (protocol.equalsIgnoreCase(ATTACHMENT_JAR_SCHEME)) { + result = new AttachmentURLStreamHandler(this.attachmentNameFactory, this.documentAccessBridge); + } + return result; + } +} Property changes on: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandlerFactory.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerFactoryTest.java =================================================================== --- src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerFactoryTest.java (revision 0) +++ src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerFactoryTest.java (revision 0) @@ -0,0 +1,49 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.macro.script.classloader; + +import org.junit.Assert; +import org.junit.Test; +import org.xwiki.rendering.internal.macro.script.classloader.AttachmentURLStreamHandler; +import org.xwiki.rendering.internal.macro.script.classloader.AttachmentURLStreamHandlerFactory; + +/** + * Unit tests for {@link AttachmentURLStreamHandlerFactory}. + * + * @version $Id$ + * @since 2.0.1 + */ +public class AttachmentURLStreamHandlerFactoryTest +{ + @Test + public void testCreateHandlerWhenNonAttachmentJarScheme() + { + AttachmentURLStreamHandlerFactory factory = new AttachmentURLStreamHandlerFactory(null, null); + Assert.assertNull(factory.createURLStreamHandler("http")); + } + + @Test + public void testCreateHandlerWhenAttachmentJarScheme() + { + AttachmentURLStreamHandlerFactory factory = new AttachmentURLStreamHandlerFactory(null, null); + Assert.assertEquals(AttachmentURLStreamHandler.class.getName(), + factory.createURLStreamHandler("attachmentjar").getClass().getName()); + } +} Property changes on: src/test/java/org/xwiki/rendering/macro/script/classloader/AttachmentURLStreamHandlerFactoryTest.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/main/java/org/xwiki/rendering/macro/script/AbstractJSR223ScriptMacro.java =================================================================== --- src/main/java/org/xwiki/rendering/macro/script/AbstractJSR223ScriptMacro.java (revision 24157) +++ src/main/java/org/xwiki/rendering/macro/script/AbstractJSR223ScriptMacro.java (working copy) @@ -220,30 +220,22 @@ { // Look for a script engine in the Execution Context since we want the same engine to be used // for all evals during the same execution lifetime. + // However only reuse a cached engine if the same ClassLoader should be used. If jar params have + // changed then recreate an engine. This is needed since the ClassLoader used by the engine may + // have been cached by the engine. ExecutionContext executionContext = this.execution.getContext(); Map scriptEngines = (Map) executionContext.getProperty(EXECUTION_CONTEXT_ENGINE_KEY); + String cachedJarsParameterValue = + (String) this.execution.getContext().getProperty(EXECUTION_CONTEXT_JARPARAMS_KEY); if (scriptEngines == null) { scriptEngines = new HashMap(); executionContext.setProperty(EXECUTION_CONTEXT_ENGINE_KEY, scriptEngines); } ScriptEngine engine = scriptEngines.get(engineName); - if (engine == null) { + if (engine == null || cachedJarsParameterValue != jarsParameterValue) { ScriptEngineManager sem = new ScriptEngineManager(); - - // Some engines will create their class loader when the engine is instantiated and use the context - // classloader as the parent class loader. Thus we must set our own class loader at this point so - // that we can add other URLs to it later on if some JARs are specified in other script macro - // executions. - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(getClassLoader(jarsParameterValue)); - engine = sem.getEngineByName(engineName); - } finally { - // Restore original class loader - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - + engine = sem.getEngineByName(engineName); scriptEngines.put(engineName, engine); } Index: src/test/java/org/xwiki/rendering/macro/script/classloader/DefaultAttachmentClassLoaderFactoryTest.java =================================================================== --- src/test/java/org/xwiki/rendering/macro/script/classloader/DefaultAttachmentClassLoaderFactoryTest.java (revision 0) +++ src/test/java/org/xwiki/rendering/macro/script/classloader/DefaultAttachmentClassLoaderFactoryTest.java (revision 0) @@ -0,0 +1,74 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.macro.script.classloader; + +import junit.framework.Assert; + +import org.jmock.Mockery; +import org.junit.Before; +import org.junit.Test; +import org.xwiki.bridge.AttachmentNameFactory; +import org.xwiki.bridge.DocumentAccessBridge; +import org.xwiki.component.internal.ReflectionUtils; +import org.xwiki.rendering.internal.macro.script.classloader.DefaultAttachmentClassLoaderFactory; +import org.xwiki.rendering.internal.macro.script.classloader.XWikiURLClassLoader; + +/** + * Unit tests for {@link DefaultAttachmentClassLoaderFactory}. + * + * @version $Id$ + * @since 2.0.1 + */ +public class DefaultAttachmentClassLoaderFactoryTest +{ + private Mockery mockery = new Mockery(); + + private DefaultAttachmentClassLoaderFactory factory; + + private AttachmentNameFactory anf; + + private DocumentAccessBridge dab; + + @Before + public void setUp() + { + factory = new DefaultAttachmentClassLoaderFactory(); + + anf = mockery.mock(AttachmentNameFactory.class); + ReflectionUtils.setFieldValue(factory, "attachmentNameFactory", anf); + + dab = mockery.mock(DocumentAccessBridge.class); + ReflectionUtils.setFieldValue(factory, "documentAccessBridge", dab); + } + + @Test + public void testCreateClassLoader() throws Exception + { + XWikiURLClassLoader cl = (XWikiURLClassLoader) factory.createAttachmentClassLoader( + "attach:page@filename1, http://some/url, attach:filename2", null); + + Assert.assertEquals(3, cl.getURLs().length); + Assert.assertEquals("attachmentjar://page%40filename1", cl.getURLs()[0].toString()); + Assert.assertEquals("http://some/url", cl.getURLs()[1].toString()); + Assert.assertEquals("attachmentjar://filename2", cl.getURLs()[2].toString()); + + Assert.assertNotNull(cl.findResource("/something")); + } +} Property changes on: src/test/java/org/xwiki/rendering/macro/script/classloader/DefaultAttachmentClassLoaderFactoryTest.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandler.java =================================================================== --- src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandler.java (revision 0) +++ src/main/java/org/xwiki/rendering/internal/macro/script/classloader/AttachmentURLStreamHandler.java (revision 0) @@ -0,0 +1,97 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.xwiki.rendering.internal.macro.script.classloader; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.net.URLStreamHandler; + +import org.xwiki.bridge.AttachmentName; +import org.xwiki.bridge.AttachmentNameFactory; +import org.xwiki.bridge.DocumentAccessBridge; + +/** + * Special handler that allows building URLs that have their contents in a document's attachment. + * + * @version $Id$ + * @since 2.0.1 + */ +public class AttachmentURLStreamHandler extends URLStreamHandler +{ + /** + * Create attachment name from a string reference. + */ + private AttachmentNameFactory attachmentNameFactory; + + /** + * Used to get the current document name and document URLs. + */ + private DocumentAccessBridge documentAccessBridge; + + public AttachmentURLStreamHandler(AttachmentNameFactory attachmentNameFactory, + DocumentAccessBridge documentAccessBridge) + { + this.attachmentNameFactory = attachmentNameFactory; + this.documentAccessBridge = documentAccessBridge; + } + + /** + * {@inheritDoc} + * + * Parse the attachment URL which is in the format {@code attachmentjar://..@}. + * + * @see URLStreamHandler#openConnection(URL) + */ + protected URLConnection openConnection(URL url) throws IOException + { + // Get the attachment reference from the URL and transform it into an AttachmentName object + AttachmentName attachmentName = this.attachmentNameFactory.createAttachmentName(getAttachmentReference(url)); + + return new AttachmentURLConnection(url, attachmentName, this.documentAccessBridge); + } + + private String getAttachmentReference(URL url) + { + // If the URL doesn't start with the JAR scheme prefix something is wrong + String urlAsString = url.toString(); + if (!urlAsString.startsWith(AttachmentURLStreamHandlerFactory.ATTACHMENT_JAR_PREFIX)) { + throw new RuntimeException("An attachment JAR URL should start with [" + + AttachmentURLStreamHandlerFactory.ATTACHMENT_JAR_PREFIX + "], got [" + urlAsString + "]"); + } + + String attachmentReference = urlAsString.substring(AttachmentURLStreamHandlerFactory.ATTACHMENT_JAR_PREFIX.length()); + try { + // Note: we decode using UTF8 since it's the W3C recommendation. + // See http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars + // TODO: Once the xwiki-url module is usable, refactor this code to use it and remove the need to + // perform explicit decoding here. + attachmentReference = URLDecoder.decode(attachmentReference, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Not supporting UTF-8 as a valid encoding for some reasons. We consider XWiki cannot work + // without that encoding. + throw new RuntimeException("Failed to URL decode [" + attachmentReference + "] using UTF-8.", e); + } + + return attachmentReference; + } +}