/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later
 * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
 */

/**
 * Support for modules that contain Java code
 */

buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath 'de.thetaphi:forbiddenapis:3.2'
	}
}

import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis
import org.apache.tools.ant.filters.ReplaceTokens

apply from: rootProject.file( 'gradle/module.gradle' )
apply from: rootProject.file( 'gradle/libraries.gradle' )
apply from: rootProject.file( 'gradle/databases.gradle' )

apply plugin: 'org.hibernate.orm.database-service'

apply plugin: 'java-library'
apply plugin: 'org.hibernate.orm.database-service'

apply plugin: 'checkstyle'
apply plugin: 'build-dashboard'
apply plugin: 'project-report'


// Attempt to leverage JetBrain's Gradle extension to automatically define
// `copyResourcesToIntelliJOutFolder` as a "build trigger" on import.
//
// However, see https://github.com/JetBrains/gradle-idea-ext-plugin/issues/8

apply plugin: 'org.jetbrains.gradle.plugin.idea-ext'

ext {
	java9ModuleNameBase = project.name.startsWith( 'hibernate-' ) ? name.drop( 'hibernate-'.length() ): name
	java9ModuleName = "org.hibernate.orm.$project.java9ModuleNameBase".replace('-','.')
	forbiddenAPITargetJDKCompatibility = '11'
}

if ( !project.description ) {
	project.description = "The Hibernate ORM $project.name module"
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Configurations and Dependencies


dependencies {
	implementation libs.logging

	compileOnly libs.loggingAnnotations

	// JUnit dependencies made up of:
	// 		* JUnit 5
	// 		* the Jupiter engine which runs JUnit 5 based tests
	//		* the "vintage" engine - which runs JUnit 3 and 4 based tests
	testImplementation testLibs.junit5Api
	testImplementation testLibs.junit5Engine
	testImplementation testLibs.junit5Params
	testImplementation testLibs.junit4
	testImplementation testLibs.junit4Engine
	testImplementation testLibs.assertjCore

	testImplementation testLibs.byteman

	testRuntimeOnly testLibs.log4j2
	testRuntimeOnly libs.byteBuddy

	//Databases
	testRuntimeOnly dbLibs.h2
	testRuntimeOnly dbLibs.derby
	testRuntimeOnly dbLibs.hsqldb
	testRuntimeOnly dbLibs.postgresql
	testRuntimeOnly dbLibs.mssql
	testRuntimeOnly dbLibs.informix
	testRuntimeOnly dbLibs.cockroachdb
	testRuntimeOnly dbLibs.oracle
	testRuntimeOnly dbLibs.oracleXml
	testRuntimeOnly dbLibs.oracleXmlParser
	testRuntimeOnly dbLibs.sybase

	// Since both the DB2 driver and HANA have a package "net.jpountz" we have to add dependencies conditionally
	// This is due to the "no split-packages" requirement of Java 9+

	if ( db.startsWith( 'db2' ) ) {
		testRuntimeOnly dbLibs.db2
	}
	else if ( db.startsWith( 'hana' ) ) {
		testRuntimeOnly dbLibs.hana
	}
	else if ( db.startsWith( 'mysql' ) || db.startsWith( 'tidb' ) ) {
		testRuntimeOnly dbLibs.mysql
	}
	else if ( db.startsWith( 'mariadb' ) ) {
		testRuntimeOnly dbLibs.mariadb
	}

	annotationProcessor libs.loggingProcessor
	annotationProcessor libs.logging
	annotationProcessor libs.loggingAnnotations


	constraints {
		implementation('org.apache.logging.log4j:log4j-core') {
			version {
				strictly('[2.17.1, 3[')
				prefer('2.17.1')
			}
			because('CVE-2021-44228, CVE-2021-45046, CVE-2021-45105, CVE-2021-44832: Log4j vulnerable to remote code execution and other critical security vulnerabilities')
		}
	}
}

configurations {
	javadocSources {
		canBeConsumed = true
		canBeResolved = false
		visible = false
		description = 'Configuration for accessing the sources that should be included in the javadoc for the project'
	}
}

artifacts {
	sourceSets.main.allJava.srcDirs.each { srcDir ->
		javadocSources srcDir
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Compilation

tasks.withType( JavaCompile ) {
	options.encoding = 'UTF-8'
	options.warnings false
//	javaCompileTask.options.compilerArgs += [
//			"-nowarn",
//			"-encoding", "UTF-8"
//	]
}

if ( !gradle.ext.javaToolchainEnabled ) {
	tasks.compileJava.configure {
		sourceCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.main.release )
		targetCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.main.release )
	}
	tasks.compileTestJava.configure {
		sourceCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.test.release )
		targetCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.test.release )
	}
}
else {
	// Configure generated bytecode
	// "sourceCompatibility" is not supported with toolchains. We have to work around that limitation.
	tasks.compileJava.configure {
		options.release = gradle.ext.javaVersions.main.release.asInt()
		// Needs add-opens because of https://github.com/gradle/gradle/issues/15538
		options.forkOptions.jvmArgs.addAll( ["--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"] )
	}
	tasks.compileTestJava.configure {
		options.release = gradle.ext.javaVersions.test.release.asInt()
		// Needs add-opens because of https://github.com/gradle/gradle/issues/15538
		options.forkOptions.jvmArgs.addAll( ["--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"] )
	}

	// Configure version of Java tools
	java {
		toolchain {
			languageVersion = gradle.ext.javaVersions.main.compiler
		}
	}
	tasks.compileTestJava {
		javaCompiler = javaToolchains.compilerFor {
			languageVersion = gradle.ext.javaVersions.test.compiler
		}
	}

	// Configure JVM Options
	// Display version of Java tools
	tasks.withType( JavaCompile ).configureEach {
		options.forkOptions.jvmArgs.addAll( getProperty( 'toolchain.compiler.jvmargs' ).toString().split( ' ' ) )
		doFirst {
			logger.lifecycle "Compiling with '${javaCompiler.get().metadata.installationPath}'"
		}
	}
	tasks.withType( Javadoc ).configureEach {
		options.setJFlags( getProperty( 'toolchain.javadoc.jvmargs' ).toString().split( ' ' ).toList().findAll( { !it.isEmpty() } ) )
		doFirst {
			logger.lifecycle "Generating javadoc with '${javadocTool.get().metadata.installationPath}'"
		}
	}
}


task compile(dependsOn: [compileJava, processResources, compileTestJava, processTestResources] )



// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attach tools JAR to the classpath for byteman tests

final File toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar")
if ( toolsJar.exists() ) {
	dependencies{
		testImplementation files( toolsJar )
	}
}



// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Testing

if ( gradle.ext.javaToolchainEnabled ) {
	tasks.test {
		// Configure version of Java tools
		javaLauncher = javaToolchains.launcherFor {
			languageVersion = gradle.ext.javaVersions.test.launcher
		}

		// Configure JVM Options
		jvmArgs( getProperty( 'toolchain.launcher.jvmargs' ).toString().split( ' ' ) )
		if ( project.hasProperty( 'test.jdk.launcher.args' ) ) {
			jvmArgs( project.getProperty( 'test.jdk.launcher.args' ).toString().split( ' ' ) )
		}

		// Display version of Java tools
		doFirst {
			logger.lifecycle "Testing with '${javaLauncher.get().metadata.installationPath}'"
		}
	}
}
class HeapDumpPathProvider implements CommandLineArgumentProvider {
	@OutputDirectory
	Provider<Directory> path

	@Override
	Iterable<String> asArguments() {
		["-XX:HeapDumpPath=${path.get().asFile.absolutePath}"]
	}
}

tasks.withType( Test.class ).each { test ->
	test.useJUnitPlatform()

	test.usesService( project.gradle.sharedServices.registrations.getByName( 'databaseService' ).service )

	// Byteman needs this property to be set, https://developer.jboss.org/thread/274997
	test.jvmArgs += ["-Djdk.attach.allowAttachSelf=true"]

	test.jvmArgumentProviders.add(
			new HeapDumpPathProvider( path: project.layout.buildDirectory.dir("OOM-dump") )
	)

	test.jvmArgs += [
			'-XX:+HeapDumpOnOutOfMemoryError',
			'-XX:MetaspaceSize=256M'
	]

	test.maxHeapSize = '3G'

	test.systemProperties['hibernate.test.validatefailureexpected'] = true
	test.systemProperties += System.properties.findAll { it.key.startsWith( "hibernate." ) }

	test.enableAssertions = true

	if ( project.name != 'hibernate-testing' ) {
		test.dependsOn ':hibernate-testing:test'
	}
}

sourceSets {
	test {
		resources {
			// add `src/test/java` as a test-resources dir
			configure( srcDir('src/test/java') ) {
				filter {
					include '**/*.properties'
					include '**/*.xml'
				}
			}
			configure( srcDir('src/test/resources') ) {
				filter {
					include '*.properties'
					include '*.xml'
					include '**/*.properties'
					include '**/*.xml'
				}
			}
		}
	}
}


processTestResources {
	duplicatesStrategy DuplicatesStrategy.INCLUDE
	inputs.property( "db", db )
	doLast {
		copy {
			from( sourceSets.test.java.srcDirs ) {
				include '**/*.properties'
				include '**/*.xml'
			}
			into sourceSets.test.java.classesDirectory
		}
		copy {
			from file( 'src/test/resources' )
			into file( "${buildDir}/resources/test" )
			exclude 'src/test/resources/hibernate.properties'
		}
		copy {
			from file( 'src/test/resources/hibernate.properties' )
			into file( "${buildDir}/resources/test" )
			filter( ReplaceTokens, tokens: dbBundle[db] )
		}
	}
}

// Keep system properties in sync with gradle.properties!
test {
	systemProperty 'user.language', 'en'
	systemProperty 'user.country', 'US'
	systemProperty 'user.timezone', 'UTC'
	systemProperty 'file.encoding', 'UTF-8'
	// Needed for AdoptOpenJDK on alpine? The problem is similar to this: https://github.com/mockito/mockito/issues/978
	jvmArgs '-XX:+StartAttachListener'
}

// Enable the experimental features of ByteBuddy with JDK 19+
test {
	// We need to test the *launcher* version,
	// because some tests will use Mockito (and thus Bytebuddy) to mock/spy
	// classes that are part of the JDK,
	// and those classes always have bytecode matching the version of the launcher.
	// So for example, when using a JDK19 launcher and compiling tests with --release 17,
	// Bytebuddy will still encounter classes with Java 19 bytecode.
	if ( gradle.ext.javaVersions.test.launcher.asInt() >= 19 ) {
		logger.warn( "The version of Java bytecode that will be tested is not supported by Bytebuddy by default. " +
				 " Setting 'net.bytebuddy.experimental=true'." )
		systemProperty 'net.bytebuddy.experimental', true
	}
}

test {
	if ( project.findProperty( 'log-test-progress' )?.toString()?.toBoolean() ) {
		// Log a statement for each test.
		// Used in the Travis build so that Travis doesn't end up panicking because there's no output for a long time.
		testLogging {
			events "passed", "skipped", "failed"
		}
	}
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Jar

jar {
	manifest {
		attributes(
				// Basic JAR manifest attributes
				'Specification-Title': project.name,
				'Specification-Version': project.version,
				'Specification-Vendor': 'Hibernate.org',
				'Implementation-Title': project.name,
				'Implementation-Version': project.version,
				'Implementation-Vendor': 'Hibernate.org',
				'Implementation-Vendor-Id': 'org.hibernate',
				'Implementation-Url': 'https://hibernate.org/orm',

				// Java 9 module name
				'Automatic-Module-Name': project.java9ModuleName,

				// Hibernate-specific JAR manifest attributes
				'Hibernate-VersionFamily': project.ormVersion.family,
				'Hibernate-JpaVersion': project.jpaVersion.name,

				// BND Plugin instructions (for OSGi):
				'-reproducible': true,
				'-noextraheaders': true,
				'Bundle-Name': project.name,
				'Bundle-SymbolicName': project.java9ModuleName,
				'Bundle-Vendor': 'Hibernate.org',
				'Bundle-DocURL': "https://www.hibernate.org/orm/${project.ormVersion.family}",
				// This is overridden in some sub-projects
				'Import-Package': [
						// Temporarily support JTA 1.1 -- Karaf and other frameworks still
						// use it.  Without this, the plugin generates [1.2,2).
						'javax.transaction;version="[1.1,2)"',
						// Also import every package referenced in the code
						// (note that '*' is resolved at build time to a list of packages)
						'*'
				].join( ',' ),
				'-exportcontents': "*;version=${project.version}"
		)
	}
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sources

task sourcesJar(type: Jar) {
	from project.sourceSets.main.allSource
	manifest {
		attributes(
				// Basic JAR manifest attributes
				'Specification-Title': project.name,
				'Specification-Version': project.version,
				'Specification-Vendor': 'Hibernate.org',
				'Implementation-Title': project.name,
				'Implementation-Version': project.version,
				'Implementation-Vendor': 'Hibernate.org',
				'Implementation-Vendor-Id': 'org.hibernate',
				'Implementation-Url': 'https://hibernate.org/orm',

				// Hibernate-specific JAR manifest attributes
				'Hibernate-VersionFamily': project.ormVersion.family,
				'Hibernate-JpaVersion': project.jpaVersion.name
		)
	}
	archiveClassifier.set( 'sources' )
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Javadoc

apply from: rootProject.file( 'gradle/javadoc.gradle' )

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IDE


/*
 The latest versions of IntelliJ copy the test resources into out/test/resources and
 use those for its test classpath.  Unfortunately, this occurs before the placeholder
 in the test config file are substituted with the testing values.

 This behaviour prevents the execution of the hibernate tests from inside the IDE.

 A solution is to enable the 'After Build' Execution of the copyResourcesToIntelliJOutFolder task
 from the 'Gradle project' IntelliJ tool window ( The task can be found under hibernate-orm > Task > other)
 */
task copyResourcesToIntelliJOutFolder(type: Task, dependsOn: project.tasks.processTestResources) {
	doLast {
		copy {
			from "$buildDir/resources/test"
			into 'out/test/resources'
		}
	}
}



/*
 Use this task to set the current DB in a given module.

 > gradlew sDB -Pdb=mysql

 Afterward, you can run any test from the IDE against that particular DB.
 */
task setDataBase dependsOn( processTestResources, copyResourcesToIntelliJOutFolder ) {
	println( "Setting current database to ${db}" )
}

tasks.copyResourcesToIntelliJOutFolder.mustRunAfter processTestResources

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Report configs

checkstyle {
	it.sourceSets = [ project.sourceSets.main ]
	configFile = rootProject.file( 'shared/config/checkstyle/checkstyle.xml' )
	showViolations = false
}

// exclude generated java sources - by explicitly setting the base source dir
tasks.checkstyleMain.source = 'src/main/java'
// because cfg package is a mess mainly from annotation stuff
tasks.checkstyleMain.exclude '**/org/hibernate/cfg/**'
tasks.checkstyleMain.exclude '**/org/hibernate/cfg/*'

// define a second checkstyle task for checking non-fatal violations
task nonFatalCheckstyle(type:Checkstyle) {
	source = project.sourceSets.main.java
	classpath = project.configurations.checkstyle
	showViolations = false
	configFile = rootProject.file( 'shared/config/checkstyle/checkstyle-non-fatal.xml' )
}


task forbiddenApisSystemOut(type: CheckForbiddenApis, dependsOn: compileJava) {
	bundledSignatures += 'jdk-system-out'
	suppressAnnotations += ['org.hibernate.internal.build.AllowSysOut', 'org.hibernate.internal.build.AllowPrintStacktrace']
}

task forbiddenApisUnsafe(type: CheckForbiddenApis, dependsOn: compileJava) {
	bundledSignatures += "jdk-unsafe-${gradle.ext.baselineJavaVersion}".toString()

	// unfortunately we currently have many uses of default Locale implicitly (~370) which need to be fixed
	// before we can fully enabled this check
	//
	// No idea how findbugs was missing these b4
	ignoreFailures = true
}

task forbiddenApisNonPortable(type: CheckForbiddenApis, dependsOn: compileJava) {
	bundledSignatures += 'jdk-non-portable'
}

task forbiddenApis {
	description 'Grouping task for all defined forbidden-apis tasks'
}

project.tasks.withType( CheckForbiddenApis ) {
	outputs.upToDateWhen { true }

	classesDirs = project.sourceSets.main.output
	classpath = configurations.runtimeClasspath + configurations.compileClasspath
	targetCompatibility = project.forbiddenAPITargetJDKCompatibility

	// This slows down the checks a little, but is necessary to avoid the gradle daemon holding on
	// to class definitions loaded previously - even possibly in a previous build.
	disableClassloadingCache = true

	tasks.forbiddenApis.finalizedBy it
}

project.tasks.check.finalizedBy tasks.forbiddenApis
