Index: build.xml =================================================================== --- build.xml (revision 638656) +++ build.xml (working copy) @@ -353,7 +353,17 @@ debug="${debug.mode}" includeantruntime="no" /> + basedir="${build.dir}/custom-classpath" + includes="org/apache/ivy/plugins/resolver/CustomResolver*"/> + + + @@ -471,7 +481,7 @@ - Index: doc/configuration/classpath.html =================================================================== --- doc/configuration/classpath.html (revision 638656) +++ doc/configuration/classpath.html (working copy) @@ -27,9 +27,10 @@ Index: src/java/org/apache/ivy/ant/IvyAntClassLoaderManager.java =================================================================== --- src/java/org/apache/ivy/ant/IvyAntClassLoaderManager.java (revision 0) +++ src/java/org/apache/ivy/ant/IvyAntClassLoaderManager.java (revision 0) @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.ant; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; + +import org.apache.ivy.Ivy; +import org.apache.ivy.core.settings.IvyClassLoaderManager; +import org.apache.ivy.util.Message; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Path; + +/** + * Class loader manager implementation to use when running within Ant. + * {@link #addPathId(String)} and {@link #addPath(String)} can be used + * to reference Ant path structures in the Ivy plugin classloader. + * + * @see org.apache.ivy.core.settings.IvySettings#setClassLoaderManager(IvyClassLoaderManager) + */ +public class IvyAntClassLoaderManager implements IvyClassLoaderManager { + + /* + * The challenge is + * 1. Ant supports parsing of complex paths, but Ivy doesn't; but + * 2. Ivy supports URL-based classloading, but Ant doesn't. + * + * So to combine the two together, we build a chain of classloaders. + * Whenever a new entry is added to the classpath, we add a new ClassLoader + * to the chain, which uses the previous ClassLoader as its parent. + */ + + private Project project; + private ClassLoader loader; + + //string representation of the full classpath, for logging purposes. + private StringBuffer string = new StringBuffer("[]"); + + public IvyAntClassLoaderManager(Project project) { + this.project = project; + } + + /** + * Add a complex path expression to the current classloader search + * path. Path expressions contain elements separated by + * ";" or ":"; see the Ant Manual + * for details. + */ + public void addPath(String pathExpression) { + appendPathInfo(pathExpression); + //chain a classloader for the new path onto the end of the existing + //classloaders. + Path path = new Path(project, pathExpression); + loader = new AntClassLoader(getClassLoader(), project, path, true); + } + + /** + * Add a reference to a path defined in Ant. + */ + public void addPathId(String pathId) { + Object ref = project.getReference(pathId); + if (ref != null) { + //throw an error if the user gave us a fileset id or something like that. + if (!(ref instanceof Path)) { + throw new IllegalArgumentException("Ant id " + pathId + + " should identify a Path, but instead got a " + ref.getClass().getName()); + } + + //chain a classloader for the new path onto the end of the existing + //classloaders. + Path path = (Path) ref; + appendPathInfo(path.toString()); + loader = new AntClassLoader(getClassLoader(), project, path, true); + } else { + Message.debug("Skipping unknown path ID " + pathId); + } + } + + public void addURL(URL url) { + appendPathInfo(url.toExternalForm()); + URL[] urls = {url}; + //chain a classloader for the URL onto the end of the existing + //classloaders. + loader = new URLClassLoader(urls, getClassLoader()); + } + + public void dumpSettings() { + Message.verbose("\t-- Ivy plugin classloader path: "); + Message.verbose("\t\t" + toString()); + } + + public ClassLoader getClassLoader() { + if (loader == null) { + //Ivy loader is the parent of the plugin classloader hierarchy. + loader = Ivy.class.getClassLoader(); + } + return loader; + } + + public String toString() { + return string.toString(); + } + + /** update the string representation with the given path info */ + private void appendPathInfo(String path) { + //delete the close brace. + string.setLength(string.length() - 1); + //separate the new path from existing entries. + if (string.length() > 1) { + string.append(File.pathSeparatorChar); + } + string.append(path); + string.append(']'); + } +} Index: src/java/org/apache/ivy/ant/IvyAntSettings.java =================================================================== --- src/java/org/apache/ivy/ant/IvyAntSettings.java (revision 638656) +++ src/java/org/apache/ivy/ant/IvyAntSettings.java (working copy) @@ -277,9 +277,11 @@ } private void createIvyEngine() { - IvyAntVariableContainer ivyAntVariableContainer = new IvyAntVariableContainer(getProject()); + Project project = getProject(); + IvyAntVariableContainer ivyAntVariableContainer = new IvyAntVariableContainer(project); + IvyAntClassLoaderManager ivyAntClassLoaderManager = new IvyAntClassLoaderManager(project); - IvySettings settings = new IvySettings(ivyAntVariableContainer); + IvySettings settings = new IvySettings(ivyAntVariableContainer, ivyAntClassLoaderManager); if (file == null && url == null) { defineDefaultSettingFile(ivyAntVariableContainer); Index: src/java/org/apache/ivy/core/settings/IvyClassLoaderManager.java =================================================================== --- src/java/org/apache/ivy/core/settings/IvyClassLoaderManager.java (revision 0) +++ src/java/org/apache/ivy/core/settings/IvyClassLoaderManager.java (revision 0) @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.core.settings; + +import java.net.URL; + +/** + * Manages the ClassLoader used by {@link IvySettings} to load + * plugin classes and other custom typedefs. Methods named add*(...) + * are used to add classpath entries. {@link #getClassLoader()} returns + * a classloader that searches all of the classpath entries in the order + * in which they were added. + * + * @see IvySettings#setClassLoaderManager(IvyClassLoaderManager) + * @see IvyClassLoaderManagerImpl + */ +public interface IvyClassLoaderManager { + + /** Add a JAR URL to the class loader's classpath. */ + public void addURL(URL url); + /** + * Add a complex path expression (e.g. of the kind used in the system property + * java.class.path) to the class loader's classpath. + * @throws UnsupportedOperationException if this manager does not support + * complex path expressions + */ + public void addPath(String path); + /** + * Add a reference to an externally defined class path. Externally defined paths + * may be available when Ivy is run embedded within another build system like Ant. + * @throws UnsupportedOperationException if this manager does not support + * external path references + */ + public void addPathId(String pathId); + + /** + * Get the ClassLoader that configures all locations configured with {@link #addURL(URL)}, + * {@link #addPath(String)}, or {@link #addPathId(String)}. The returned loader is + * not affected by subsequent calls to add* methods. + */ + public ClassLoader getClassLoader(); + + /** + * Send debug information about the current classloader settings to the Message logger. + */ + public void dumpSettings(); +} Index: src/java/org/apache/ivy/core/settings/IvyClassLoaderManagerImpl.java =================================================================== --- src/java/org/apache/ivy/core/settings/IvyClassLoaderManagerImpl.java (revision 0) +++ src/java/org/apache/ivy/core/settings/IvyClassLoaderManagerImpl.java (revision 0) @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.core.settings; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.ivy.Ivy; +import org.apache.ivy.util.Message; + +/** + * Default class loader manager implementation, for Ivy standalone mode. + * Only URL classpath entries are supported. Attempts to include complex + * path or path ID references will raise exceptions. + */ +public class IvyClassLoaderManagerImpl implements IvyClassLoaderManager { + + private List classpathURLs = new ArrayList(); + private ClassLoader classloader = null; + + /** @throws UnsupportedOperationException if called */ + public void addPath(String path) { + throw new UnsupportedOperationException("Cannot add path '" + path + + "': Complex path expressions are not supported in the standalone Ivy ClassLoader"); + } + + /** @throws UnsupportedOperationException if called */ + public void addPathId(String pathId) { + throw new UnsupportedOperationException("Cannot add path id '" + pathId + + "': Path ID references are not supported in the standalone Ivy ClassLoader"); + } + + /** add a Jar file URL to the classpath */ + public void addURL(URL url) { + classpathURLs.add(url); + classloader = null; + } + + public ClassLoader getClassLoader() { + if (classloader == null) { + if (classpathURLs.isEmpty()) { + classloader = Ivy.class.getClassLoader(); + } else { + classloader = new URLClassLoader((URL[]) classpathURLs + .toArray(new URL[classpathURLs.size()]), Ivy.class.getClassLoader()); + } + } + return classloader; + } + + public void dumpSettings() { + if (!classpathURLs.isEmpty()) { + Message.verbose("\t-- " + classpathURLs.size() + " custom classpath urls:"); + for (Iterator iter = classpathURLs.iterator(); iter.hasNext();) { + Message.debug("\t\t" + iter.next()); + } + } + } + + public String toString() { + return classpathURLs.toString(); + } +} Index: src/java/org/apache/ivy/core/settings/IvySettings.java =================================================================== --- src/java/org/apache/ivy/core/settings/IvySettings.java (revision 638656) +++ src/java/org/apache/ivy/core/settings/IvySettings.java (working copy) @@ -25,7 +25,6 @@ import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; @@ -35,7 +34,6 @@ import java.util.Map; import java.util.Properties; -import org.apache.ivy.Ivy; import org.apache.ivy.core.IvyPatternHelper; import org.apache.ivy.core.NormalRelativeUrlResolver; import org.apache.ivy.core.RelativeUrlResolver; @@ -179,10 +177,8 @@ private File defaultUserDir; - private List classpathURLs = new ArrayList(); + private IvyClassLoaderManager classLoaderManager = null; - private ClassLoader classloader; - private Boolean debugConflictResolution; private boolean logNotConvertedExclusionRule; @@ -204,11 +200,13 @@ private String defaultResolveMode = ResolveOptions.RESOLVEMODE_DEFAULT; public IvySettings() { - this(new IvyVariableContainerImpl()); + this(new IvyVariableContainerImpl(), new IvyClassLoaderManagerImpl()); } - public IvySettings(IvyVariableContainer variableContainer) { + public IvySettings(IvyVariableContainer variableContainer, + IvyClassLoaderManager classLoaderManager) { setVariableContainer(variableContainer); + setClassLoaderManager(classLoaderManager); setVariable("ivy.default.settings.dir", getDefaultSettingsDir(), true); setDeprecatedVariable("ivy.default.conf.dir", "ivy.default.settings.dir"); @@ -499,12 +497,8 @@ Message.debug("\tvalidate: " + doValidate()); Message.debug("\tcheck up2date: " + isCheckUpToDate()); - if (!classpathURLs.isEmpty()) { - Message.verbose("\t-- " + classpathURLs.size() + " custom classpath urls:"); - for (Iterator iter = classpathURLs.iterator(); iter.hasNext();) { - Message.debug("\t\t" + iter.next()); - } - } + classLoaderManager.dumpSettings(); + Message.verbose("\t-- " + resolversMap.size() + " resolvers:"); for (Iterator iter = resolversMap.values().iterator(); iter.hasNext();) { DependencyResolver resolver = (DependencyResolver) iter.next(); @@ -605,31 +599,38 @@ } catch (ClassNotFoundException e) { if (silentFail) { Message.info("impossible to define new type: class not found: " + className - + " in " + classpathURLs + " nor Ivy classloader"); + + " in " + classLoaderManager + " nor Ivy classloader"); return null; } else { throw new RuntimeException("impossible to define new type: class not found: " - + className + " in " + classpathURLs + " nor Ivy classloader"); + + className + " in " + classLoaderManager + " nor Ivy classloader"); } } } private ClassLoader getClassLoader() { - if (classloader == null) { - if (classpathURLs.isEmpty()) { - classloader = Ivy.class.getClassLoader(); - } else { - classloader = new URLClassLoader((URL[]) classpathURLs - .toArray(new URL[classpathURLs.size()]), Ivy.class.getClassLoader()); - } - } - return classloader; + return classLoaderManager.getClassLoader(); } public void addClasspathURL(URL url) { - classpathURLs.add(url); - classloader = null; + classLoaderManager.addURL(url); } + + public void addClasspath(String path) { + classLoaderManager.addPath(path); + } + + public void addClasspathId(String pathId) { + classLoaderManager.addPathId(pathId); + } + + /** + * Set a non-default class loader manager (e.g. for integration with + * Ant classpaths). + */ + public void setClassLoaderManager(IvyClassLoaderManager manager) { + this.classLoaderManager = manager; + } public Map getTypeDefs() { return typeDefs; Index: src/java/org/apache/ivy/core/settings/XmlSettingsParser.java =================================================================== --- src/java/org/apache/ivy/core/settings/XmlSettingsParser.java (revision 638656) +++ src/java/org/apache/ivy/core/settings/XmlSettingsParser.java (working copy) @@ -393,20 +393,36 @@ } private void classpathStarted(Map attributes) throws MalformedURLException { - String urlStr = (String) attributes.get("url"); - URL url = null; - if (urlStr == null) { - String file = (String) attributes.get("file"); - if (file == null) { - throw new IllegalArgumentException( - "either url or file should be given for classpath element"); + + //make sure that we have exactly one classpath attribute specified. + if (attributes.size() != 1) { + throw new IllegalArgumentException( + "Exactly one of refid, path, url, or file must be specified on "); + } + + String refId = (String) attributes.get("refid"); + if (refId != null) { + ivy.addClasspathId(refId); + } else { + String path = (String) attributes.get("path"); + if (path != null) { + ivy.addClasspath(path); } else { - url = new File(file).toURL(); + String urlStr = (String) attributes.get("url"); + if (urlStr != null) { + ivy.addClasspathURL(new URL(urlStr)); + } else { + String file = (String) attributes.get("file"); + if (file != null) { + ivy.addClasspathURL(new File(file).toURL()); + } else { + //some invalid attribute was specified. + throw new IllegalArgumentException( + "refid, path, url, or file must be specified on "); + } + } } - } else { - url = new URL(urlStr); } - ivy.addClasspathURL(url); } private void inConfiguratorStarted(String qName, Map attributes) { Index: test/custom-classpath/org/apache/ivy/plugins/resolver/ComplexClasspathResolver.java =================================================================== --- test/custom-classpath/org/apache/ivy/plugins/resolver/ComplexClasspathResolver.java (revision 0) +++ test/custom-classpath/org/apache/ivy/plugins/resolver/ComplexClasspathResolver.java (revision 0) @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.plugins.resolver; + +/** + * A dummy resolver that requires four different classes to load: + * ComplexClasspathResolver, SubclassedCustomResolver, CustomResolver, + * and ResolverUtility. All of these classes should be placed in separate + * jars at build time, so that we can test advanced classloading + * features in Ivy. + */ +public class ComplexClasspathResolver extends SubclassedCustomResolver { + + /** this field exists only to force us to load the ResolverUtility class at runtime */ + ResolverUtility dependency = new ResolverUtility(); + +} Index: test/custom-classpath/org/apache/ivy/plugins/resolver/ResolverUtility.java =================================================================== --- test/custom-classpath/org/apache/ivy/plugins/resolver/ResolverUtility.java (revision 0) +++ test/custom-classpath/org/apache/ivy/plugins/resolver/ResolverUtility.java (revision 0) @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.plugins.resolver; + +/** a dummy class, required by {@link ComplexClasspathResolver} */ +public class ResolverUtility { +} Index: test/custom-classpath/org/apache/ivy/plugins/resolver/SubclassedCustomResolver.java =================================================================== --- test/custom-classpath/org/apache/ivy/plugins/resolver/SubclassedCustomResolver.java (revision 0) +++ test/custom-classpath/org/apache/ivy/plugins/resolver/SubclassedCustomResolver.java (revision 0) @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.plugins.resolver; + +/** + * A subclassed custom resolver, to be packaged in a different + * jar from CustomResolver for testing complex classpath configurations. + */ +public class SubclassedCustomResolver extends CustomResolver { + +} Index: test/java/org/apache/ivy/ant/AntCallTriggerTest.java =================================================================== --- test/java/org/apache/ivy/ant/AntCallTriggerTest.java (revision 638656) +++ test/java/org/apache/ivy/ant/AntCallTriggerTest.java (working copy) @@ -18,28 +18,18 @@ package org.apache.ivy.ant; import java.io.File; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.Vector; import junit.framework.TestCase; import org.apache.ivy.util.FileUtil; -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.BuildLogger; -import org.apache.tools.ant.DefaultLogger; -import org.apache.tools.ant.DemuxInputStream; -import org.apache.tools.ant.DemuxOutputStream; -import org.apache.tools.ant.Main; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.ProjectHelper; -import org.apache.tools.ant.input.DefaultInputHandler; -import org.apache.tools.ant.input.InputHandler; public class AntCallTriggerTest extends TestCase { + + private TestAntRunner antRunner = new TestAntRunner(); + public void test() throws Exception { assertFalse(new File("test/triggers/ant-call/A/out/foo.txt").exists()); - runAnt(new File("test/triggers/ant-call/A/build.xml"), "resolve"); + antRunner.runAnt(new File("test/triggers/ant-call/A/build.xml"), "resolve"); // should have unzipped foo.zip assertTrue(new File("test/triggers/ant-call/A/out/foo.txt").exists()); } @@ -49,175 +39,4 @@ FileUtil.forceDelete(new File("test/triggers/ant-call/cache")); } - private void runAnt(File buildFile, String target) throws BuildException { - runAnt(buildFile, target, Project.MSG_INFO); - } - - private void runAnt(File buildFile, String target, int messageLevel) throws BuildException { - Vector targets = new Vector(); - targets.add(target); - runAnt(buildFile, targets, messageLevel); - } - - private void runAnt(File buildFile, Vector targets, int messageLevel) throws BuildException { - runBuild(buildFile, targets, messageLevel); - - // this exits the jvm at the end of the call - // Main.main(new String[] {"-f", buildFile.getAbsolutePath(), target}); - - // this does not set the good message level - // Ant ant = new Ant(); - // Project project = new Project(); - // project.setBaseDir(buildFile.getParentFile()); - // project.init(); - // - // ant.setProject(project); - // ant.setTaskName("ant"); - // - // ant.setAntfile(buildFile.getAbsolutePath()); - // ant.setInheritAll(false); - // if (target != null) { - // ant.setTarget(target); - // } - // ant.execute(); - } - - // //////////////////////////////////////////////////////////////////////////// - // miserable copy (updated to simple test cases) from ant Main class: - // the only available way I found to easily run ant exits jvm at the end - private void runBuild(File buildFile, Vector targets, int messageLevel) throws BuildException { - - final Project project = new Project(); - project.setCoreLoader(null); - - Throwable error = null; - - try { - addBuildListeners(project, messageLevel); - addInputHandler(project, null); - - PrintStream err = System.err; - PrintStream out = System.out; - InputStream in = System.in; - - // use a system manager that prevents from System.exit() - SecurityManager oldsm = null; - oldsm = System.getSecurityManager(); - - // SecurityManager can not be installed here for backwards - // compatibility reasons (PD). Needs to be loaded prior to - // ant class if we are going to implement it. - // System.setSecurityManager(new NoExitSecurityManager()); - try { - project.setDefaultInputStream(System.in); - System.setIn(new DemuxInputStream(project)); - System.setOut(new PrintStream(new DemuxOutputStream(project, false))); - System.setErr(new PrintStream(new DemuxOutputStream(project, true))); - - project.fireBuildStarted(); - - project.init(); - project.setUserProperty("ant.version", Main.getAntVersion()); - - project.setUserProperty("ant.file", buildFile.getAbsolutePath()); - - ProjectHelper.configureProject(project, buildFile); - - // make sure that we have a target to execute - if (targets.size() == 0) { - if (project.getDefaultTarget() != null) { - targets.addElement(project.getDefaultTarget()); - } - } - - project.executeTargets(targets); - } finally { - // put back the original security manager - // The following will never eval to true. (PD) - if (oldsm != null) { - System.setSecurityManager(oldsm); - } - - System.setOut(out); - System.setErr(err); - System.setIn(in); - } - } catch (RuntimeException exc) { - error = exc; - throw exc; - } catch (Error err) { - error = err; - throw err; - } finally { - project.fireBuildFinished(error); - } - } - - /** - * Adds the listeners specified in the command line arguments, along with the default listener, - * to the specified project. - * - * @param project - * The project to add listeners to. Must not be null. - */ - protected void addBuildListeners(Project project, int level) { - - // Add the default listener - project.addBuildListener(createLogger(level)); - - } - - /** - * Creates the InputHandler and adds it to the project. - * - * @param project - * the project instance. - * @param inputHandlerClassname - * @exception BuildException - * if a specified InputHandler implementation could not be loaded. - */ - private void addInputHandler(Project project, String inputHandlerClassname) - throws BuildException { - InputHandler handler = null; - if (inputHandlerClassname == null) { - handler = new DefaultInputHandler(); - } else { - try { - handler = (InputHandler) (Class.forName(inputHandlerClassname).newInstance()); - if (project != null) { - project.setProjectReference(handler); - } - } catch (ClassCastException e) { - String msg = "The specified input handler class " + inputHandlerClassname - + " does not implement the InputHandler interface"; - throw new BuildException(msg); - } catch (Exception e) { - String msg = "Unable to instantiate specified input handler " + "class " - + inputHandlerClassname + " : " + e.getClass().getName(); - throw new BuildException(msg); - } - } - project.setInputHandler(handler); - } - - // XXX: (Jon Skeet) Any reason for writing a message and then using a bare - // RuntimeException rather than just using a BuildException here? Is it - // in case the message could end up being written to no loggers (as the - // loggers could have failed to be created due to this failure)? - /** - * Creates the default build logger for sending build events to the ant log. - * - * @return the logger instance for this build. - */ - private BuildLogger createLogger(int level) { - BuildLogger logger = null; - logger = new DefaultLogger(); - - logger.setMessageOutputLevel(level); - logger.setOutputPrintStream(System.out); - logger.setErrorPrintStream(System.err); - - return logger; - } - } Index: test/java/org/apache/ivy/ant/IvyAntClassLoaderTest.java =================================================================== --- test/java/org/apache/ivy/ant/IvyAntClassLoaderTest.java (revision 0) +++ test/java/org/apache/ivy/ant/IvyAntClassLoaderTest.java (revision 0) @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.ant; + +import java.io.File; + +import junit.framework.TestCase; + +import org.apache.ivy.Ivy; +import org.apache.ivy.plugins.resolver.DependencyResolver; +import org.apache.tools.ant.Project; + +/** + * Test that {@link IvyAntClassLoaderManager} can use Ant paths to load + * Ivy plugins. + */ +public class IvyAntClassLoaderTest extends TestCase { + + private TestAntRunner antRunner = new TestAntRunner(); + + /** + * Test that ant paths and path expressions can be used + * in Ivy settings <classpath> elements. + */ + public void testAntClassLoader() { + //first, various sanity checks to make sure this is a valid test. + File buildFile = + new File("test/java/org/apache/ivy/ant/ivysettings-ant-classpath-build.xml"); + assertTrue("found build file " + buildFile.getAbsolutePath(), buildFile.isFile()); + //all of these classes should be loaded using elements, + //not from the test path + assertNotDefined("org.apache.ivy.plugins.resolver.CustomResolver"); + assertNotDefined("org.apache.ivy.plugins.resolver.SubclassedCustomResolver"); + assertNotDefined("org.apache.ivy.plugins.resolver.ComplexClasspathResolver"); + assertNotDefined("org.apache.ivy.plugins.resolver.ResolverUtility"); + + //set test directory location for use by ant script. + System.setProperty("ivy.custom.test.dir", + new File("test/java/org/apache/ivy/core/settings").getAbsolutePath()); + + //run the ant script, which only has to parse the ivy settings file for us. + Project project = antRunner.runAnt(buildFile, "configure", Project.MSG_VERBOSE); + + //inspect the ant ivy environment and verify that it was able to load the custom + //resolver using ant path entries. + IvyAntSettings settings = (IvyAntSettings) project.getReference("ivy.instance"); + Ivy ivy = settings.getConfiguredIvyInstance(); + assertNotNull("ivy configured in project", ivy); + DependencyResolver resolver = ivy.getSettings().getResolver("complex-custom"); + assertNotNull("resolver using ant paths was successfully loaded", resolver); + assertEquals("resolver has correct type", + "org.apache.ivy.plugins.resolver.ComplexClasspathResolver", + resolver.getClass().getName()); + } + + private static void assertNotDefined(String className) { + try { + Class.forName(className); + fail("the class " + className + " should not be reachable from the test classloader"); + } catch (ClassNotFoundException expected) {} + } +} Index: test/java/org/apache/ivy/ant/ivysettings-ant-classpath-build.xml =================================================================== --- test/java/org/apache/ivy/ant/ivysettings-ant-classpath-build.xml (revision 0) +++ test/java/org/apache/ivy/ant/ivysettings-ant-classpath-build.xml (revision 0) @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: test/java/org/apache/ivy/ant/ivysettings-ant-classpath.xml =================================================================== --- test/java/org/apache/ivy/ant/ivysettings-ant-classpath.xml (revision 0) +++ test/java/org/apache/ivy/ant/ivysettings-ant-classpath.xml (revision 0) @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file Index: test/java/org/apache/ivy/ant/TestAntRunner.java =================================================================== --- test/java/org/apache/ivy/ant/TestAntRunner.java (revision 0) +++ test/java/org/apache/ivy/ant/TestAntRunner.java (revision 0) @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ +package org.apache.ivy.ant; + +import java.io.File; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Vector; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.DemuxInputStream; +import org.apache.tools.ant.DemuxOutputStream; +import org.apache.tools.ant.Main; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.input.DefaultInputHandler; +import org.apache.tools.ant.input.InputHandler; + +/** + * Utility class for unit tests that need to run an Ant script and examine + * the results. + */ +public class TestAntRunner { + + /** + * equivalent to {@link #runAnt(File, String, Project.MSG_INFO) + * runAnt(buildFile, target, Project.MSG_INFO)} + */ + public Project runAnt(File buildFile, String target) throws BuildException { + return runAnt(buildFile, target, Project.MSG_INFO); + } + + /** + * equivalent to {@link #runAnt(File, Vector, int) + * runAnt(buildFile, targets, messageLevel)} with the targets + * vector containing only target. + */ + public Project runAnt(File buildFile, String target, int messageLevel) throws BuildException { + Vector targets = new Vector(); + targets.add(target); + return runAnt(buildFile, targets, messageLevel); + } + + /** + * Run the given Ant build file, executing all of the targets in targets. + * @param the ant log level for the build + * @return the Project used to run the file. + */ + public Project runAnt(File buildFile, Vector targets, int messageLevel) throws BuildException { + // //////////////////////////////////////////////////////////////////////////// + // miserable copy (updated to simple test cases) from ant Main class: + // the only available way I found to easily run ant exits jvm at the end + + final Project project = new Project(); + project.setCoreLoader(null); + + Throwable error = null; + + try { + addBuildListeners(project, messageLevel); + addInputHandler(project, null); + + PrintStream err = System.err; + PrintStream out = System.out; + InputStream in = System.in; + + // use a system manager that prevents from System.exit() + SecurityManager oldsm = null; + oldsm = System.getSecurityManager(); + + // SecurityManager can not be installed here for backwards + // compatibility reasons (PD). Needs to be loaded prior to + // ant class if we are going to implement it. + // System.setSecurityManager(new NoExitSecurityManager()); + try { + project.setDefaultInputStream(System.in); + System.setIn(new DemuxInputStream(project)); + System.setOut(new PrintStream(new DemuxOutputStream(project, false))); + System.setErr(new PrintStream(new DemuxOutputStream(project, true))); + + project.fireBuildStarted(); + + project.init(); + project.setUserProperty("ant.version", Main.getAntVersion()); + + project.setUserProperty("ant.file", buildFile.getAbsolutePath()); + + ProjectHelper.configureProject(project, buildFile); + + // make sure that we have a target to execute + if (targets.size() == 0) { + if (project.getDefaultTarget() != null) { + targets.addElement(project.getDefaultTarget()); + } + } + + project.executeTargets(targets); + } finally { + // put back the original security manager + // The following will never eval to true. (PD) + if (oldsm != null) { + System.setSecurityManager(oldsm); + } + + System.setOut(out); + System.setErr(err); + System.setIn(in); + } + } catch (RuntimeException exc) { + error = exc; + throw exc; + } catch (Error err) { + error = err; + throw err; + } finally { + project.fireBuildFinished(error); + } + return project; + } + + /** + * Adds the listeners specified in the command line arguments, along with the default listener, + * to the specified project. + * + * @param project + * The project to add listeners to. Must not be null. + */ + protected void addBuildListeners(Project project, int level) { + + // Add the default listener + project.addBuildListener(createLogger(level)); + + } + + /** + * Creates the InputHandler and adds it to the project. + * + * @param project + * the project instance. + * @param inputHandlerClassname + * @exception BuildException + * if a specified InputHandler implementation could not be loaded. + */ + protected void addInputHandler(Project project, String inputHandlerClassname) + throws BuildException { + InputHandler handler = null; + if (inputHandlerClassname == null) { + handler = new DefaultInputHandler(); + } else { + try { + handler = (InputHandler) (Class.forName(inputHandlerClassname).newInstance()); + if (project != null) { + project.setProjectReference(handler); + } + } catch (ClassCastException e) { + String msg = "The specified input handler class " + inputHandlerClassname + + " does not implement the InputHandler interface"; + throw new BuildException(msg); + } catch (Exception e) { + String msg = "Unable to instantiate specified input handler " + "class " + + inputHandlerClassname + " : " + e.getClass().getName(); + throw new BuildException(msg); + } + } + project.setInputHandler(handler); + } + + // XXX: (Jon Skeet) Any reason for writing a message and then using a bare + // RuntimeException rather than just using a BuildException here? Is it + // in case the message could end up being written to no loggers (as the + // loggers could have failed to be created due to this failure)? + /** + * Creates the default build logger for sending build events to the ant log. + * + * @return the logger instance for this build. + */ + protected BuildLogger createLogger(int level) { + BuildLogger logger = null; + logger = new DefaultLogger(); + + logger.setMessageOutputLevel(level); + logger.setOutputPrintStream(System.out); + logger.setErrorPrintStream(System.err); + + return logger; + } + +} Index: test/java/org/apache/ivy/core/settings/custom-resolver-2.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: test\java\org\apache\ivy\core\settings\custom-resolver-2.jar ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Index: test/java/org/apache/ivy/core/settings/custom-resolver-3.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: test\java\org\apache\ivy\core\settings\custom-resolver-3.jar ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Index: test/java/org/apache/ivy/core/settings/custom-resolver-4.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: test\java\org\apache\ivy\core\settings\custom-resolver-4.jar ___________________________________________________________________ Name: svn:mime-type + application/octet-stream