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 24import javax.naming.Context; 25import java.io.*; 26import java.nio.file.Files; 27import java.nio.file.Path; 28import java.nio.file.Paths; 29import java.util.*; 30import java.util.Collection; 31import java.util.Collections; 32import java.util.List; 33import java.util.function.Function; 34import java.util.stream.Collectors; 35import java.util.stream.Stream; 36 37import static java.lang.String.format; 38import static java.util.Arrays.asList; 39import static java.util.Collections.singleton; 40import 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 */ 49public 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 Path build = Files.createDirectory(tmp.resolve("build")); 72 73 javac(build, dst); 74 75 Map<String, String> props 76 = singletonMap(Context.INITIAL_CONTEXT_FACTORY, factoryClassFqn); 77 78 Result r = java(props, singleton(build), "Test"); 79 80 if (r.exitValue == 0 || !r.output.startsWith( 81 stackTraceStringForClassNotFound(factoryClassFqn))) { 82 throw new RuntimeException( 83 "Expected a different kind of failure: " + r.output); 84 } 85 } 86 87 private static String stackTraceStringForClassNotFound(String fqn) { 88 return String.format( 89 "Exception in thread \"main\" javax.naming.NoInitialContextException: " 90 + "Cannot instantiate class: %s " 91 + "[Root exception is java.lang.ClassNotFoundException: %s]", 92 fqn, fqn); 93 } 94 95 private static void initialContextFactoryInAJar() throws Throwable { 96 97 String factoryClassFqn = 98 "net.java.openjdk.test.DummyInitialContextFactory"; 99 100 Path tmp = Files.createDirectory(Paths.get("InitialContextTest-2")); 101 102 Path src = templatesHome().resolve("test.template"); 103 Path dst = tmp.resolve("Test.java"); 104 Files.copy(src, dst); 105 106 Path dst1 = createFactoryFrom(templatesHome().resolve("factory.template"), 107 factoryClassFqn, tmp); 108 109 Path build = Files.createDirectory(tmp.resolve("build")); 110 111 javac(build, dst); 112 Path explodedJar = Files.createDirectory(tmp.resolve("exploded-jar")); 113 javac(explodedJar, dst1); 114 jar(tmp.resolve("test.jar"), explodedJar); 115 116 Files.copy(tmp.resolve("test.jar"), build.resolve("test.jar")); 117 118 Map<String, String> props 119 = singletonMap(Context.INITIAL_CONTEXT_FACTORY, factoryClassFqn); 120 121 Result r = java(props, asList(build.resolve("test.jar"), build), "Test"); 122 123 if (r.exitValue != 0 || !r.output.isEmpty()) 124 throw new RuntimeException(r.output); 125 } 126 127 128 private static Path createFactoryFrom(Path srcTemplate, 129 String factoryFqn, 130 Path dstFolder) throws IOException { 131 132 String factorySimpleName, packageName; 133 int i = factoryFqn.lastIndexOf('.'); 134 if (i < 0) { 135 packageName = ""; 136 factorySimpleName = factoryFqn; 137 } else { 138 packageName = factoryFqn.substring(0, i); 139 factorySimpleName = factoryFqn.substring(i + 1); 140 } 141 142 Path result = dstFolder.resolve(factorySimpleName + ".java"); 143 File dst = result.toFile(); 144 File src = srcTemplate.toFile(); 145 try (BufferedReader r = new BufferedReader(new FileReader(src)); 146 BufferedWriter w = new BufferedWriter(new FileWriter(dst))) { 147 148 List<String> lines = processTemplate(packageName, factorySimpleName, 149 r.lines()).collect(Collectors.toList()); 150 151 Iterator<String> it = lines.iterator(); 152 if (it.hasNext()) 153 w.write(it.next()); 154 while (it.hasNext()) { 155 w.newLine(); 156 w.write(it.next()); 157 } 158 } 159 return result; 160 } 161 162 private static Stream<String> processTemplate(String packageName, 163 String factorySimpleName, 164 Stream<String> lines) { 165 Function<String, String> pckg; 166 167 if (packageName.isEmpty()) { 168 pckg = s -> s.contains("$package") ? "" : s; 169 } else { 170 pckg = s -> s.replaceAll("\\$package", packageName); 171 } 172 173 Function<String, String> factory 174 = s -> s.replaceAll("\\$factoryName", factorySimpleName); 175 176 return lines.map(pckg).map(factory); 177 } 178 179 private static void initialContextFactoryAsService() throws Throwable { 180 181 String factoryClassFqn = 182 "net.java.openjdk.test.BrokenInitialContextFactory"; 183 184 Path tmp = Files.createDirectory(Paths.get("InitialContextTest-3")); 185 186 Path src = templatesHome().resolve("test.template"); 187 Path dst = tmp.resolve("Test.java"); 188 Files.copy(src, dst); 189 190 Path dst1 = createFactoryFrom(templatesHome().resolve("broken_factory.template"), 191 factoryClassFqn, tmp); 192 193 Path build = Files.createDirectory(tmp.resolve("build")); 194 195 javac(build, dst); 196 197 Path explodedJar = Files.createDirectory(tmp.resolve("exploded-jar")); 198 Path services = Files.createDirectories(explodedJar.resolve("META-INF") 199 .resolve("services")); 200 201 Path s = services.resolve("javax.naming.spi.InitialContextFactory"); 202 FileWriter fw = new FileWriter(s.toFile()); 203 try { 204 fw.write(factoryClassFqn); 205 } finally { 206 fw.close(); 207 } 208 209 javac(explodedJar, dst1); 210 jar(tmp.resolve("test.jar"), explodedJar); 211 212 Files.copy(tmp.resolve("test.jar"), build.resolve("test.jar")); 213 214 Map<String, String> props 215 = singletonMap(Context.INITIAL_CONTEXT_FACTORY, factoryClassFqn); 216 217 Result r = java(props, asList(build.resolve("test.jar"), build), "Test"); 218 219 if (r.exitValue == 0 || !verifyOutput(r.output, factoryClassFqn)) 220 throw new RuntimeException(r.output); 221 } 222 223 // IMO, that's the easiest way that gives you a fair amount of confidence in 224 // that j.u.ServiceLoader is loading a factory rather than Class.forName 225 private static boolean verifyOutput(String output, String fqn) { 226 String s1 = String.format( 227 "Exception in thread \"main\" javax.naming.NoInitialContextException: " 228 + "Cannot load initial context factory '%s' " 229 + "[Root exception is java.util.ServiceConfigurationError: " 230 + "javax.naming.spi.InitialContextFactory: " 231 + "Provider %s could not be instantiated]", fqn, fqn); 232 233 String s2 = String.format("Caused by: java.util.ServiceConfigurationError: " 234 + "javax.naming.spi.InitialContextFactory: " 235 + "Provider %s could not be instantiated", fqn); 236 237 String s3 = "Caused by: java.lang.RuntimeException: " 238 + "This is a broken factory. It is supposed to throw this exception."; 239 240 return output.startsWith(s1) && output.contains(s2) 241 && output.contains(s1); 242 } 243 244 private static void jar(Path jarName, Path jarRoot) { 245 String jar = getJDKTool("jar"); 246 ProcessBuilder p = new ProcessBuilder(jar, "cf", jarName.toString(), 247 "-C", jarRoot.toString(), "."); 248 quickFail(run(p)); 249 } 250 251 private static void javac(Path compilationOutput, Path... sourceFiles) { 252 String javac = getJDKTool("javac"); 253 List<String> commands = new ArrayList<>(); 254 commands.addAll(asList(javac, "-d", compilationOutput.toString())); 255 List<Path> paths = asList(sourceFiles); 256 commands.addAll(paths.stream() 257 .map(Path::toString) 258 .collect(Collectors.toList())); 259 quickFail(run(new ProcessBuilder(commands))); 260 } 261 262 private static void quickFail(Result r) { 263 if (r.exitValue != 0) 264 throw new RuntimeException(r.output); 265 } 266 267 private static Result java(Map<String, String> properties, 268 Collection<Path> classpath, 269 String classname) { 270 271 String java = getJDKTool("java"); 272 273 List<String> commands = new ArrayList<>(); 274 commands.add(java); 275 commands.addAll(properties.entrySet() 276 .stream() 277 .map(e -> "-D" + e.getKey() + "=" + e.getValue()) 278 .collect(Collectors.toList())); 279 280 String cp = classpath.stream() 281 .map(Path::toString) 282 .collect(Collectors.joining(File.pathSeparator)); 283 commands.add("-cp"); 284 commands.add(cp); 285 commands.add(classname); 286 287 return run(new ProcessBuilder(commands)); 288 } 289 290 private static Result run(ProcessBuilder b) { 291 Process p = null; 292 try { 293 p = b.start(); 294 } catch (IOException e) { 295 throw new RuntimeException( 296 format("Couldn't start process '%s'", b.command()), e); 297 } 298 299 String output; 300 try { 301 output = toString(p.getInputStream(), p.getErrorStream()); 302 } catch (IOException e) { 303 throw new RuntimeException( 304 format("Couldn't read process output '%s'", b.command()), e); 305 } 306 307 try { 308 p.waitFor(); 309 } catch (InterruptedException e) { 310 throw new RuntimeException( 311 format("Process hasn't finished '%s'", b.command()), e); 312 } 313 314 return new Result(p.exitValue(), output); 315 } 316 317 private static String getJDKTool(String name) { 318 String testJdk = System.getProperty("test.jdk"); 319 if (testJdk == null) 320 throw new RuntimeException("Please provide test.jdk property at a startup"); 321 return testJdk + File.separator + "bin" + File.separator + name; 322 } 323 324 private static Path templatesHome() { 325 String testSrc = System.getProperty("test.src"); 326 if (testSrc == null) 327 throw new RuntimeException("Please provide test.src property at a startup"); 328 return Paths.get(testSrc); 329 } 330 331 private static String toString(InputStream... src) throws IOException { 332 StringWriter dst = new StringWriter(); 333 Reader concatenated = 334 new InputStreamReader( 335 new SequenceInputStream( 336 Collections.enumeration(asList(src)))); 337 copy(concatenated, dst); 338 return dst.toString(); 339 } 340 341 private static void copy(Reader src, Writer dst) throws IOException { 342 int len; 343 char[] buf = new char[1024]; 344 try { 345 while ((len = src.read(buf)) != -1) 346 dst.write(buf, 0, len); 347 } finally { 348 try { 349 src.close(); 350 } catch (IOException ignored1) { 351 } finally { 352 try { 353 dst.close(); 354 } catch (IOException ignored2) { 355 } 356 } 357 } 358 } 359 360 private static class Result { 361 362 final int exitValue; 363 final String output; 364 365 private Result(int exitValue, String output) { 366 this.exitValue = exitValue; 367 this.output = output; 368 } 369 } 370} 371