View | Details | Raw Unified | Return to bug 32142
Collapse All | Expand All

(-)build.xml (+9 lines)
Lines 1393-1398 Link Here
1393
            <include name="taskdefs/test2.antlib.xml"/>
1393
            <include name="taskdefs/test2.antlib.xml"/>
1394
        </zipfileset>
1394
        </zipfileset>
1395
    </jar>
1395
    </jar>
1396
1397
    <jar jarfile="${build.tests}/org/apache/tools/ant/include.jar">
1398
        <zipfileset dir="${tests.etc.dir}" fullpath="META-INF/services/org.apache.tools.ant.helper.EntityResolver">
1399
            <include name="core/include/entityResolverTest.txt"/>
1400
        </zipfileset>
1401
        <zipfileset dir="${tests.etc.dir}" fullpath="PUBLIC/include.inc">
1402
            <include name="core/include/public/include.inc"/>
1403
        </zipfileset>
1404
    </jar>
1396
  </target>
1405
  </target>
1397
1406
1398
  <target name="dump-info" depends="dump-sys-properties,run-which"/>
1407
  <target name="dump-info" depends="dump-sys-properties,run-which"/>
(-)docs/manual/CoreTypes/entities.html (+91 lines)
Line 0 Link Here
1
<html>
2
3
  <head>
4
    <meta http-equiv="Content-Language" content="en-us"></meta>
5
    <title>External Entities</title>
6
  </head>
7
8
  <body>
9
    <h2><a name="entities">External Entities</a></h2>
10
    <h3>Description</h3>
11
    <p>
12
      The XML parser used by ant is capable of resolving parsed external
13
      entities using system identifiers and, when configured appropriately,
14
      public identifiers.  These entities appear in one of the following forms:
15
    </p>
16
    <blockquote>
17
      <pre>
18
        &lt;!ENTITY <U>Name</U> SYSTEM <U>System Identifier</U>&gt;
19
        &lt;!ENTITY <U>Name</U> PUBLIC <U>Public Identifier</U> <U>System Identifier</U>&gt;
20
      </pre>
21
    </blockquote>
22
    <h3><a name="system">System Identifier</a></h3>
23
    <p>
24
      A URI that uniquely identifies the external resource whose contents will become the entity's definition.  All Java protocols are supported as well as Ant's custom protocol (<A href="../protocol.html#resource">resource</A>).
25
    </p>
26
    <h3><a name="public">Public Identifier</a></h3>
27
    <p>
28
      A public identifier is an unique, but otherwise, arbitrary identifier.  When using public identifiers, Ant depends upon an entity resolver identified by the JDK service "org.apache.tools.ant.helper.EntityResolver".  The author of this service may provide any implementation for mapping public identifiers to InputSource objects so long as the service's implementation extends the org.apache.tools.ant.helper.EntityResolver class.
29
    </p>
30
    <p>
31
      A sample implementation for a custom entity resolver is:
32
      <blockquote>
33
        <pre>
34
package my.org.ant.helper;
35
36
import org.apache.tools.ant.Project;
37
import org.xml.sax.InputSource;
38
39
public class PublicEntityResolver extends org.apache.tools.ant.helper.EntityResolver {
40
    /**
41
     * Resolves XML external entities.  Attempts to get the resource
42
     * using the system ID so that the most up-to-date resource is
43
     * used.  However, if the system ID fails then uses the public ID
44
     * to reference a backup copy of the resource.
45
     *
46
     * @param publicId The public identifier, or &lt;code&gt;null&lt;/code&gt;
47
     *                 if none is available. 
48
     * @param systemId The system identifier provided in the XML
49
     *                 document. Will not be &lt;code&gt;null&lt;/code&gt;.
50
     */
51
    public InputSource resolveEntity(String publicId,
52
                                     String systemId) {
53
        InputSource source = super.resolveEntity(publicId, systemId);
54
55
        if (source == null && publicId != null) {
56
            getProject().log("resolving publicId: " + publicId, Project.MSG_VERBOSE);
57
58
            // Use the public identifier to identify entities that
59
            // I stored within the same jar as this resolver.  In
60
            // principle, this will let me retrieve a resource
61
            // even when the system ID is blocked.
62
            if ("-- my public Identifier --".equals(publicId)) {
63
                String cachedResource="local/resource/my_private_resource_name";
64
65
                ClassLoader ldr = this.getClass().getClassLoader();
66
                source = 
67
                  new InputSource (ldr.getResource(cachedResource));
68
                source.setPublic(publicId);
69
            } else {
70
                getProject().log("failed to resolve publicId: " + publicId, Project.MSG_WARN);
71
            }
72
        }
73
74
        return source;
75
    }
76
}
77
          
78
        </pre>
79
      </blockquote>
80
    </p>
81
    <p>
82
Installation of this custom resolver requires construction of a jar file containing, as a minimum, two files.  The first file is a provider configuration file that will be placed in the jar file with the resource name 'META&minus;INF/services/org.apache.tools.ant.helper.EntityResolver'.  The content of this file is the fully qualified class name of the class implementing this service (See the JavaDoc for the java.util.jar package for full details).  The second file is the Java class file named by the provider configuration file.
83
    </p>
84
    
85
<hr>
86
<p align="center">Copyright &copy; 2003-2004 Apache Software
87
Foundation. All rights Reserved.</p>
88
89
</body>
90
</html>
91
(-)docs/manual/conceptstypeslist.html (-1 / +3 lines)
Lines 14-19 Link Here
14
<a href="clonevm.html">build.clonevm</a><br>
14
<a href="clonevm.html">build.clonevm</a><br>
15
<a href="sysclasspath.html">build.sysclasspath</a><br>
15
<a href="sysclasspath.html">build.sysclasspath</a><br>
16
<a href="CoreTasks/common.html">Common Attributes</a><br>
16
<a href="CoreTasks/common.html">Common Attributes</a><br>
17
<a href="protocol.html">resource Protocol</a><br>
17
18
18
<h3>Core Types</h3>
19
<h3>Core Types</h3>
19
<a href="CoreTypes/description.html">Description Type</a><br>
20
<a href="CoreTypes/description.html">Description Type</a><br>
Lines 45-51 Link Here
45
<a href="CoreTypes/antlib.html">Antlib</a><br>
46
<a href="CoreTypes/antlib.html">Antlib</a><br>
46
<a href="CoreTypes/antlib.html#antlibnamespace">Antlib namespace</a><br>
47
<a href="CoreTypes/antlib.html#antlibnamespace">Antlib namespace</a><br>
47
<a href="CoreTypes/antlib.html#currentnamespace">Current namespace</a><br>
48
<a href="CoreTypes/antlib.html#currentnamespace">Current namespace</a><br>
48
                                            
49
<h3>Entity Resolution</h3>
50
<a href="CoreTypes/entities.html">XML Entities</a><br>
49
51
50
<h3>Custom Components</h3>
52
<h3>Custom Components</h3>
51
<a href="CoreTypes/custom-programming.html">Custom Components</a><br>
53
<a href="CoreTypes/custom-programming.html">Custom Components</a><br>
(-)docs/manual/protocol.html (+30 lines)
Line 0 Link Here
1
<html>
2
3
  <head>
4
    <meta http-equiv="Content-Language" content="en-us"></meta>
5
    <title>Ant Uniform Resource Locator (URL) Protocol</title>
6
  </head>
7
8
  <body>
9
    <h2><a name="resource">Ant Uniform Resource Locator (URL) Protocol</a></h2>
10
    <h3>Description</h3>
11
    <p>Ant provides a custom URL protocol (<b>resource</b>) for referencing resources located via Ant's class loaders.  A URL using the <b>resource</b> protocol will use the syntax, <code>resource:path</code>, where <code>path</code> names the resource.  Leading slashes(/) on the <code>path</code> are automatically stripped off to match the class loader convention of using relative paths to name resources.
12
    </p>
13
    <p>
14
      Ant will search for the resource first using the current thread's context class loader.  If that loader fails, Ant will then try the class loader used to load the Ant core class files.  Finally, if the resource still hasn't been located, Ant will search for the resource using the system class loader.
15
    </p>
16
    <p>
17
      This protocol does not support specifying a host (//host name), port (:port number), fragment (#fragment), or query parameters (?parameters).
18
    </p>
19
    <p>
20
      The <b>resource</b> protocol may be used by any part (type, task, etc) of Ant. No type or task may change the class path referenced by the <b>resource</b> protocol except by changing the project's class path.
21
    </p>
22
    <p>
23
      In the event that the same resource is located in multiple jar files or directories, the <b>resource</b> protocol will return the first resource found when searching in the order specified by the project's classpath.
24
    </p>
25
<hr>
26
<p align="center">Copyright &copy; 2003-2004 Apache Software
27
Foundation. All rights Reserved.</p>
28
29
</body>
30
</html>
(-)src/etc/testcases/core/include/entityResolverTest.txt (+1 lines)
Line 0 Link Here
1
org.apache.tools.ant.IncludeTest$EntityResolver
(-)src/etc/testcases/core/include/public/include.inc (+3 lines)
Line 0 Link Here
1
<target name="test1">
2
    <echo message="from included entity"/>
3
</target>
(-)src/etc/testcases/core/include/public/include.xml (+9 lines)
Line 0 Link Here
1
<?xml version="1.0"?>
2
3
<!DOCTYPE project [
4
  <!ENTITY include PUBLIC "-- include.inc --" "file:unknown">
5
]>
6
7
<project name="include-test" basedir="." default="test1">
8
    &include;
9
</project>
(-)src/etc/testcases/core/include/resource/bad-include.xml (+9 lines)
Line 0 Link Here
1
<?xml version="1.0"?>
2
3
<!DOCTYPE project [
4
  <!ENTITY include SYSTEM "resource:/core/include/resource/missing.inc">
5
]>
6
7
<project name="include-test" basedir="." default="test1">
8
    &include;
9
</project>
(-)src/etc/testcases/core/include/resource/include.inc (+3 lines)
Line 0 Link Here
1
<target name="test1">
2
    <echo message="from included entity"/>
3
</target>
(-)src/etc/testcases/core/include/resource/include.xml (+9 lines)
Line 0 Link Here
1
<?xml version="1.0"?>
2
3
<!DOCTYPE project [
4
  <!ENTITY include SYSTEM "resource:/core/include/resource/include.inc">
5
]>
6
7
<project name="include-test" basedir="." default="test1">
8
    &include;
9
</project>
(-)src/main/org/apache/tools/ant/ProjectHelper.java (-30 / +11 lines)
Lines 26-31 Link Here
26
import java.util.Vector;
26
import java.util.Vector;
27
import org.apache.tools.ant.helper.ProjectHelper2;
27
import org.apache.tools.ant.helper.ProjectHelper2;
28
import org.apache.tools.ant.util.LoaderUtils;
28
import org.apache.tools.ant.util.LoaderUtils;
29
import org.apache.tools.ant.util.ServiceUtils;
29
import org.xml.sax.AttributeList;
30
import org.xml.sax.AttributeList;
30
31
31
/**
32
/**
Lines 182-219 Link Here
182
        // automatically if in CLASSPATH, with the right META-INF/services.
183
        // automatically if in CLASSPATH, with the right META-INF/services.
183
        if (helper == null) {
184
        if (helper == null) {
184
            try {
185
            try {
185
                ClassLoader classLoader = LoaderUtils.getContextClassLoader();
186
                String[] helperClasses = ServiceUtils.newServiceUtils().serviceProvidersFor(ProjectHelper.class);
186
                InputStream is = null;
187
                if (helperClasses != null) {
187
                if (classLoader != null) {
188
                    helperClass = helperClasses [0];
188
                    is = classLoader.getResourceAsStream(SERVICE_ID);
189
                
189
                }
190
                    if (helperClass != null) {
190
                if (is == null) {
191
                        helper = newHelper (helperClass);
191
                    is = ClassLoader.getSystemResourceAsStream(SERVICE_ID);
192
                }
193
194
                if (is != null) {
195
                    // This code is needed by EBCDIC and other strange systems.
196
                    // It's a fix for bugs reported in xerces
197
                    InputStreamReader isr;
198
                    try {
199
                        isr = new InputStreamReader(is, "UTF-8");
200
                    } catch (java.io.UnsupportedEncodingException e) {
201
                        isr = new InputStreamReader(is);
202
                    }
203
                    BufferedReader rd = new BufferedReader(isr);
204
205
                    String helperClassName = rd.readLine();
206
                    rd.close();
207
208
                    if (helperClassName != null
209
                        && !"".equals(helperClassName)) {
210
211
                        helper = newHelper(helperClassName);
212
                    }
192
                    }
213
                }
193
                }
214
            } catch (Exception ex) {
194
            } catch (BuildException e) {
215
                System.out.println("Unable to load ProjectHelper "
195
                throw e;
216
                    + "from service \"" + SERVICE_ID);
196
            } catch (Exception e) {
197
                throw new BuildException (e);
217
            }
198
            }
218
        }
199
        }
219
200
(-)src/main/org/apache/tools/ant/helper/EntityResolver.java (+159 lines)
Line 0 Link Here
1
/*
2
 * Copyright  2000-2004 The Apache Software Foundation
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17
18
package org.apache.tools.ant.helper;
19
20
import java.io.File;
21
import java.io.FileInputStream;
22
import java.io.FileNotFoundException;
23
import java.io.InputStream;
24
import java.io.InputStreamReader;
25
import org.apache.tools.ant.BuildException;
26
import org.apache.tools.ant.Project;
27
import org.apache.tools.ant.ProjectHelper;
28
import org.apache.tools.ant.util.FileUtils;
29
import org.apache.tools.ant.util.LoaderUtils;
30
import org.xml.sax.InputSource;
31
32
/**
33
 * <P>Ant's default entity resolver provides for resolving relative file URLs
34
 * with respect to the current build file's directory.</P> <P>Developers who
35
 * wish to provide their own entity resolver must extend from this class.</P>
36
 */
37
public class EntityResolver implements org.xml.sax.EntityResolver {
38
    private File          buildFileParent;
39
    private AntXMLContext context;
40
    private Project       project;
41
42
    /**
43
     * Construct an uninitialized entity resolver.  Ant must call either {@link
44
     * #setContext(AntXMLContext)}, or {@link
45
     * #setProject(org.apache.tools.ant.Project)} and {@link
46
     * #setBuildFileParent(java.io.File)} before this resolver can be used.
47
     */
48
    public EntityResolver() {
49
    }
50
51
    /**
52
     * helper for path -> URI and URI -> path conversions.
53
     */
54
    private static final FileUtils fu = FileUtils.newFileUtils();
55
56
    /**
57
     * Set the Context for which this EntityResolver is working.
58
     *
59
     * @param context the Ant XML context to configure
60
     */
61
    void setContext(AntXMLContext context) {
62
        this.context = context;
63
    }
64
65
    /**
66
     * <P>Set the Project for which this EntityResolver is working.</P> <P>NOTE:
67
     * The project set by this method is ignored once {@link
68
     * #setContext(AntXMLContext)} has been called.</P>
69
     *
70
     * @param project the ANT project to configure.
71
     */
72
    void setProject(Project project) {
73
        this.project = project;
74
    }
75
76
    /**
77
     * <P>Set the build file parent for resolving relative file URLs.</P>
78
     * <P>NOTE: The build file parent(directory) set by this method is ignored
79
     * once {@link #setContext(AntXMLContext)} has been called.</P>
80
     *
81
     * @param file the project's build directory
82
     */
83
    void setBuildFileParent(File buildFileParent) {
84
        this.buildFileParent = buildFileParent;
85
    }
86
87
    /**
88
     * Get the Project for which this EntityResolver is working.
89
     *
90
     * @return the ANT Project
91
     */
92
93
    public Project getProject() {
94
        if (context == null) {
95
            return project;
96
        }
97
98
        return context.getProject();
99
    }
100
101
    /**
102
     * Get the BuildFileParent for which this EntityResolver is working.
103
     *
104
     * @return the ANT BuildFileParent
105
     */
106
107
    public File getBuildFileParent() {
108
        if (context == null) {
109
            return buildFileParent;
110
        }
111
112
        return context.getBuildFileParent();
113
    }
114
115
    /**
116
     * Get the class loader for this context.
117
     */
118
    public ClassLoader getClassLoader() {
119
        return LoaderUtils.getContextClassLoader();
120
    }
121
122
    /**
123
     * Resolves file URIs relative to the build file.
124
     *
125
     * @param publicId The public identifier, or <code>null</code> if none is
126
     *                 available. Ignored in this implementation.
127
     * @param systemId The system identifier provided in the XML document. Will
128
     *                 not be <code>null</code>.
129
     */
130
    public InputSource resolveEntity(String publicId,
131
                                     String systemId) {
132
        getProject().log("resolving systemId: " + systemId, Project.MSG_VERBOSE);
133
134
        if (systemId.startsWith("file:")) {
135
            String path = fu.fromURI(systemId);
136
137
            File file = new File(path);
138
            if (!file.isAbsolute()) {
139
                file = fu.resolveFile(getBuildFileParent(), path);
140
            }
141
            try {
142
                getProject().log("resolved systemId: " + systemId 
143
                                 + " as " + file.getAbsolutePath(),
144
                                 Project.MSG_DEBUG);
145
146
                InputSource inputSource = 
147
                    new InputSource(new InputStreamReader(new FileInputStream(file)));
148
                inputSource.setSystemId(fu.toURI(file.getAbsolutePath()));
149
                return inputSource;
150
            } catch (FileNotFoundException fne) {
151
                getProject().log(file.getAbsolutePath() + " could not be found",
152
                                 Project.MSG_WARN);
153
            }
154
        }
155
156
        // use default if not file or file not found
157
        return null;
158
    }
159
}
(-)src/main/org/apache/tools/ant/helper/ProjectHelper2.java (-24 / +21 lines)
Lines 36-41 Link Here
36
36
37
import org.apache.tools.ant.util.JAXPUtils;
37
import org.apache.tools.ant.util.JAXPUtils;
38
import org.apache.tools.ant.util.FileUtils;
38
import org.apache.tools.ant.util.FileUtils;
39
import org.apache.tools.ant.util.ServiceUtils;
39
40
40
import org.apache.tools.ant.ProjectHelper;
41
import org.apache.tools.ant.ProjectHelper;
41
import org.apache.tools.ant.Project;
42
import org.apache.tools.ant.Project;
Lines 66-71 Link Here
66
     */
67
     */
67
    private static FileUtils fu = FileUtils.newFileUtils();
68
    private static FileUtils fu = FileUtils.newFileUtils();
68
69
70
    static {
71
        URLStreamHandlerFactory factory = 
72
            (URLStreamHandlerFactory) ServiceUtils.newServiceUtils()
73
            .serviceFor (URLStreamHandlerFactory.class);
74
75
        try {
76
            java.net.URL.setURLStreamHandlerFactory (factory);
77
        } catch (Error e) {
78
            // Error will occur if both ProjectHelper2 and ProjectHelperImpl are loaded
79
        }
80
    }
81
69
    /**
82
    /**
70
     * Parse an unknown element from a url
83
     * Parse an unknown element from a url
71
     *
84
     *
Lines 377-382 Link Here
377
        private Stack antHandlers = new Stack();
390
        private Stack antHandlers = new Stack();
378
        private AntHandler currentHandler = null;
391
        private AntHandler currentHandler = null;
379
        private AntXMLContext context;
392
        private AntXMLContext context;
393
        private EntityResolver entityResolver;
380
394
381
        /**
395
        /**
382
         * Creates a new RootHandler instance.
396
         * Creates a new RootHandler instance.
Lines 388-393 Link Here
388
            currentHandler = rootHandler;
402
            currentHandler = rootHandler;
389
            antHandlers.push(currentHandler);
403
            antHandlers.push(currentHandler);
390
            this.context = context;
404
            this.context = context;
405
406
            this.entityResolver =
407
                (EntityResolver) ServiceUtils.newServiceUtils()
408
                .serviceFor (org.apache.tools.ant.helper.EntityResolver.class);
409
410
            entityResolver.setContext(context);
391
        }
411
        }
392
412
393
        /**
413
        /**
Lines 410-439 Link Here
410
         */
430
         */
411
        public InputSource resolveEntity(String publicId,
431
        public InputSource resolveEntity(String publicId,
412
                                         String systemId) {
432
                                         String systemId) {
413
433
            return entityResolver.resolveEntity(publicId, systemId);
414
            context.getProject().log("resolving systemId: "
415
                + systemId, Project.MSG_VERBOSE);
416
417
            if (systemId.startsWith("file:")) {
418
                String path = fu.fromURI(systemId);
419
420
                File file = new File(path);
421
                if (!file.isAbsolute()) {
422
                    file = fu.resolveFile(context.getBuildFileParent(), path);
423
                }
424
                try {
425
                    InputSource inputSource =
426
                            new InputSource(new FileInputStream(file));
427
                    inputSource.setSystemId(fu.toURI(file.getAbsolutePath()));
428
                    return inputSource;
429
                } catch (FileNotFoundException fne) {
430
                    context.getProject().log(file.getAbsolutePath()
431
                        + " could not be found", Project.MSG_WARN);
432
                }
433
434
            }
435
            // use default if not file or file not found
436
            return null;
437
        }
434
        }
438
435
439
        /**
436
        /**
(-)src/main/org/apache/tools/ant/helper/ProjectHelperImpl.java (-35 / +26 lines)
Lines 35-40 Link Here
35
import org.apache.tools.ant.TaskContainer;
35
import org.apache.tools.ant.TaskContainer;
36
import org.apache.tools.ant.UnknownElement;
36
import org.apache.tools.ant.UnknownElement;
37
import org.apache.tools.ant.util.FileUtils;
37
import org.apache.tools.ant.util.FileUtils;
38
import org.apache.tools.ant.util.ServiceUtils;
38
import org.apache.tools.ant.util.JAXPUtils;
39
import org.apache.tools.ant.util.JAXPUtils;
39
import org.xml.sax.AttributeList;
40
import org.xml.sax.AttributeList;
40
import org.xml.sax.DocumentHandler;
41
import org.xml.sax.DocumentHandler;
Lines 83-88 Link Here
83
     */
84
     */
84
    private static FileUtils fu = FileUtils.newFileUtils();
85
    private static FileUtils fu = FileUtils.newFileUtils();
85
86
87
    static {
88
        URLStreamHandlerFactory factory = 
89
            (URLStreamHandlerFactory) ServiceUtils.newServiceUtils()
90
            .serviceFor (URLStreamHandlerFactory.class);
91
92
        try {
93
            java.net.URL.setURLStreamHandlerFactory (factory);
94
        } catch (Error e) {
95
            // Error will occur if both ProjectHelper2 and ProjectHelperImpl are loaded
96
        }
97
    }
98
86
    /**
99
    /**
87
     * default constructor
100
     * default constructor
88
     */
101
     */
Lines 125-133 Link Here
125
            inputSource.setSystemId(uri);
138
            inputSource.setSystemId(uri);
126
            project.log("parsing buildfile " + buildFile + " with URI = "
139
            project.log("parsing buildfile " + buildFile + " with URI = "
127
                + uri, Project.MSG_VERBOSE);
140
                + uri, Project.MSG_VERBOSE);
141
142
            EntityResolver entityResolver =
143
                (EntityResolver) ServiceUtils.newServiceUtils()
144
                .serviceFor (org.apache.tools.ant.helper.EntityResolver.class);
145
146
            entityResolver.setProject(project);
147
            entityResolver.setBuildFileParent(buildFileParent);
148
128
            HandlerBase hb = new RootHandler(this);
149
            HandlerBase hb = new RootHandler(this);
129
            parser.setDocumentHandler(hb);
150
            parser.setDocumentHandler(hb);
130
            parser.setEntityResolver(hb);
151
            parser.setEntityResolver(entityResolver);
131
            parser.setErrorHandler(hb);
152
            parser.setErrorHandler(hb);
132
            parser.setDTDHandler(hb);
153
            parser.setDTDHandler(hb);
133
            parser.parse(inputSource);
154
            parser.parse(inputSource);
Lines 143-148 Link Here
143
                    be.setLocation(location);
164
                    be.setLocation(location);
144
                }
165
                }
145
                throw be;
166
                throw be;
167
            } else if (t == null) {
168
                t = exc;
146
            }
169
            }
147
170
148
            throw new BuildException(exc.getMessage(), t, location);
171
            throw new BuildException(exc.getMessage(), t, location);
Lines 150-155 Link Here
150
            Throwable t = exc.getException();
173
            Throwable t = exc.getException();
151
            if (t instanceof BuildException) {
174
            if (t instanceof BuildException) {
152
                throw (BuildException) t;
175
                throw (BuildException) t;
176
            } else if (t == null) {
177
                t = exc;
153
            }
178
            }
154
            throw new BuildException(exc.getMessage(), t);
179
            throw new BuildException(exc.getMessage(), t);
155
        } catch (FileNotFoundException exc) {
180
        } catch (FileNotFoundException exc) {
Lines 279-318 Link Here
279
304
280
        public RootHandler(ProjectHelperImpl helperImpl) {
305
        public RootHandler(ProjectHelperImpl helperImpl) {
281
            this.helperImpl = helperImpl;
306
            this.helperImpl = helperImpl;
282
        }
283
284
        /**
285
         * Resolves file: URIs relative to the build file.
286
         *
287
         * @param publicId The public identifier, or <code>null</code>
288
         *                 if none is available. Ignored in this
289
         *                 implementation.
290
         * @param systemId The system identifier provided in the XML
291
         *                 document. Will not be <code>null</code>.
292
         */
293
        public InputSource resolveEntity(String publicId,
294
                                         String systemId) {
295
296
            helperImpl.project.log("resolving systemId: " + systemId, Project.MSG_VERBOSE);
297
298
            if (systemId.startsWith("file:")) {
299
                String path = fu.fromURI(systemId);
300
301
                File file = new File(path);
302
                if (!file.isAbsolute()) {
303
                    file = fu.resolveFile(helperImpl.buildFileParent, path);
304
                }
305
                try {
306
                    InputSource inputSource = new InputSource(new FileInputStream(file));
307
                    inputSource.setSystemId(fu.toURI(file.getAbsolutePath()));
308
                    return inputSource;
309
                } catch (FileNotFoundException fne) {
310
                    helperImpl.project.log(file.getAbsolutePath() + " could not be found",
311
                                Project.MSG_WARN);
312
                }
313
            }
314
            // use default if not file or file not found
315
            return null;
316
        }
307
        }
317
308
318
        /**
309
        /**
(-)src/main/org/apache/tools/ant/helper/URLStreamHandlerFactory.java (+59 lines)
Line 0 Link Here
1
/*
2
 * Copyright  2000-2004 The Apache Software Foundation
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17
18
package org.apache.tools.ant.helper;
19
20
/**
21
 * <P>Ant's default URL stream handler factory. This factory adds the following
22
 * class name pattern
23
 * <code>org.apache.tools.ant.protocol.<U><I>protocol</I></U>.Handler</code> to
24
 * the default patterns supported by Java.  Any number of custom protocols may
25
 * be added to Ant by simply declaring a new handler class matching this
26
 * pattern.</P>
27
 *
28
 * <P>Ant's resource handler, {@link
29
 * org.apache.tools.ant.protocol.resource.Handler}, may be used as an example
30
 * implementation.</P>
31
 * 
32
 * <P>This handler may be overriden using JDK1.3 service discovery with the
33
 * service identifier of
34
 * <code>META&#8211;INF/services/org.apache.tools.ant.helper.URLStreamHandlerFactory</code>.</P>
35
 */
36
37
public class URLStreamHandlerFactory implements java.net.URLStreamHandlerFactory {
38
    /**
39
     * Creates a new <code>URLStreamHandler</code> instance with the specified
40
     * protocol.
41
     *
42
     * @param protocol the protocol ("<code>resource</code>", <code>ftp</code>",
43
     * "<code>http</code>", "<code>nntp</code>", etc.).
44
     * @return a <code>URLStreamHandler</code> for the specific protocol.
45
     */
46
    public java.net.URLStreamHandler createURLStreamHandler(String protocol) {
47
        try {
48
            return (java.net.URLStreamHandler)
49
                this.getClass().getClassLoader()
50
                .loadClass("org.apache.tools.ant.protocol." + protocol 
51
                           + ".Handler").newInstance();
52
        } catch (Exception e) {
53
            // It's OK for Ant to not provide a handler.  When that happens,
54
            // java.net.URL will fall back on its internal logic.
55
        }
56
57
        return null;
58
    }
59
}
(-)src/main/org/apache/tools/ant/protocol/resource/Handler.java (+94 lines)
Line 0 Link Here
1
/*
2
 * Copyright  2000-2004 The Apache Software Foundation
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17
18
package org.apache.tools.ant.protocol.resource;
19
20
import java.io.IOException;
21
import java.net.URL;
22
23
/**
24
 * The URL stream handler for the resource protocol.  This handler parses the
25
 * string representation of a URL storing the various elements within a provided
26
 * URL object. It also provides the factory for creating a connection specific
27
 * to a URL based on this protocol.
28
 */
29
public final class Handler extends java.net.URLStreamHandler {
30
    /**
31
     * Default constructor.  Creates a component used by the java.net framework
32
     * to add support for the <code>resource</code> protocol.
33
     */
34
    public Handler () {
35
    }
36
37
    /**
38
     * Determines whether two URLs reference the same resource.
39
     */
40
    protected boolean equals(URL u1, URL u2) {
41
	String path1 = u1.getPath();
42
	String path2 = u2.getPath();
43
	return (super.equals(u1, u2) && 
44
                path1 == null? path2 == null: path1.equals(path2));
45
    }
46
47
    /**
48
     * <p>Parses the string representation of a <code>URL</code> into a
49
     * <code>URL</code> object.</P> <p>If there is any inherited context, then
50
     * it has already been copied into the <code>URL</code> argument.</P>
51
     * <p>Uses the <code>parseURL</code> method of <code>URLStreamHandler</code>
52
     * to parse the string representation as if it were an <code>http</code>
53
     * specification. Standard components, such as the authority and query, must
54
     * not be present for the <code>spec</code> to be legal.</P>
55
     *
56
     * @param u the <code>URL</code> to receive the result of parsing the spec.
57
     * @param spec the <code>String</code> representing the URL that must be
58
     * parsed.
59
     * @param start the character index at which to begin parsing. This is just
60
     * past the '<code>:</code>' (if there is one) that specifies the
61
     * determination of the protocol name.
62
     * @param limit the character position to stop parsing at. This is the end
63
     * of the string or the position of the "<code>#</code>" character, if
64
     * present. All information after the sharp sign indicates an anchor.
65
     */
66
    protected void parseURL (URL url, String spec, int start, int finish) {
67
        super.parseURL (url, spec, start, finish);
68
69
        if (url.getAuthority () != null) {
70
            throw new RuntimeException ("resource: protocol does not support "
71
                                        + "setting the URL's authority "
72
                                        + "(i.e. host, port, user, or password)");
73
        }
74
75
        if (url.getQuery () != null) {
76
            throw new RuntimeException ("resource: protocol does not " 
77
                                        + "support query parameters.");
78
        }
79
    }
80
81
    /**
82
     * Opens a connection to the object referenced by 
83
     * <code>url</code>.
84
     *
85
     * @param url the URL that this connects to.
86
     * @return an object specific to a <code>resource:URL</code> that extends
87
     * from the {@link java.net.URLConnection} class.
88
     * @exception IOException if an I/O error occurs while opening the
89
     * connection.
90
     */
91
    protected java.net.URLConnection openConnection(URL url) throws IOException {
92
	return new ResourceURLConnection(url);
93
    }
94
}
(-)src/main/org/apache/tools/ant/protocol/resource/ResourceURLConnection.java (+665 lines)
Line 0 Link Here
1
/*
2
 * Copyright  2000-2004 The Apache Software Foundation
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17
18
package org.apache.tools.ant.protocol.resource;
19
20
import org.apache.tools.ant.util.LoaderUtils;
21
22
import java.io.IOException;
23
import java.io.InputStream;
24
import java.net.URL;
25
import java.net.URLConnection;
26
import java.net.URLDecoder;
27
import java.util.Collections;
28
29
/** open an resource input stream given a URL */
30
31
public class ResourceURLConnection extends URLConnection {
32
    /**
33
     * URLConnection of the jar URL that is the actual source of this resource.
34
     */
35
    protected URLConnection physicalConnection = null;
36
37
    /**
38
     * <P>Construct a URL connection that is specific to the resource protocol.  This constructor
39
     * decodes the given URL to get the resource name, performs a class loader search to locate
40
     * that resource, and then constructs the physical URLConnection to that resource.</P>
41
     * <P>The physical connection is created by first getting the physical URL by calling
42
     * getResource() on the context class loader (See
43
     * <code>LoaderUtils.getContextClassLoader()</code>).  If that class loader fails, a second
44
     * attempt will be made using the same class loader as used to load the ResourceURLConnection
45
     * class.  Finally, if neither of those class loaders succeed, the system class loader will
46
     * be attempted.</P>
47
     *
48
     * @param url the target of this connection
49
     * @throws IOException to report that the resource can not be located as well as all I/O
50
     * errors while creating the physical connection.
51
     */
52
    public ResourceURLConnection(URL url) throws IOException {
53
	super(url);
54
55
        String resourceName = url.getPath();
56
        
57
        // Decode escaped characters now that the path has been identified.  This will permit
58
        // escaping path delimiters into the actual path.
59
        // Note: FileUtils.fromURI() can NOT be used here as it is hardcoded to only work with
60
        // file: URLs.
61
        resourceName = URLDecoder.decode(resourceName);
62
63
        URL physicalUrl = null;
64
        findURL:
65
        {
66
            ClassLoader[] loaders = new ClassLoader[] {
67
                LoaderUtils.getContextClassLoader(),
68
                this.getClass().getClassLoader()};
69
70
            for (int i = 0; i < loaders.length; i++) {
71
                physicalUrl = loaders[i].getResource(resourceName);
72
                if (physicalUrl != null) {
73
                    break findURL;
74
                }
75
            }
76
77
            for (int i = 0; i < loaders.length; i++) {
78
                physicalUrl = loaders[i].getSystemResource(resourceName);
79
                if (physicalUrl != null) {
80
                    break findURL;
81
                }
82
            }
83
84
            StringBuffer msg = new StringBuffer();
85
            msg
86
                .append("Failed to find resource ")
87
                .append(url)
88
                .append(" in ");
89
90
            for (int i = 0; i < loaders.length; i++) {
91
                if (loaders[i] instanceof org.apache.tools.ant.AntClassLoader) {
92
                    msg.append(((org.apache.tools.ant.AntClassLoader) loaders[i]).getClasspath());
93
                } else if (loaders[i] instanceof java.net.URLClassLoader) {
94
                    URL urls[] = ((java.net.URLClassLoader) loaders[i]).getURLs();
95
96
                    msg.append(urls[0].toString());
97
                    for (int j = 1; j < urls.length; j++) {
98
                        msg.append(", ");
99
                        msg.append(urls[j].toString());
100
                    }
101
                }
102
            }
103
104
            throw new IOException(msg.toString());
105
        }
106
107
        physicalConnection = physicalUrl.openConnection();
108
    }
109
110
    /**
111
     * Opens a communications link to the resource referenced by the physical connection.
112
     */
113
    public void connect() throws IOException {
114
	physicalConnection.connect();
115
    }
116
117
    /**
118
     * Returns the value of the <code>allowUserInteraction</code> field for
119
     * this object.
120
     *
121
     * @return  the value of the <code>allowUserInteraction</code> field for
122
     *          this object.
123
     * @see     #setAllowUserInteraction(boolean)
124
     */
125
    public boolean getAllowUserInteraction() {
126
        return physicalConnection.getAllowUserInteraction();
127
    }
128
129
    /**
130
     * Returns the default value of a <code>URLConnection</code>'s
131
     * <code>useCaches</code> flag.
132
     * <p>
133
     * Ths default is "sticky", being a part of the static state of all
134
     * URLConnections.  This flag applies to the next, and all following
135
     * URLConnections that are created.
136
     *
137
     * @return  the default value of a <code>URLConnection</code>'s
138
     *          <code>useCaches</code> flag.
139
     * @see     #setDefaultUseCaches(boolean)
140
     */
141
    public boolean getDefaultUseCaches() {
142
        return physicalConnection.getDefaultUseCaches();
143
    }
144
145
    /**
146
     * Returns the value of this <code>URLConnection</code>'s
147
     * <code>doInput</code> flag.
148
     *
149
     * @return  the value of this <code>URLConnection</code>'s
150
     *          <code>doInput</code> flag.
151
     * @see     #setDoInput(boolean)
152
     */
153
    public boolean getDoInput() {
154
        return physicalConnection.getDoInput();
155
    }
156
157
    /**
158
     * Returns the value of this <code>URLConnection</code>'s
159
     * <code>doOutput</code> flag.
160
     *
161
     * @return  the value of this <code>URLConnection</code>'s
162
     *          <code>doOutput</code> flag.
163
     * @see     #setDoOutput(boolean)
164
     */
165
    public boolean getDoOutput() {
166
        return physicalConnection.getDoOutput();
167
    }
168
169
    /**
170
     * Returns the value of this <code>URLConnection</code>'s
171
     * <code>useCaches</code> field.
172
     *
173
     * @return  the value of this <code>URLConnection</code>'s
174
     *          <code>useCaches</code> field.
175
     * @see #setUseCaches(boolean)
176
     */
177
    public boolean getUseCaches() {
178
        return physicalConnection.getUseCaches();
179
    }
180
181
    /**
182
     * Returns the value of the <code>content-length</code> header field.
183
     *
184
     * @return  the content length of the resource that this connection's URL
185
     *          references, or <code>-1</code> if the content length is
186
     *          not known.
187
     */
188
    public int getContentLength() {
189
        return physicalConnection.getContentLength();
190
    }
191
192
    /**
193
     * Returns the value of the named field parsed as a number.
194
     * <p>
195
     * This form of <code>getHeaderField</code> exists because some
196
     * connection types (e.g., <code>http-ng</code>) have pre-parsed
197
     * headers. Classes for that connection type can override this method
198
     * and short-circuit the parsing.
199
     *
200
     * @param   name      the name of the header field.
201
     * @param   Default   the default value.
202
     * @return  the value of the named field, parsed as an integer. The
203
     *          <code>Default</code> value is returned if the field is
204
     *          missing or malformed.
205
     */
206
    public int getHeaderFieldInt(String name,int Default) {
207
        return physicalConnection.getHeaderFieldInt(name, Default);
208
    }
209
210
    /**
211
     * Returns an input stream that reads from this open connection.
212
     *
213
     * @return     an input stream that reads from this open connection.
214
     * @exception  IOException              if an I/O error occurs while
215
     *               creating the input stream.
216
     * @exception  UnknownServiceException  if the protocol does not support
217
     *               input.
218
     */
219
    public java.io.InputStream getInputStream()throws IOException {
220
        return physicalConnection.getInputStream();
221
    }
222
223
    /**
224
     * Returns an output stream that writes to this connection.
225
     *
226
     * @return     an output stream that writes to this connection.
227
     * @exception  IOException              if an I/O error occurs while
228
     *               creating the output stream.
229
     * @exception  UnknownServiceException  if the protocol does not support
230
     *               output.
231
     */
232
    public java.io.OutputStream getOutputStream()throws IOException {
233
        return physicalConnection.getOutputStream();
234
    }
235
236
    /**
237
     * Retrieves the contents of this URL connection.
238
     *
239
     * @return     the object fetched. The <code>instanceof</code> operator
240
     *               should be used to determine the specific kind of object
241
     *               returned.
242
     * @exception  IOException              if an I/O error occurs while
243
     *               getting the content.
244
     * @exception  UnknownServiceException  if the protocol does not support
245
     *               the content type.
246
     * @see        java.net.URLConnection#getContent()
247
     */
248
    public Object getContent()throws IOException {
249
        return physicalConnection.getContent();
250
    }
251
252
    /**
253
     * Retrieves the contents of this URL connection.
254
     *
255
     * @param classes the <code>Class</code> array
256
     * indicating the requested types
257
     * @return the object fetched that is the first match of the type specified in the classes
258
     *         array. Returns <code>null</code> if none of the requested types are supported.
259
     *         The <code>instanceof</code> operator should be used to determine the specific
260
     *         kind of object returned.
261
     * @exception IOException if an I/O error occurs while getting the content.
262
     * @exception UnknownServiceException if the protocol does not support the content type.
263
     * @see        java.net.URLConnection#getContent(Class[])
264
     */
265
    public Object getContent(Class[] classes) throws IOException {
266
        return physicalConnection.getContent(classes);
267
    }
268
269
    /**
270
     * Returns the value of the <code>content-encoding</code> header field.
271
     *
272
     * @return  the content encoding of the resource that the URL references,
273
     *          or <code>null</code> if not known.
274
     * @see     java.net.URLConnection#getHeaderField(java.lang.String)
275
     */
276
    public String getContentEncoding() {
277
        return physicalConnection.getContentEncoding();
278
    }
279
280
    /**
281
     * Returns the value of the <code>content-type</code> header field.
282
     *
283
     * @return  the content type of the resource that the URL references,
284
     *          or <code>null</code> if not known.
285
     * @see     java.net.URLConnection#getHeaderField(java.lang.String)
286
     */
287
    public String getContentType() {
288
        return physicalConnection.getContentType();
289
    }
290
291
    /**
292
     * <P>Returns the value for the <code>n</code><sup>th</sup> header field.
293
     * It returns <code>null</code> if there are fewer than
294
     * <code>n+1</code> fields.</P>
295
     *
296
     * <P>This method can be used in conjunction with the
297
     * {@link #getHeaderFieldKey(int) getHeaderFieldKey} method to iterate through all
298
     * the headers in the message. </P>
299
     *
300
     * @param   n   an index, where n>=0
301
     * @return  the value of the <code>n</code><sup>th</sup> header field
302
     *		or <code>null</code> if there are fewer than <code>n+1</code> fields
303
     * @see     java.net.URLConnection#getHeaderFieldKey(int)
304
     */
305
    public String getHeaderField(int n) {
306
        return physicalConnection.getHeaderField(n);
307
    }
308
309
    /**
310
     * <P>Returns the value of the named header field.</P>
311
     *
312
     * <P>If called on a connection that sets the same header multiple times
313
     * with possibly different values, only the last value is returned.</P>
314
     *
315
     * @param   name   the name of a header field.
316
     * @return  the value of the named header field, or <code>null</code>
317
     *          if there is no such field in the header.
318
     */
319
    public String getHeaderField(String name) {
320
        return physicalConnection.getHeaderField(name);
321
    }
322
323
    /**
324
     * Returns the key for the <code>n</code><sup>th</sup> header field.
325
     * It returns <code>null</code> if there are fewer than <code>n+1</code> fields.
326
     *
327
     * @param   n   an index, where n>=0
328
     * @return  the key for the <code>n</code><sup>th</sup> header field,
329
     *          or <code>null</code> if there are fewer than <code>n+1</code>
330
     *		fields.
331
     */
332
    public String getHeaderFieldKey(int n) {
333
        return physicalConnection.getHeaderFieldKey(n);
334
    }
335
336
    /**
337
     * Returns the value of the named general request property for this
338
     * connection.
339
     *
340
     * @param key the keyword by which the request is known (e.g., "accept").
341
     * @return  the value of the named general request property for this
342
     *           connection. If key is null, then null is returned.
343
     * @throws IllegalStateException if already connected
344
     * @see #setRequestProperty(java.lang.String, java.lang.String)
345
     */
346
    public String getRequestProperty(String key) {
347
        return physicalConnection.getRequestProperty(key);
348
    }
349
350
    /**
351
     * Returns a <code>String</code> representation of this URL connection.
352
     *
353
     * @return  a string representation of this <code>URLConnection</code>.
354
     */
355
    public String toString() {
356
	return this.getClass().getName() + ":" + url;
357
    }
358
359
    /**
360
     * Returns a permission object representing the permission
361
     * necessary to make the connection represented by this
362
     * object. This method returns null if no permission is
363
     * required to make the connection.
364
     *
365
     * <p>The permission returned may dependent upon the state of the
366
     * connection. For example, the permission before connecting may be
367
     * different from that after connecting. For example, an HTTP
368
     * sever, say foo.com, may redirect the connection to a different
369
     * host, say bar.com. Before connecting the permission returned by
370
     * the connection will represent the permission needed to connect
371
     * to foo.com, while the permission returned after connecting will
372
     * be to bar.com.
373
     *
374
     * <p>Permissions are generally used for two purposes: to protect
375
     * caches of objects obtained through URLConnections, and to check
376
     * the right of a recipient to learn about a particular URL. In
377
     * the first case, the permission should be obtained
378
     * <em>after</em> the object has been obtained. For example, in an
379
     * HTTP connection, this will represent the permission to connect
380
     * to the host from which the data was ultimately fetched. In the
381
     * second case, the permission should be obtained and tested
382
     * <em>before</em> connecting.
383
     *
384
     * @return the permission object representing the permission
385
     * necessary to make the connection represented by this
386
     * URLConnection.
387
     *
388
     * @exception IOException if the computation of the permission
389
     * requires network or file I/O and an exception occurs while
390
     * computing it.
391
     */
392
    public java.security.Permission getPermission()throws IOException {
393
        return physicalConnection.getPermission();
394
    }
395
396
    /**
397
     * Returns the value of the <code>date</code> header field.
398
     *
399
     * @return  the sending date of the resource that the URL references,
400
     *          or <code>0</code> if not known. The value returned is the
401
     *          number of milliseconds since January 1, 1970 GMT.
402
     * @see     java.net.URLConnection#getHeaderField(java.lang.String)
403
     */
404
    public long getDate() {
405
        return physicalConnection.getDate();
406
    }
407
408
    /**
409
     * Returns the value of the <code>expires</code> header field.
410
     *
411
     * @return  the expiration date of the resource that this URL references,
412
     *          or 0 if not known. The value is the number of milliseconds since
413
     *          January 1, 1970 GMT.
414
     * @see     java.net.URLConnection#getHeaderField(java.lang.String)
415
     */
416
    public long getExpiration() {
417
        return physicalConnection.getExpiration();
418
    }
419
420
    /**
421
     * Returns the value of the named field parsed as date.
422
     * The result is the number of milliseconds since January 1, 1970 GMT
423
     * represented by the named field.
424
     * <p>
425
     * This form of <code>getHeaderField</code> exists because some
426
     * connection types (e.g., <code>http-ng</code>) have pre-parsed
427
     * headers. Classes for that connection type can override this method
428
     * and short-circuit the parsing.
429
     *
430
     * @param   name     the name of the header field.
431
     * @param   Default   a default value.
432
     * @return  the value of the field, parsed as a date. The value of the
433
     *          <code>Default</code> argument is returned if the field is
434
     *          missing or malformed.
435
     */
436
    public long getHeaderFieldDate(String name,long Default) {
437
        return physicalConnection.getHeaderFieldDate(name, Default);
438
    }
439
440
    /**
441
     * Returns the value of this object's <code>ifModifiedSince</code> field.
442
     *
443
     * @return  the value of this object's <code>ifModifiedSince</code> field.
444
     * @see #setIfModifiedSince(long)
445
     */
446
    public long getIfModifiedSince() {
447
        return physicalConnection.getIfModifiedSince();
448
    }
449
450
    /**
451
     * Returns the value of the <code>last-modified</code> header field.
452
     * The result is the number of milliseconds since January 1, 1970 GMT.
453
     *
454
     * @return  the date the resource referenced by this
455
     *          <code>URLConnection</code> was last modified, or 0 if not known.
456
     * @see     java.net.URLConnection#getHeaderField(java.lang.String)
457
     */
458
    public long getLastModified() {
459
        return physicalConnection.getLastModified();
460
    }
461
462
    /**
463
     * Set the value of the <code>allowUserInteraction</code> field of
464
     * this <code>URLConnection</code>.
465
     *
466
     * @param   allowuserinteraction   the new value.
467
     * @throws IllegalStateException if already connected
468
     * @see     #getAllowUserInteraction()
469
     */
470
    public void setAllowUserInteraction(boolean allowuserinteraction) {
471
        physicalConnection.setAllowUserInteraction(allowuserinteraction);
472
    }
473
474
    /**
475
     * Sets the default value of the <code>useCaches</code> field to the
476
     * specified value.
477
     *
478
     * @param   defaultusecaches   the new value.
479
     * @see     #getDefaultUseCaches()
480
     */
481
    public void setDefaultUseCaches(boolean defaultusecaches) {
482
        physicalConnection.setDefaultUseCaches(defaultusecaches);
483
    }
484
485
    /**
486
     * Sets the value of the <code>doInput</code> field for this
487
     * <code>URLConnection</code> to the specified value.
488
     * <p>
489
     * A URL connection can be used for input and/or output.  Set the DoInput
490
     * flag to true if you intend to use the URL connection for input,
491
     * false if not.  The default is true.
492
     *
493
     * @param   doinput   the new value.
494
     * @throws IllegalStateException if already connected
495
     * @see     java.net.URLConnection#doInput
496
     * @see #getDoInput()
497
     */
498
    public void setDoInput(boolean doinput) {
499
        physicalConnection.setDoInput(doinput);
500
    }
501
502
    /**
503
     * Sets the value of the <code>doOutput</code> field for this
504
     * <code>URLConnection</code> to the specified value.
505
     * <p>
506
     * A URL connection can be used for input and/or output.  Set the DoOutput
507
     * flag to true if you intend to use the URL connection for output,
508
     * false if not.  The default is false.
509
     *
510
     * @param   dooutput   the new value.
511
     * @throws IllegalStateException if already connected
512
     * @see #getDoOutput()
513
     */
514
    public void setDoOutput(boolean dooutput) {
515
        physicalConnection.setDoOutput(dooutput);
516
    }
517
518
    /**
519
     * Sets the value of the <code>ifModifiedSince</code> field of
520
     * this <code>URLConnection</code> to the specified value.
521
     *
522
     * @param   ifmodifiedsince   the new value.
523
     * @throws IllegalStateException if already connected
524
     * @see     #getIfModifiedSince()
525
     */
526
    public void setIfModifiedSince(long ifmodifiedsince) {
527
        physicalConnection.setIfModifiedSince(ifmodifiedsince);
528
    }
529
530
    /**
531
     * Sets the general request property. If a property with the key already
532
     * exists, overwrite its value with the new value.
533
     *
534
     * @param   key     the keyword by which the request is known
535
     *                  (e.g., "<code>accept</code>").
536
     * @param   value   the value associated with it.
537
     * @throws IllegalStateException if already connected
538
     * @throws NullPointerException if key is <CODE>null</CODE>
539
     * @see #getRequestProperty(java.lang.String)
540
     */
541
    public void setRequestProperty(String key, String value) {
542
        physicalConnection.setRequestProperty(key, value);
543
    }
544
545
    /**
546
     * Sets the value of the <code>useCaches</code> field of this
547
     * <code>URLConnection</code> to the specified value.
548
     * <p>
549
     * Some protocols do caching of documents.  Occasionally, it is important
550
     * to be able to "tunnel through" and ignore the caches (e.g., the
551
     * "reload" button in a browser).  If the UseCaches flag on a connection
552
     * is true, the connection is allowed to use whatever caches it can.
553
     *  If false, caches are to be ignored.
554
     *  The default value comes from DefaultUseCaches, which defaults to
555
     * true.
556
     *
557
     * @param usecaches a <code>boolean</code> indicating whether
558
     * or not to allow caching
559
     * @throws IllegalStateException if already connected
560
     * @see #getUseCaches()
561
     */
562
    public void setUseCaches(boolean usecaches) {
563
        physicalConnection.setUseCaches(usecaches);
564
    }
565
566
    /**
567
     * Returns an unmodifiable Map of the header fields.
568
     * The Map keys are Strings that represent the
569
     * response-header field names. Each Map value is an
570
     * unmodifiable List of Strings that represents
571
     * the corresponding field values.
572
     *
573
     * @return a Map of header fields
574
     * @since 1.4 (returns Collections.EMPTY_MAP for all previous versions)
575
     */
576
    public java.util.Map getHeaderFields() {
577
        try {
578
            java.lang.reflect.Method method =
579
                physicalConnection.getClass().getDeclaredMethod("getHeaderFields", new Class[]{});
580
            return (java.util.Map) method.invoke(physicalConnection, new Object[]{});
581
        } catch (NoSuchMethodException e) {
582
        } catch (java.lang.reflect.InvocationTargetException e) {
583
            Throwable tgt = e.getTargetException();
584
585
            if (tgt instanceof IllegalStateException) {
586
                throw (IllegalStateException) tgt;
587
            }
588
589
            throw new RuntimeException(tgt.getClass().getName() + ": " + tgt.getMessage());
590
        } catch (IllegalAccessException e) {
591
            throw new RuntimeException(e.getClass().getName() + ": " + e.getMessage());
592
        }
593
594
        return Collections.EMPTY_MAP;
595
    }
596
597
    /**
598
     * Returns an unmodifiable Map of general request
599
     * properties for this connection. The Map keys
600
     * are Strings that represent the request-header
601
     * field names. Each Map value is a unmodifiable List
602
     * of Strings that represents the corresponding
603
     * field values.
604
     *
605
     * @return  a Map of the general request properties for this connection.
606
     * @throws IllegalStateException if already connected
607
     * @since 1.4 (returns Collections.EMPTY_MAP for all previous versions)
608
     */
609
    public java.util.Map getRequestProperties() {
610
        try {
611
            java.lang.reflect.Method method =
612
                physicalConnection.getClass().getDeclaredMethod("getRequestProperties", new Class[]{});
613
            return (java.util.Map) method.invoke(physicalConnection, new Object[]{});
614
        } catch (NoSuchMethodException e) {
615
        } catch (java.lang.reflect.InvocationTargetException e) {
616
            Throwable tgt = e.getTargetException();
617
618
            if (tgt instanceof IllegalStateException) {
619
                throw (IllegalStateException) tgt;
620
            }
621
622
            throw new RuntimeException(tgt.getClass().getName() + ": " + tgt.getMessage());
623
        } catch (IllegalAccessException e) {
624
            throw new RuntimeException(e.getClass().getName() + ": " + e.getMessage());
625
        }
626
627
	return Collections.EMPTY_MAP;
628
    }
629
630
    /**
631
     * Adds a general request property specified by a
632
     * key-value pair.  This method will not overwrite
633
     * existing values associated with the same key.
634
     *
635
     * @param   key     the keyword by which the request is known
636
     *                  (e.g., "<code>accept</code>").
637
     * @param   value  the value associated with it.
638
     * @throws IllegalStateException if already connected
639
     * @throws NullPointerException if key is null
640
     * @see #getRequestProperties(java.lang.String)
641
     * @since 1.4 (does nothing for all previous versions)
642
     */
643
644
    public void addRequestProperty(String s1, String s2) {
645
        try {
646
            java.lang.reflect.Method method =
647
                physicalConnection.getClass()
648
                .getDeclaredMethod("addRequestProperty",
649
                                   new Class[]{String.class, String.class});
650
            method.invoke(physicalConnection, new Object[]{s1, s2});
651
        } catch (NoSuchMethodException e) {
652
        } catch (java.lang.reflect.InvocationTargetException e) {
653
            Throwable tgt = e.getTargetException();
654
655
            if (tgt instanceof IllegalStateException) {
656
                throw (IllegalStateException) tgt;
657
            }
658
659
            throw new RuntimeException(tgt.getClass().getName()
660
                                       + ": " + tgt.getMessage());
661
        } catch (IllegalAccessException e) {
662
            throw new RuntimeException(e.getClass().getName() + ": " + e.getMessage());
663
        }
664
    }
665
}
(-)src/main/org/apache/tools/ant/util/ServiceUtils.java (+384 lines)
Line 0 Link Here
1
/*
2
 * Copyright  2001-2004 The Apache Software Foundation
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17
18
package org.apache.tools.ant.util;
19
20
import java.io.BufferedReader;
21
import java.io.InputStream;
22
import java.io.InputStreamReader;
23
import java.lang.reflect.Modifier;
24
import java.lang.reflect.Constructor;
25
import java.util.HashMap;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Vector;
29
import org.apache.tools.ant.BuildException;
30
import org.apache.tools.ant.util.LoaderUtils;
31
32
/**
33
 * A collection of methods that implement the JDK 1.3 service discovery
34
 * mechanism.  While this API was introduced in JDK 1.3, it's implementation is
35
 * fully compatible with JDK 1.2.
36
 */
37
38
public class ServiceUtils {
39
    private static Map knownServiceProviderClassNames = new HashMap ();
40
41
    /**
42
     * Factory for constructing a new ServiceUtils instance.
43
     *
44
     * @return a new instance of ServiceUtils.
45
     */
46
    public static ServiceUtils newServiceUtils() {
47
        return new ServiceUtils();
48
    }
49
50
    /**
51
     * The default constructor.
52
     */
53
    protected ServiceUtils() {
54
    }
55
56
    /**
57
     * Gets the service providers for the specified service.  The service's
58
     * fully qualified class name is used to construct the name of a service
59
     * configuration file that should be found using either the context or
60
     * system class loader.  This configuration file should contain one, or
61
     * more, class names identifying providers of the specified service.
62
     *
63
     * This method simply locates and parses the configuration file without any
64
     * consideration for the validity of the class names found therein.
65
     *
66
     * @param service the interface or class that the service provider must
67
     * implement
68
     * @return An array of service provider class names that are presumed to
69
     * implement the service; null when no providers were found.
70
     *
71
     * @since Ant1.6
72
     * @exception BuildException if the provider class can not be loaded
73
     */
74
    public String[] serviceProvidersFor(Class service) throws BuildException {
75
        String serviceID = "META-INF/services/" + service.getName ();
76
        String[] classNames = (String[]) knownServiceProviderClassNames.get(serviceID);
77
78
        if (classNames == null) {
79
            try {
80
                ClassLoader classLoader = LoaderUtils.getContextClassLoader();
81
                InputStream is = null;
82
                if (classLoader != null) {
83
                    is = classLoader.getResourceAsStream(serviceID);
84
                }
85
                if (is == null) {
86
                    is = ClassLoader.getSystemResourceAsStream(serviceID);
87
                }
88
89
                if (is != null) {
90
                    // This code is needed by EBCDIC and other strange systems.
91
                    // It's a fix for bugs reported in xerces
92
                    InputStreamReader isr;
93
                    try {
94
                        isr = new InputStreamReader(is, "UTF-8");
95
                    } catch (java.io.UnsupportedEncodingException e) {
96
                        isr = new InputStreamReader(is);
97
                    }
98
                    BufferedReader rd = new BufferedReader(isr);
99
100
                    // This simple parser implements the Service Provider syntax
101
                    // described in the JAR Specification.
102
                    String className       = null;
103
                    List   providerClasses = new Vector ();
104
                    while ((className = rd.readLine()) != null) {
105
                        // Strip off comment beginning with first pound sign (#).
106
                        int pound = className.indexOf ('#');
107
108
                        if (pound > -1) {
109
                            className = className.substring (0, pound);
110
                        }
111
112
                        // Ignore spaces and tabs
113
                        className = className.trim ();
114
115
                        if (!"".equals (className)) {
116
                            providerClasses.add (className);
117
                        }
118
                    }
119
                    rd.close();
120
121
                    if (providerClasses.size () > 0) {
122
                        classNames = new String [providerClasses.size ()];
123
                        providerClasses.toArray (classNames);
124
                        knownServiceProviderClassNames.put(serviceID, classNames);
125
                    }
126
                }
127
            } catch (Exception ex) {
128
                throw new BuildException("Unable to find service for \""
129
                                         + serviceID + "\"", ex);
130
            }
131
        }
132
133
        return classNames;
134
    }
135
136
    /**
137
     * A constraint based selector to pick one of possibly several service
138
     * provider classes.  This class represents an extension to the JDK 1.3
139
     * service discovery mechanism.
140
     */
141
    public interface Constraints {
142
        /**
143
         * Determines whether the specified service provider class satisfies all
144
         * of the constraints imposed by the current request.
145
         *
146
         * @throws Exception thrown when the provider does not satisfy all of
147
         * the constraints.
148
         */
149
        public void satisfiedBy (Class provider) throws Exception;
150
    }
151
152
    /**
153
     * Gets the service provider class that provides the specified service.
154
     * This service provider class must meet all of the following requirements.
155
     *  <OL><LI>It must have been named in the service provider configuration
156
     *  file.</LI>
157
     *  <LI>It must be assignable to the service class (that is, it either
158
     *  implements or extends the service class).</LI>
159
     *  <LI>It must provide a public default constructor.</LI>
160
     *  <LI>It must satisfy the optional constraints parameter.</LI></OL>
161
     *
162
     * When no service provider can be found satisfying the preceeding
163
     * requirements.  A check will be made to see if the service class is itself
164
     * instantiable as the service provider.  For this to be true, the following
165
     * requirements must be met.
166
     *  <OL><LI>It must not be an interface or abstract class.</LI>
167
     *  <LI>It must provide a public default constructor.</LI>
168
     *  <LI>It must satisfy the optional constraints parameter.</LI></OL>
169
     *
170
     * @param service the interface or class that the service provider must
171
     * implement
172
     * @param constraints optional constraints that can be used to select one of
173
     * several possible service provider classes.  The first class, in order in
174
     * which they were listed in the service provider configuration file, to
175
     * satisfy all of the constraints will be returned.
176
     * @return The class providing the service subject to the constraints; null
177
     * when no providers were found.
178
     *
179
     * @since Ant1.6
180
     * @exception BuildException if no provider classes were found that
181
     * satisfied all of the constraints
182
     */
183
    public Class serviceProviderFor(Class service,
184
                                    Constraints constraints) throws BuildException {
185
        String[] classNames = serviceProvidersFor(service);
186
187
        // Use lazy construction to avoid creating a StringBuffer when the first
188
        // provider satisfies all of the constraints.
189
        StringBuffer status = null;
190
191
        if (classNames != null) {
192
            ClassLoader classLoader = LoaderUtils.getContextClassLoader();
193
194
            for (int i = 0; i < classNames.length; i++) {
195
                String className = classNames [i];
196
                Class  provider  = null;
197
198
                // Try to load the provider.  If not loaded, continue with next provider.
199
                try {
200
                    provider = classLoader.loadClass(className);
201
                } catch (Exception e) {
202
                    try {
203
                        provider = Class.forName(className);
204
                    } catch (Exception ex) {
205
                        status = addStatusMsg(status, service,
206
                                              "  Unable to find class named '"
207
                                              + className + "'.");
208
                        continue;
209
                    }
210
                }
211
212
                String errMsg = validateProvider (provider, service, constraints);
213
214
                if (errMsg == null) {
215
                    return provider;
216
                }
217
218
                status = addStatusMsg(status, service, errMsg);
219
            }
220
        }
221
222
        // if no valid service providers found in the configuration file, throw
223
        // the error
224
        if (status != null) {
225
            throw new BuildException(status.toString ());
226
        }
227
228
        // No service providers found in the configuration file.  See if it is
229
        // possible to use the service class as a minimal provider
230
        String errMsg = validateProvider (service, service, constraints);
231
232
        if (errMsg == null) {
233
            return service;
234
        }
235
236
        return null;
237
    }
238
239
    /**
240
     * Gets the service provider class that provides the specified service.
241
     * This service provider class must meet all of the following requirements.
242
     *  <OL><LI>It must have been named in the service provider configuration
243
     *  file.</LI>
244
     *  <LI>It must be assignable to the service class (that is, it either
245
     *  implements or extends the service class).</LI>
246
     *  <LI>It must provide a public default constructor.</LI></OL>
247
     *
248
     * @param service the interface or class that the service provider must
249
     * implement
250
     * @return The class providing the service; null when no service provider
251
     * was found.
252
     *
253
     * @since Ant1.6
254
     * @exception BuildException if the provider class can not be loaded
255
     */
256
    public Class serviceProviderFor(Class service) throws BuildException {
257
        return serviceProviderFor(service, null);
258
    }
259
260
    /**
261
     * Gets the service provider instance for the specified service.
262
     *
263
     * @param service the interface or class that the service provider must
264
     * implement
265
     * @param constraints optional constraints that can be used to select one of
266
     * several possible service provider classes.  The first class, in order in
267
     * which they were listed in the service provider configuration file, to
268
     * satisfy all of the constraints will be returned.
269
     * @return The instance providing the service
270
     *
271
     * @since Ant1.6
272
     * @exception BuildException if the instance can not created
273
     */
274
    public Object serviceFor(Class service,
275
                             Constraints constraints) throws BuildException {
276
        try {
277
            return serviceProviderFor(service, constraints).newInstance ();
278
        } catch (BuildException e) {
279
            throw e;
280
        } catch (Exception e) {
281
            throw new BuildException("The service provider for "
282
                                     + service.getClass ()
283
                                     + " could not be instantiated.");
284
        }
285
    }
286
287
    /**
288
     * Gets the service provider instance for the specified service.
289
     *
290
     * @param service the interface or class that the service provider must
291
     * implement
292
     * @return The instance providing the service
293
     *
294
     * @since Ant1.6
295
     * @exception BuildException if the instance can not created
296
     */
297
    public Object serviceFor(Class service) throws BuildException {
298
        return serviceFor(service, null);
299
    }
300
301
    /**
302
     * Used internally by serviceProviderFor.  Assembles a multi-line exception
303
     * message over the course of several calls.
304
     */
305
    private StringBuffer addStatusMsg (StringBuffer allMessages,
306
                                       Class service, String message) {
307
        if (message != null) {
308
            if (allMessages == null) {
309
                allMessages = new StringBuffer ();
310
                allMessages.append ("Attempting to locate service provider for ");
311
                allMessages.append (service.getName ());
312
                allMessages.append ("\n");
313
            }
314
315
            allMessages.append ("  ");
316
            allMessages.append (message);
317
            allMessages.append ("\n");
318
        }
319
320
        return allMessages;
321
    }
322
323
    /**
324
     * Validate the indicated provider by checking that it satisfies all
325
     * internal and external provider constraints.  If anything is wrong, return
326
     * a string suitable for throwing in an exception.
327
     *
328
     * @param provider the class that is being validated as the service provider
329
     * @param service the interface or class that the service provider must
330
     * implement
331
     * @param constraints optional constraints that can be used to select one of
332
     * several possible service provider classes.  The first class, in order in
333
     * which they were listed in the service provider configuration file, to
334
     * satisfy all of the constraints will be returned.
335
     * @return a string suitable for use as the message in an exception
336
     *
337
     * @since Ant1.6
338
     */
339
    private String validateProvider(Class provider, Class service, Constraints constraints) {
340
        // Validate the provider class
341
        if (provider.isInterface ()){
342
            return provider.getName() + " can not be a provider as it is an interface.";
343
        } else if (Modifier.isAbstract(provider.getModifiers())) {
344
            return provider.getName() + " can not be a provider as it is abstract.";
345
        } else if (provider.isArray()) {
346
            return provider.getName() + " can not be a provider as it is an array.";
347
        } else if (provider.isPrimitive()) {
348
            return provider.getName() + " can not be a provider as it is primitive.";
349
        } else if (service.isAssignableFrom(provider) == false) {
350
            return provider.getName() + " can not be cast to " + service.getName ();
351
        }
352
353
        // Validate the provider's constructor
354
355
        // Use getDeclaredConstructors, not getConstructor nor getConstructors,
356
        // to get constructor when class is using implicit default constructor.
357
        foundPublicDefaultConstructor: {
358
            Constructor[] constructors = provider.getDeclaredConstructors();
359
360
            for (int i = 0; i < constructors.length; i++) {
361
                Constructor c = constructors [i];
362
                if (Modifier.isPublic (c.getModifiers ()) 
363
                    && c.getParameterTypes ().length == 0) {
364
                    break foundPublicDefaultConstructor;
365
                }
366
            }
367
368
            return "No default constructor for " + provider.getName();
369
        }
370
371
        // This provider satisfies the built-in constraints.
372
373
        // if the caller provided custom constraints, check them.
374
        if (constraints != null) {
375
            try {
376
                constraints.satisfiedBy(provider);
377
            } catch (Exception e) {
378
                return e.getMessage ();
379
            }
380
        }
381
382
        return null; // valid provider
383
    }
384
}
(-)src/testcases/org/apache/tools/ant/IncludeTest.java (+49 lines)
Lines 18-23 Link Here
18
package org.apache.tools.ant;
18
package org.apache.tools.ant;
19
19
20
import org.apache.tools.ant.BuildFileTest;
20
import org.apache.tools.ant.BuildFileTest;
21
import org.apache.tools.ant.util.LoaderUtils;
22
import java.net.URL;
23
import java.net.URLClassLoader;
24
import org.xml.sax.InputSource;
25
import java.io.InputStream;
26
import java.io.InputStreamReader;
21
27
22
/**
28
/**
23
 * Test the build file inclusion using XML entities.
29
 * Test the build file inclusion using XML entities.
Lines 122-125 Link Here
122
        expectLog("test1", "from included entity in 'with space'");
128
        expectLog("test1", "from included entity in 'with space'");
123
    }
129
    }
124
130
131
    public void testOfResource() {
132
        configureProject("src/etc/testcases/core/include/resource/include.xml");
133
        expectLog("test1", "from included entity");
134
    }
135
136
    public void testOfBadResource() {
137
        try {
138
            configureProject("src/etc/testcases/core/include/resource/bad-include.xml");
139
        } catch (BuildException e) {
140
            assertTrue(e.getMessage() + " should contain 'Failed to find resource resource:'",
141
                       e.getMessage().indexOf("Failed to find resource resource:") > -1);
142
        }
143
    }
144
145
    public void testOfPublicId() {
146
        URL jarURL = this.getClass().getResource("/org/apache/tools/ant/include.jar");
147
        assertNotNull(jarURL);
148
149
        ClassLoader parentLoader = LoaderUtils.getContextClassLoader();
150
        URLClassLoader testLoader = new URLClassLoader(new URL[]{jarURL}, 
151
                                                       parentLoader);
152
        LoaderUtils.setContextClassLoader(testLoader);
153
        configureProject("src/etc/testcases/core/include/public/include.xml");
154
        expectLog("test1", "from included entity");
155
        LoaderUtils.setContextClassLoader(parentLoader);
156
    }
157
158
    public static class EntityResolver extends org.apache.tools.ant.helper.EntityResolver {
159
        public InputSource resolveEntity(String publicId,
160
                                         String systemId) {
161
            if ("-- include.inc --".equals(publicId)) {
162
                InputStream is = getClassLoader().getResourceAsStream("PUBLIC/include.inc");
163
        
164
                if (is != null) {
165
                    InputSource inputSource = new InputSource(new InputStreamReader(is));
166
                    inputSource.setPublicId(publicId);
167
                    return inputSource;
168
                }
169
            }
170
171
            return super.resolveEntity(publicId, systemId);
172
        }
173
    }
125
}
174
}
(-)src/testcases/org/apache/tools/ant/util/ServiceUtilsTest.java (+236 lines)
Line 0 Link Here
1
/*
2
 * Copyright  2001-2004 The Apache Software Foundation
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17
18
package org.apache.tools.ant.util;
19
20
import java.io.*;
21
import java.util.jar.*;
22
23
import junit.framework.TestCase;
24
25
import org.apache.tools.ant.util.LoaderUtils;
26
import org.apache.tools.ant.BuildException;
27
28
import java.lang.reflect.Constructor;
29
import java.lang.reflect.Modifier;
30
31
/**
32
 * Tests for org.apache.tools.ant.util.ServiceUtils.
33
 *
34
 */
35
public class ServiceUtilsTest extends TestCase {
36
37
    private ServiceUtils su;
38
39
    public ServiceUtilsTest(String name) {
40
        super(name);
41
    }
42
43
    interface ServiceNoProvider {};
44
45
    abstract static class AbstractService {} {
46
    }
47
48
    interface ServiceNoConfig {};
49
50
    private byte[] getJarContents () {
51
        ByteArrayOutputStream baos = new ByteArrayOutputStream ();
52
53
        try {
54
            JarOutputStream jos = new JarOutputStream (baos);
55
            PrintStream     ps  = new PrintStream (jos, true);
56
57
            jos.putNextEntry (new JarEntry ("META-INF/services/" + ServiceNoProvider.class.getName ()));
58
            ps.println ("# comment"); // legal comment
59
            ps.println ("  # comment"); // ignorable whitespace with comment
60
            ps.println (ServiceNoProvider.class.getName () + "# comment"); // Interface, not instantiable
61
            ps.println (""); // blank line
62
63
            jos.putNextEntry (new JarEntry ("META-INF/services/" + NoDefaultConstructorProvider.class.getName ()));
64
            ps.println (NoDefaultConstructorProvider.class.getName ());
65
66
            jos.putNextEntry (new JarEntry ("META-INF/services/" + PrivateDefaultConstructorProvider.class.getName ()));
67
            ps.println (PrivateDefaultConstructorProvider.class.getName ());
68
69
            jos.putNextEntry (new JarEntry ("META-INF/services/" + AbstractService.class.getName ()));
70
            ps.println (NoDefaultConstructorProvider.class.getName ()); // not valid (wrong provider)
71
            ps.println (AbstractService   .class.getName ()); // not valid (abstract)
72
            ps.println (ValidProvider     .class.getName ()); // valid
73
            ps.println (ValidProvider2    .class.getName ()); // also valid
74
            ps.close ();
75
            jos.close ();
76
            baos.close ();
77
        } catch (IOException e) {
78
            e.printStackTrace ();
79
        }
80
81
        return baos.toByteArray ();
82
    }
83
84
    public void setUp() {
85
        su = ServiceUtils.newServiceUtils();
86
87
        final byte[] jarByteArray = getJarContents ();
88
89
        // This ClassLoader provides a custom getResourcesAsStream method that can retrieve the
90
        // service configuration files from the custom jar file byte array.  It does not support
91
        // loading classes from that source.  It depends upon its parent to load classes.
92
        ClassLoader customLoader = new ClassLoader (LoaderUtils.getContextClassLoader ()) {
93
                public InputStream getResourceAsStream(String name) {
94
                    InputStream is = super.getResourceAsStream(name);
95
                    
96
                    if (is != null) {
97
                        return is;
98
                    }
99
                    
100
                    if (name.startsWith ("META-INF/services/") == false) {
101
                        return null;
102
                    }
103
104
                    ByteArrayInputStream bais = new ByteArrayInputStream (jarByteArray);
105
                    try {
106
                        JarInputStream jis = new JarInputStream (bais);
107
                        JarEntry entry;
108
                    
109
                        while ((entry = jis.getNextJarEntry ()) != null) {
110
                            if (entry.getName ().equals (name)) {
111
                                return jis;
112
                            }
113
                        }
114
                    } catch (Exception e) {
115
                    }
116
117
                    return null;
118
                }
119
            };
120
121
        LoaderUtils.setContextClassLoader (customLoader);
122
    }
123
124
    public void tearDown() {
125
        LoaderUtils.setContextClassLoader (LoaderUtils.getContextClassLoader ().getParent ());
126
        su = null;
127
    }
128
129
    public void testServiceProvidersFor () {
130
        assertNull (su.serviceProvidersFor (ServiceNoConfig.class));
131
        String[] expectedProviders = new String [] {ServiceNoProvider.class.getName ()};
132
        String[] actualProviders   = su.serviceProvidersFor (ServiceNoProvider.class);
133
134
        if (expectedProviders.length != actualProviders.length) {
135
            fail ("Expected " + expectedProviders.length + " providers but found " + actualProviders.length);
136
        }
137
138
        for (int i = 0; i < expectedProviders.length; i++) {
139
            assertEquals (expectedProviders[i], actualProviders[i]);
140
        }
141
    }
142
143
    public void testServiceProviderForDefaults () {
144
        assertNull (                        su.serviceProviderFor (ServiceNoConfig.class));
145
        assertSame (ProviderNoConfig.class, su.serviceProviderFor (ProviderNoConfig.class));
146
    }
147
148
    public void testServiceProviderForInterface () {
149
        try {
150
            Class provider = su.serviceProviderFor (ServiceNoProvider.class);
151
            fail ("successfully returned interface");
152
        } catch (BuildException e) {
153
            // Expected Exception caught
154
        } catch (Throwable t) {
155
            fail (t.getMessage ());
156
        }
157
    }
158
159
    public void testServiceProviderForMissingConstructor () {
160
        try {
161
            Class provider = su.serviceProviderFor (NoDefaultConstructorProvider.class);
162
            fail ("successfully returned class lacking default constructor");
163
        } catch (BuildException e) {
164
            // Expected Exception caught
165
        } catch (Throwable t) {
166
            fail (t.getMessage ());
167
        }
168
    }
169
170
    public void testServiceProviderForFirstValidProvider () {
171
        try {
172
            Class provider = su.serviceProviderFor (AbstractService.class);
173
            assertSame (provider, ValidProvider.class);
174
        } catch (Throwable t) {
175
            fail (t.getMessage ());
176
        }
177
    }
178
179
    public void testServiceProviderForSecondValidProvider () {
180
        try {
181
            ServiceUtils.Constraints c = new ServiceUtils.Constraints () {
182
                    public void satisfiedBy (Class provider) throws Exception {
183
                        if (provider == ValidProvider.class) {
184
                            throw new Exception ("Found class blocked by explicit constraints.");
185
                        }
186
                    }
187
                };
188
189
            Class provider = su.serviceProviderFor (AbstractService.class, c);
190
            assertSame (provider, ValidProvider2.class);
191
        } catch (Throwable t) {
192
            fail (t.getMessage ());
193
        }
194
    }
195
196
    public void testServiceProviderForAbstractService() {
197
        try {
198
            ServiceUtils.Constraints c = new ServiceUtils.Constraints () {
199
                    public void satisfiedBy (Class provider) throws Exception {
200
                        if (provider != AbstractService.class) {
201
                            throw new Exception ("Found class blocked by explicit constraints.");
202
                        }
203
                    }
204
                };
205
206
            Class provider = su.serviceProviderFor (AbstractService.class, c);
207
            fail ("Found abstract service provider.");
208
        } catch (BuildException e) {
209
            // Expected Exception caught
210
        } catch (Throwable t) {
211
            fail (t.getMessage ());
212
        }
213
    }
214
215
    public static class NoDefaultConstructorProvider {
216
        public NoDefaultConstructorProvider (int i) { // No default constructor
217
        }
218
    }
219
220
    public static class PrivateDefaultConstructorProvider {
221
        private PrivateDefaultConstructorProvider () {
222
        }
223
    }
224
225
    public static class ValidProvider  extends ServiceUtilsTest.AbstractService {
226
        public ValidProvider () {} // Explicit default constructor
227
    }
228
229
    public static class ValidProvider2 extends ServiceUtilsTest.AbstractService {
230
        // Implicit default constructor
231
    }
232
233
    public static class ProviderNoConfig implements ServiceNoConfig {
234
    };
235
}
236

Return to bug 32142