| /* |
| * Copyright (c) 2018, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // 02/17/2018-2.7.2 Lukas Jungmann |
| // - 531305: Canonical model generator fails to run on JDK9 |
| package org.eclipse.persistence.jpa.test.modelgen; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.net.URI; |
| import java.net.URL; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.StandardOpenOption; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import jakarta.annotation.Generated; |
| import jakarta.persistence.Entity; |
| import javax.tools.Diagnostic; |
| import javax.tools.DiagnosticCollector; |
| import javax.tools.JavaCompiler; |
| import javax.tools.JavaCompiler.CompilationTask; |
| import javax.tools.JavaFileObject; |
| import javax.tools.SimpleJavaFileObject; |
| import javax.tools.StandardJavaFileManager; |
| import javax.tools.StandardLocation; |
| import javax.tools.ToolProvider; |
| |
| import org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor; |
| import org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProperties; |
| import org.junit.Assert; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| public class TestProcessor { |
| |
| @BeforeClass |
| public static void prepare() throws IOException { |
| File testRoot = new File(System.getProperty("run.dir")); |
| if (testRoot.exists() && testRoot.isDirectory()) { |
| for (File testDir: testRoot.listFiles()) { |
| delete(testDir); |
| } |
| } |
| } |
| |
| @Test |
| public void testProc() throws Exception { |
| TestFO entity = new TestFO("org.Sample", |
| "package org; import jakarta.persistence.Entity; @Entity public class Sample { public Sample() {} public int getX() {return 1;} interface A {}}"); |
| TestFO nonSC = new TestFO("some.IF", |
| "package some; public class IF { public IF() {}}"); |
| TestFO nonAnn = new TestFO("custom.Ann", |
| "package custom; public @interface Ann { }"); |
| TestFO nonExt = new TestFO("external.Cls", |
| "package external; public class Cls { public Cls(){}}"); |
| TestFO nonEntity = new TestFO("org.NotE", |
| "package org; import jakarta.persistence.Entity; public class NotE extends some.IF { public NotE() {} @custom.Ann public external.Cls getW() {return new Object();}}"); |
| TestFO generated8 = new TestFO("org.Gen8", |
| "package org; import jakarta.annotation.Generated; @Generated(\"com.example.Generator\") public class Gen8 { public Gen8() {} public int getY() {return 42;}}"); |
| TestFO generated9 = new TestFO("org.Gen9", |
| "package org; @javax.annotation.processing.Generated(\"com.example.Generator\") public class Gen9 { public Gen9() {} public int getZ() {return 9*42;}}"); |
| |
| Result result = runProject("testProc", |
| getJavacOptions("-Aeclipselink.logging.level.processor=OFF"), |
| Arrays.asList(entity, nonSC, nonAnn, nonExt, nonEntity, generated8, generated9)); |
| |
| File outputFile = new File(result.srcOut, "org/Sample_.java"); |
| Assert.assertTrue("Model file not generated", outputFile.exists()); |
| Assert.assertTrue(Files.lines(outputFile.toPath()).anyMatch(s -> s.contains("@StaticMetamodel(Sample.class)"))); |
| } |
| |
| @Test |
| public void testGenerateComment() throws Exception { |
| TestFO entity = new TestFO("org.Sample", |
| "package org; import jakarta.persistence.Entity; @Entity public class Sample { public Sample() {} public int getX() {return 1;} interface A {}}"); |
| |
| Result result = runProject("testGenerateComment", |
| getJavacOptions("-A" + CanonicalModelProperties.CANONICAL_MODEL_GENERATE_COMMENTS + "=false", |
| "-Aeclipselink.logging.level.processor=OFF"), |
| Arrays.asList(entity)); |
| |
| File outputFile = new File(result.srcOut, "org/Sample_.java"); |
| Assert.assertTrue("Model file not generated", outputFile.exists()); |
| Assert.assertTrue(Files.lines(outputFile.toPath()).noneMatch(s -> s.contains("comments="))); |
| Assert.assertTrue("Compilation failed", result.success); |
| } |
| |
| @Test |
| public void testGenerate() throws Exception { |
| TestFO entity = new TestFO("org.Sample", |
| "package org; import jakarta.persistence.Entity; @Entity public class Sample { public Sample() {} public int getX() {return 1;} interface A {}}"); |
| |
| Result result = runProject("testGenerate", |
| getJavacOptions("-A" + CanonicalModelProperties.CANONICAL_MODEL_GENERATE_GENERATED + "=false", |
| "-Aeclipselink.logging.level.processor=OFF"), |
| Arrays.asList(entity)); |
| |
| File outputFile = new File(result.srcOut, "org/Sample_.java"); |
| Assert.assertTrue("Model file not generated", outputFile.exists()); |
| Assert.assertTrue(Files.lines(outputFile.toPath()).noneMatch(s -> s.contains("Generated"))); |
| Assert.assertTrue("Compilation failed", result.success); |
| } |
| |
| @Test |
| public void testTypeUse() throws Exception { |
| TestFO entity = new TestFO("org.Ent", |
| "package org; @jakarta.persistence.Entity public class Ent { @org.ann.NotNull private byte[] bytes;}"); |
| TestFO ann = new TestFO("org.ann.NotNull", |
| "package org.ann; @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public @interface NotNull {}"); |
| |
| Result result = runProject("testTypeUse", |
| getJavacOptions("-Aeclipselink.logging.level.processor=OFF"), |
| Arrays.asList(entity, ann)); |
| |
| File outputFile = new File(result.srcOut, "org/Ent_.java"); |
| Assert.assertTrue("Model file not generated", outputFile.exists()); |
| Assert.assertTrue(Files.lines(outputFile.toPath()).noneMatch(s -> s.contains("NotNull"))); |
| Assert.assertTrue("Compilation failed", result.success); |
| } |
| |
| @Test |
| public void testProcessorLoggingOffFromCmdLine() throws Exception { |
| verifyLogging("testProcessorLoggingOffFromCmdLine", PXML, false, |
| "-Aeclipselink.logging.level.processor=OFF"); |
| } |
| |
| @Test |
| public void testGlobalLoggingOffFromCmdLine() throws Exception { |
| verifyLogging("testGlobalLoggingOffFromCmdLine", PXML, false, |
| "-Aeclipselink.logging.level=OFF"); |
| } |
| |
| @Test |
| public void testProcessorLoggingOffFromPU() throws Exception { |
| final String pu = buildPU("testProcessorLoggingOffFromPU", |
| new Property("eclipselink.logging.level.processor", "OFF")); |
| // Turning logging off from PU can't remove messages logged before PU properties are processed. |
| verifyLogging("testProcessorLoggingOffFromPU", pu, true); |
| } |
| |
| @Test |
| public void testGlobalLoggingOffFromPU() throws Exception { |
| final String pu = buildPU("testGlobalLoggingOffFromPU", |
| new Property("eclipselink.logging.level", "OFF")); |
| // Turning logging off from PU can't remove messages logged before PU properties are processed. |
| verifyLogging("testGlobalLoggingOffFromPU", pu, true); |
| } |
| |
| @Test |
| public void testProcessorLoggingFinestFromCmdLine() throws Exception { |
| verifyLogging("testProcessorLoggingFinestFromCmdLine", PXML, true, |
| "-Aeclipselink.logging.level.processor=FINEST"); |
| } |
| |
| @Test |
| public void testGlobalLoggingFinestFromCmdLine() throws Exception { |
| verifyLogging("testGlobalLoggingFinestFromCmdLine", PXML, true, |
| "-Aeclipselink.logging.level=FINEST"); |
| } |
| |
| @Test |
| public void testProcessorLoggingFinestFromPU() throws Exception { |
| final String pu = buildPU("testProcessorLoggingFinestFromPU", |
| new Property("eclipselink.logging.level.processor", "FINEST")); |
| verifyLogging("testProcessorLoggingFinestFromPU", pu, true); |
| } |
| |
| @Test |
| public void testGlobalLoggingFinestFromPU() throws Exception { |
| final String pu = buildPU("testGlobalLoggingFinestFromPU", |
| new Property("eclipselink.logging.level", "FINEST")); |
| verifyLogging("testGlobalLoggingFinestFromPU", pu, true); |
| } |
| |
| private List<String> getJavacOptions(String... opts) { |
| List<String> result = new ArrayList<>(); |
| String systemOpts = System.getProperty("test.junit.jvm.modules"); |
| if (systemOpts != null && systemOpts.contains("--add-modules")) { |
| result.addAll(Arrays.asList(System.getProperty("test.junit.jvm.modules").split(" "))); |
| } |
| result.add("-proc:only"); |
| result.add("-Aeclipselink.canonicalmodel.use_static_factory=false"); |
| result.addAll(Arrays.asList(opts)); |
| System.out.println("OPTIONS: " + result); |
| return result; |
| } |
| /** |
| * Verify logging output suppression |
| * @param testName name of the test |
| * @param pu persistence unit {@code String} |
| * @param whether there should be logging messages in the output or not |
| * @param options compiler options |
| * @throws Exception |
| */ |
| private void verifyLogging(final String testName, final String pu, final boolean haveMsgs, final String... options) throws Exception { |
| File runDir = new File(System.getProperty("run.dir"), testName); |
| File srcOut = new File(runDir, "src"); |
| srcOut.mkdirs(); |
| File cpDir = new File(runDir, "cp"); |
| cpDir.mkdirs(); |
| File pxml = new File(cpDir, "META-INF/persistence.xml"); |
| pxml.getParentFile().mkdirs(); |
| try (BufferedWriter writer = Files.newBufferedWriter(pxml.toPath(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { |
| writer.write(pu, 0, pu.length()); |
| } catch (IOException x) { |
| throw x; |
| } |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
| DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); |
| |
| StandardJavaFileManager sfm = compiler.getStandardFileManager(diagnostics, null, null); |
| URL apiUrl = Entity.class.getProtectionDomain().getCodeSource().getLocation(); |
| URL generatedUrl = Generated.class.getProtectionDomain().getCodeSource().getLocation(); |
| sfm.setLocation(StandardLocation.CLASS_PATH, Arrays.asList(new File(apiUrl.getFile()), new File(generatedUrl.getFile()), cpDir)); |
| sfm.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singleton(srcOut)); |
| sfm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(cpDir)); |
| |
| TestFO entity = new TestFO("org.Sample", |
| "package org; import jakarta.persistence.Entity; @Entity public class Sample { public Sample() {} public int getX() {return 1;}}"); |
| |
| CompilationTask task = compiler.getTask( |
| new PrintWriter(System.out), sfm, diagnostics, getJavacOptions(options), null, |
| Arrays.asList(entity)); |
| CanonicalModelProcessor modelProcessor = new CanonicalModelProcessor(); |
| task.setProcessors(Collections.singleton(modelProcessor)); |
| task.call(); |
| |
| for ( Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) { |
| System.out.println(diagnostic); |
| } |
| if (haveMsgs) { |
| Assert.assertFalse("Log messages should be generated", diagnostics.getDiagnostics().isEmpty()); |
| } else { |
| Assert.assertTrue("No log message should be generated", diagnostics.getDiagnostics().isEmpty()); |
| } |
| } |
| |
| private Result runProject(String name, List<String> options, List<JavaFileObject> sources) throws Exception { |
| File runDir = new File(System.getProperty("run.dir"), name); |
| File srcOut = new File(runDir, "src"); |
| srcOut.mkdirs(); |
| File cpDir = new File(runDir, "cp"); |
| cpDir.mkdirs(); |
| File pxml = new File(cpDir, "META-INF/persistence.xml"); |
| pxml.getParentFile().mkdirs(); |
| try (BufferedWriter writer = Files.newBufferedWriter(pxml.toPath(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { |
| writer.write(PXML, 0, PXML.length()); |
| } catch (IOException x) { |
| throw x; |
| } |
| File oxml = new File(cpDir, "META-INF/orm.xml"); |
| try (BufferedWriter writer = Files.newBufferedWriter(oxml.toPath(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { |
| writer.write(OXML, 0, OXML.length()); |
| } catch (IOException x) { |
| throw x; |
| } |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
| DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); |
| |
| StandardJavaFileManager sfm = compiler.getStandardFileManager(diagnostics, null, null); |
| URL apiUrl = Entity.class.getProtectionDomain().getCodeSource().getLocation(); |
| URL generatedUrl = Generated.class.getProtectionDomain().getCodeSource().getLocation(); |
| sfm.setLocation(StandardLocation.CLASS_PATH, Arrays.asList(new File(apiUrl.getFile()), new File(generatedUrl.getFile()), cpDir)); |
| sfm.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singleton(srcOut)); |
| sfm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(cpDir)); |
| |
| CompilationTask task = compiler.getTask(new PrintWriter(System.out), sfm, diagnostics, |
| options, null, sources); |
| CanonicalModelProcessor modelProcessor = new CanonicalModelProcessor(); |
| task.setProcessors(Collections.singleton(modelProcessor)); |
| boolean result = task.call(); |
| |
| for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) { |
| System.out.println(diagnostic); |
| String msg = diagnostic.getMessage(null); |
| Assert.assertFalse(msg, |
| msg.contains("The following options were not recognized by any processor:")); |
| } |
| return new Result(srcOut, cpDir, result); |
| } |
| |
| private static class TestFO extends SimpleJavaFileObject { |
| private final String text; |
| |
| public TestFO(String name, String code) { |
| super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), |
| Kind.SOURCE); |
| this.text = code; |
| } |
| |
| @Override |
| public CharSequence getCharContent(boolean ignoreEncodingErrors) { |
| return text; |
| } |
| } |
| |
| private static class Result { |
| File srcOut, binOut; |
| boolean success; |
| Result(File srcOut, File binOut, boolean success) { |
| this.srcOut = srcOut; |
| this.binOut = binOut; |
| this.success = success; |
| } |
| } |
| |
| private static final String PXML = "<persistence xmlns=\"https://jakarta.ee/xml/ns/persistence\"\n" + |
| " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + |
| " xsi:schemaLocation=\"https://jakarta.ee/xml/ns/persistence\n" + |
| " https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd\"\n" + |
| " version=\"3.0\">\n" + |
| " <persistence-unit name=\"sample-pu\" transaction-type=\"RESOURCE_LOCAL\">\n" + |
| " <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>\n" + |
| " <exclude-unlisted-classes>false</exclude-unlisted-classes>\n" + |
| " <properties>\n" + |
| " </properties>\n" + |
| " </persistence-unit>\n" + |
| "</persistence>"; |
| |
| private static final String OXML = "<entity-mappings xmlns=\"https://jakarta.ee/xml/ns/persistence/orm\"\n" + |
| " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + |
| " xsi:schemaLocation=\"https://jakarta.ee/xml/ns/persistence/orm https://jakarta.ee/xml/ns/persistence/orm/orm_3_0.xsd\"\n" + |
| " version=\"3.0\">" + |
| "</entity-mappings>"; |
| |
| private static final String PXML_LOG_BEG = |
| "<persistence xmlns=\"http://xmlns.jcp.org/xml/ns/persistence\"\n" + |
| " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + |
| " xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/persistence\n" + |
| " http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd\"\n" + |
| " version=\"2.2\">\n" + |
| " <persistence-unit name=\""; |
| |
| private static final String PXML_LOG_MID = |
| "\" transaction-type=\"RESOURCE_LOCAL\">\n" + |
| " <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>\n" + |
| " <exclude-unlisted-classes>false</exclude-unlisted-classes>\n" + |
| " <properties>\n"; |
| |
| private static final String PXML_LOG_END = |
| " </properties>\n" + |
| " </persistence-unit>\n" + |
| "</persistence>"; |
| |
| /** |
| * Simple property holding class. |
| */ |
| private static final class Property { |
| private final String name; |
| private final String value; |
| private Property(final String name, final String value) { |
| this.name = name; |
| this.value = value; |
| } |
| } |
| |
| /** |
| * Builds persistence unit with properties. |
| * @param name persistence unit name |
| * @param properties properties to be added to persistence unit |
| * @return persistence unit with specified properties |
| */ |
| private static String buildPU(final String name, Property ... properties) { |
| int len = PXML_LOG_BEG.length() + PXML_LOG_MID.length() + PXML_LOG_END.length() + name.length(); |
| for (Property property : properties) { |
| len += property.name.length() + property.value.length() + 43; |
| } |
| final StringBuilder sb = new StringBuilder(len); |
| sb.append(PXML_LOG_BEG); |
| sb.append(name); |
| sb.append(PXML_LOG_MID); |
| for (Property property : properties) { |
| sb.append(" <property name=\""); |
| sb.append(property.name); |
| sb.append("\" value=\""); |
| sb.append(property.value); |
| sb.append("\"/>\n"); |
| } |
| sb.append(PXML_LOG_END); |
| return sb.toString(); |
| } |
| |
| private static void delete(File dir) throws IOException { |
| Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { |
| Files.delete(file); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { |
| Files.delete(dir); |
| return FileVisitResult.CONTINUE; |
| } |
| }); |
| } |
| |
| } |