1 /* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import javax.naming.Context; 25 import java.io.*; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.nio.file.Paths; 29 import java.util.*; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.function.Function; 34 import java.util.stream.Collectors; 35 import java.util.stream.Stream; 36 37 import static java.lang.String.format; 38 import static java.util.Arrays.asList; 39 import static java.util.Collections.singleton; 40 import static java.util.Collections.singletonMap; 41 42 /* 43 * @test 44 * @bug 8044627 45 * @summary Examines different ways JNDI providers can hook up themselves and 46 * become available. Each case mimics the most straightforward way of 47 * executing scenarios. 48 */ 49 public class InitialContextTest { 50 51 public static void main(String[] args) throws Throwable { 52 unknownInitialContextFactory(); 53 initialContextFactoryInAJar(); 54 initialContextFactoryAsService(); 55 } 56 57 private static void unknownInitialContextFactory() throws Throwable { 58 59 // This is a parameter of this test case, it should work for any value 60 // of it, provided a class with this FQN is not available in a runtime. 61 // So pick any name you like. 62 String factoryClassFqn = 63 "net.java.openjdk.test.UnknownInitialContextFactory"; 64 65 Path tmp = Files.createDirectory(Paths.get("InitialContextTest-1")); 66 67 Path src = templatesHome().resolve("test.template"); 68 Path dst = tmp.resolve("Test.java"); 69 Files.copy(src, dst); 70 71 javac(tmp, dst); 72 73 Path build = Files.createDirectory(tmp.resolve("build")); 74 Files.copy(tmp.resolve("Test.class"), build.resolve("Test.class")); 75 76 Map<String, String> props 77 = singletonMap(Context.INITIAL_CONTEXT_FACTORY, factoryClassFqn); 78 79 Result r = java(props, singleton(build), "Test"); 80 81 if (r.exitValue == 0 || !r.output.startsWith( 82 stackTraceStringForClassNotFound(factoryClassFqn))) { 83 throw new RuntimeException( 84 "Expected a different kind of failure: " + r.output); 85 } 86 } 87 88 private static String stackTraceStringForClassNotFound(String fqn) { 89 return String.format( 90 "Exception in thread \"main\" javax.naming.NoInitialContextException: " 91 + "Cannot instantiate class: %s " 92 + "[Root exception is java.lang.ClassNotFoundException: %s]", 93 fqn, fqn); 94 } 95 96 private static void initialContextFactoryInAJar() throws Throwable { 97 98 String factoryClassFqn = 99 "net.java.openjdk.test.DummyInitialContextFactory"; 100 101 Path tmp = Files.createDirectory(Paths.get("InitialContextTest-2")); 102 103 Path src = templatesHome().resolve("test.template"); 104 Path dst = tmp.resolve("Test.java"); 105 Files.copy(src, dst); 106 107 Path dst1 = createFactoryFrom(templatesHome().resolve("factory.template"), 108 factoryClassFqn, tmp); 109 110 javac(tmp, dst); 111 Path explodedJar = Files.createDirectory(tmp.resolve("exploded-jar")); 112 javac(explodedJar, dst1); 113 jar(tmp.resolve("test.jar"), explodedJar); 114 115 Path build = Files.createDirectory(tmp.resolve("build")); 116 Files.copy(tmp.resolve("Test.class"), build.resolve("Test.class")); 117 Files.copy(tmp.resolve("test.jar"), build.resolve("test.jar")); 118 119 Map<String, String> props 120 = singletonMap(Context.INITIAL_CONTEXT_FACTORY, factoryClassFqn); 121 122 Result r = java(props, asList(build.resolve("test.jar"), build), "Test"); 123 124 if (r.exitValue != 0 || !r.output.isEmpty()) 125 throw new RuntimeException(r.output); 126 } 127 128 129 private static Path createFactoryFrom(Path srcTemplate, 130 String factoryFqn, 131 Path dstFolder) throws IOException { 132 133 String factorySimpleName, packageName; 134 int i = factoryFqn.lastIndexOf('.'); 135 if (i < 0) { 136 packageName = ""; 137 factorySimpleName = factoryFqn; 138 } else { 139 packageName = factoryFqn.substring(0, i); 140 factorySimpleName = factoryFqn.substring(i + 1); 141 } 142 143 Path result = dstFolder.resolve(factorySimpleName + ".java"); 144 File dst = result.toFile(); 145 File src = srcTemplate.toFile(); 146 try (BufferedReader r = new BufferedReader(new FileReader(src)); 147 BufferedWriter w = new BufferedWriter(new FileWriter(dst))) { 148 149 List<String> lines = processTemplate(packageName, factorySimpleName, 150 r.lines()).collect(Collectors.toList()); 151 152 Iterator<String> it = lines.iterator(); 153 if (it.hasNext()) 154 w.write(it.next()); 155 while (it.hasNext()) { 156 w.newLine(); 157 w.write(it.next()); 158 } 159 } 160 return result; 161 } 162 163 private static Stream<String> processTemplate(String packageName, 164 String factorySimpleName, 165 Stream<String> lines) { 166 Function<String, String> pckg; 167 168 if (packageName.isEmpty()) { 169 pckg = s -> s.contains("$package") ? "" : s; 170 } else { 171 pckg = s -> s.replaceAll("\\$package", packageName); 172 } 173 174 Function<String, String> factory 175 = s -> s.replaceAll("\\$factoryName", factorySimpleName); 176 177 return lines.map(pckg).map(factory); 178 } 179 180 private static void initialContextFactoryAsService() throws Throwable { 181 182 String factoryClassFqn = 183 "net.java.openjdk.test.BrokenInitialContextFactory"; 184 185 Path tmp = Files.createDirectory(Paths.get("InitialContextTest-3")); 186 187 Path src = templatesHome().resolve("test.template"); 188 Path dst = tmp.resolve("Test.java"); 189 Files.copy(src, dst); 190 191 Path dst1 = createFactoryFrom(templatesHome().resolve("broken_factory.template"), 192 factoryClassFqn, tmp); 193 194 javac(tmp, dst); 195 196 Path explodedJar = Files.createDirectory(tmp.resolve("exploded-jar")); 197 Path services = Files.createDirectories(explodedJar.resolve("META-INF") 198 .resolve("services")); 199 200 Path s = services.resolve("javax.naming.spi.InitialContextFactory"); 201 FileWriter fw = new FileWriter(s.toFile()); 202 try { 203 fw.write(factoryClassFqn); 204 } finally { 205 fw.close(); 206 } 207 208 javac(explodedJar, dst1); 209 jar(tmp.resolve("test.jar"), explodedJar); 210 211 Path build = Files.createDirectory(tmp.resolve("build")); 212 Files.copy(tmp.resolve("Test.class"), build.resolve("Test.class")); 213 Files.copy(tmp.resolve("test.jar"), build.resolve("test.jar")); 214 215 Map<String, String> props = new HashMap<>(); 216 props.put("java.ext.dirs", build.toString()); 217 props.put(Context.INITIAL_CONTEXT_FACTORY, factoryClassFqn); 218 219 Result r = java(props, singleton(build), "Test"); 220 221 if (r.exitValue == 0 || !verifyOutput(r.output, factoryClassFqn)) 222 throw new RuntimeException(r.output); 223 } 224 225 // IMO, that's the easiest way that gives you a fair amount of confidence in 226 // that j.u.ServiceLoader is loading a factory rather than Class.forName 227 private static boolean verifyOutput(String output, String fqn) { 228 String s1 = String.format( 229 "Exception in thread \"main\" javax.naming.NoInitialContextException: " 230 + "Cannot load initial context factory '%s' " 231 + "[Root exception is java.util.ServiceConfigurationError: " 232 + "javax.naming.spi.InitialContextFactory: " 233 + "Provider %s could not be instantiated]", fqn, fqn); 234 235 String s2 = String.format("Caused by: java.util.ServiceConfigurationError: " 236 + "javax.naming.spi.InitialContextFactory: " 237 + "Provider %s could not be instantiated", fqn); 238 239 String s3 = "Caused by: java.lang.RuntimeException: " 240 + "This is a broken factory. It is supposed to throw this exception."; 241 242 return output.startsWith(s1) && output.contains(s2) 243 && output.contains(s1); 244 } 245 246 private static void jar(Path jarName, Path jarRoot) { 247 String jar = getJDKTool("jar"); 248 ProcessBuilder p = new ProcessBuilder(jar, "cf", jarName.toString(), 249 "-C", jarRoot.toString(), "."); 250 quickFail(run(p)); 251 } 252 253 private static void javac(Path compilationOutput, Path... sourceFiles) { 254 String javac = getJDKTool("javac"); 255 List<String> commands = new ArrayList<>(); 256 commands.addAll(asList(javac, "-d", compilationOutput.toString())); 257 List<Path> paths = asList(sourceFiles); 258 commands.addAll(paths.stream() 259 .map(Path::toString) 260 .collect(Collectors.toList())); 261 quickFail(run(new ProcessBuilder(commands))); 262 } 263 264 private static void quickFail(Result r) { 265 if (r.exitValue != 0) 266 throw new RuntimeException(r.output); 267 } 268 269 private static Result java(Map<String, String> properties, 270 Collection<Path> classpath, 271 String classname) { 272 273 String java = getJDKTool("java"); 274 275 List<String> commands = new ArrayList<>(); 276 commands.add(java); 277 commands.addAll(properties.entrySet() 278 .stream() 279 .map(e -> "-D" + e.getKey() + "=" + e.getValue()) 280 .collect(Collectors.toList())); 281 282 String cp = classpath.stream() 283 .map(Path::toString) 284 .collect(Collectors.joining(File.pathSeparator)); 285 commands.add("-cp"); 286 commands.add(cp); 287 commands.add(classname); 288 289 return run(new ProcessBuilder(commands)); 290 } 291 292 private static Result run(ProcessBuilder b) { 293 Process p = null; 294 try { 295 p = b.start(); 296 } catch (IOException e) { 297 throw new RuntimeException( 298 format("Couldn't start process '%s'", b.command()), e); 299 } 300 301 String output; 302 try { 303 output = toString(p.getInputStream(), p.getErrorStream()); 304 } catch (IOException e) { 305 throw new RuntimeException( 306 format("Couldn't read process output '%s'", b.command()), e); 307 } 308 309 try { 310 p.waitFor(); 311 } catch (InterruptedException e) { 312 throw new RuntimeException( 313 format("Process hasn't finished '%s'", b.command()), e); 314 } 315 316 return new Result(p.exitValue(), output); 317 } 318 319 private static String getJDKTool(String name) { 320 String testJdk = System.getProperty("test.jdk"); 321 if (testJdk == null) 322 throw new RuntimeException("Please provide test.jdk property at a startup"); 323 return testJdk + File.separator + "bin" + File.separator + name; 324 } 325 326 private static Path templatesHome() { 327 String testSrc = System.getProperty("test.src"); 328 if (testSrc == null) 329 throw new RuntimeException("Please provide test.src property at a startup"); 330 return Paths.get(testSrc); 331 } 332 333 private static String toString(InputStream... src) throws IOException { 334 StringWriter dst = new StringWriter(); 335 Reader concatenated = 336 new InputStreamReader( 337 new SequenceInputStream( 338 Collections.enumeration(asList(src)))); 339 copy(concatenated, dst); 340 return dst.toString(); 341 } 342 343 private static void copy(Reader src, Writer dst) throws IOException { 344 int len; 345 char[] buf = new char[1024]; 346 try { 347 while ((len = src.read(buf)) != -1) 348 dst.write(buf, 0, len); 349 } finally { 350 try { 351 src.close(); 352 } catch (IOException ignored1) { 353 } finally { 354 try { 355 dst.close(); 356 } catch (IOException ignored2) { 357 } 358 } 359 } 360 } 361 362 private static class Result { 363 364 final int exitValue; 365 final String output; 366 367 private Result(int exitValue, String output) { 368 this.exitValue = exitValue; 369 this.output = output; 370 } 371 } 372 }