#!/usr/bin/perl
# 
# $Header: opatchauto/oms/JdkUpgradeValidations.pl /st_opatchauto_pt-em13.5mos/2 2024/07/17 16:10:19 smkumbar Exp $
#
# JdkUpgradeValidations.pl
# 
# Copyright (c) 2023, 2024, Oracle and/or its affiliates. 
#
#    NAME
#      JdkUpgradeValidations.pl - <one-line expansion of the name>
#
#    DESCRIPTION
#      <short description of component this file declares/defines>
#
#    NOTES
#      <other useful comments, qualifications, etc.>
#
#    MODIFIED   (MM/DD/YY)
#    smkumbar    05/24/23 - Validations for JDK upgrade
#    smkumbar    05/24/23 - Creation
# 
use Cwd ;
use File::Spec; 
use File::Path;
use File::Copy;
use File::stat;
use File::Basename;
use Fcntl qw(:flock);
use JSON::PP;
use IO::Compress::Zip;
use File::Spec::Functions qw(splitpath);
use IO::File;
use IO::Uncompress::Unzip qw($UnzipError);
use File::Path qw(mkpath);
local $| =1;


sub formatJdk{
	my ($inputJavaVer, $fh, $datestring) = @_;
	if (index($inputJavaVer, "-hp-ux") != -1) {
	print $fh "$datestring :: The version has hp-ux....removing the characters..\n";
	$inputJavaVer =~ s/-hp-ux//g;
	$inputJavaVer =~ s/^\s+|\s+$//g;  # trim whitespace
	print $fh "$datestring :: Java version after removing characters: $inputJavaVer\n";
}

#If the version start as 1. like 1.7.1, 1.8.0_351
if ($inputJavaVer =~ /^1\./) {
	if ($inputJavaVer =~ /_/) {
		my ($inputVersion, $release) = split('_', $inputJavaVer, 2);
		$inputVersion =~ s/^\s+|\s+$//g;
		$release =~ s/^\s+|\s+$//g;
		if ($inputVersion && $release) {
			return ($inputVersion, $release, 1);
		} else {
			print $fh "$datestring :: Java version for version '8 or lower' is not as per standards\n";
			die "Java version for version '8 or lower' is not as per standards\n";
		}
	} else {
		my @arr1 = split('\.', $inputJavaVer);
		
		if (@arr1 == 3 || @arr1 == 4) {
			if (@arr1 == 3) {
				my ($inputVersion, $release) = ($inputJavaVer, 0);
				$inputVersion =~ s/^\s+|\s+$//g;
				print $fh "$datestring :: Input version is $inputVersion\n";
		        print $fh "$datestring :: Input release is $release\n";	
				
				return ($inputVersion, $release, 1);
			} else {
				my ($inputVersion, $release) = (join('.', @arr1[0..2]), $arr1[3]);
				$inputVersion =~ s/^\s+|\s+$//g;
				$release =~ s/^\s+|\s+$//g;
				return ($inputVersion, $release, 1);
			}
		} else {
			print $fh "$datestring :: Java version with dot separated is not as per standards\n";
			die "Java version with dot separated is not as per standards\n";
		}
	}
	} else { #If the version starts as 9.0.1
		my @arr1 = split('\.', $inputJavaVer);
		
		if (@arr1 == 3) {
			my ($inputVersion, $release) = (join('.', @arr1[0..1]), $arr1[2]);
			$inputVersion =~ s/^\s+|\s+$//g;
			$release =~ s/^\s+|\s+$//g;
			return ($inputVersion, $release, 1);
		} else {
			print $fh "$datestring :: There was an error while getting the java version\n";
			die "There was an error while getting the java version\n";
		}
	}
}

sub validateJDKVersion{
	my ($inputVendor, $inputPlatform, $inputVersion, $release, $jsonFileLoc, $analyzeFlag, $fh, $datestring) = @_;	
	print $fh "$datestring :: Validating the jdk version\n";
	my $vendorFound = 0;
	my $json_text = do {
	open(my $json_fh, "<:encoding(UTF-8)", $jsonFileLoc)
		or die("Can't open \"$jsonFileLoc\": $!\n");
	local $/;
	<$json_fh>
	};
	my $json = JSON::PP->new;
	my $obj = $json->decode($json_text);

	if ($obj->{"12.2.1.4"}) {
		my $arr = $obj->{"12.2.1.4"};
		
		foreach my $element (@$arr) {
			if (ref($element) eq "HASH") {
				my $vendor = $element;
				
				if ($vendor->{$inputVendor}) {
					print $fh "$datestring :: Found the vendor $inputVendor\n";
					$vendorFound = 1;
					my $arr1 = $vendor->{$inputVendor};
					print $fh "$datestring :: Checking for platform.....\n";
					checkForPlatform($arr1, $inputPlatform, $inputVersion, $release, $analyzeFlag, $fh, $datestring);
					last;
				}
			}
		}
		
		if (!$vendorFound) {
			print $fh "$datestring :: Java Vendor not found in the spb_jdk_info.json....Exiting\n";
			die "Java Vendor is not as per SPB standards....Exiting\n";
		}
	} else {
		print $fh "$datestring :: Fmw version not found in the spb_jdk_info.json....Exiting\n";
		die "Fmw version is not as per SPB standards....Exiting\n";
	}
	return 1;
}

sub checkForPlatform {
    my ($arr1, $inputPlatform, $inputVersion, $release, $analyzeFlag, $fh, $datestring) = @_;
    my $platformFound = 0;
    
    foreach my $element1 (@$arr1) {
        if (ref($element1) eq "HASH") {
            my $platform = $element1;
            
            if ($platform->{$inputPlatform}) {
                print $fh "$datestring :: Platform found $inputPlatform\n";
                $platformFound = 1;
                my $arr2 = $platform->{$inputPlatform};
                print $fh "$datestring :: Checking for version.....\n";
                checkForJavaVersion($arr2, $inputVersion, $release, $analyzeFlag, $fh, $datestring);
                last;
            }
        }
    }
    
    if (!$platformFound) {
		print $fh "$datestring :: Platform $inputPlatform not found in the spb_jdk_info.json....Exiting\n";
        die "Platform $inputPlatform is not as per SPB standards....Exiting\n";
    }
}

sub checkForJavaVersion {
    my ($arr2, $inputVersion, $release, $analyzeFlag, $fh, $datestring) = @_;
    
    foreach my $element2 (@$arr2) {
        if (ref($element2) eq "HASH") {
            my $version = $element2;
            my $subVersion = $version->{$inputVersion};
            
            if ($subVersion) {
                print $fh "$datestring :: Version found $inputVersion\n";
                checkForRelease($subVersion, $release, $analyzeFlag, $fh, $datestring);
                last;
            } else {#We are erroring out if the version is not found in the json file
				print $fh "$datestring :: ERROR :: Passed Java Version $inputVersion not found in the spb_jdk_info.json\n";
				print "\n----------------------------------------------------------------\n";
				print  "ERROR :: Passed Java Version $inputVersion is not as per SPB standards\n";
				print "----------------------------------------------------------------\n";
				if(!$analyzeFlag){
                	die "Version $inputVersion is not as per SPB standards....Exiting\n";
				}
            }
        }
    }
}

sub checkForRelease {
    my ($subVersion, $release, $analyzeFlag, $fh, $datestring) = @_;

    #If the min version is not present in the json file, then its patch metadata error
    if (!$subVersion->{"min_version"}) {
		print $fh "$datestring :: [FAILED] The json input file is missing min_version field for the jdk release $inputVersion\n";
        die "[FAILED] SPB patch content for verifying the jdk version is not as per standards\n";
    }
    
    my $minVer = $subVersion->{"min_version"};    
   
    #If passed version is min than min version in json file, then error out 
    if ($release < $minVer) {
		print $fh "$datestring :: JDK version $release passed is lower than the JDK version $minVer expected by the SPB patch. Pass the recommended JDK version\n";
		print "\n----------------------------------------------------------------\n";
		print "ERROR :: JDK version $release passed is lower than the JDK version $minVer expected by the SPB patch. Pass the recommended JDK version\n";
		print "----------------------------------------------------------------\n";
		if(!$analyzeFlag){
            die "Invalid JDK update request\n";
		}        
    }
	else{
		print $fh "$datestring :: JDK version $release passed is as per the recommended JDK version expected by the SPB patch.\n";
		print "\nJDK version $release passed is as per the recommended JDK version expected by the SPB patch.\n"
	}
	#my $recoFlag = 0;
    #my $recoVer = 0;

	#if ($subVersion->{"reco_version"}) {
    #    $recoVer = $subVersion->{"reco_version"};
    #    $recoFlag = 1;
    #}
    
    #if ($release >= $minVer) {
    #    if ($recoFlag) {
    #        if ($release < $recoVer) {
	#			print $fh "$datestring :: The recommended sub-version is $recoVer but the input sub-version is $release\n";
    #            print "\nWARNING :: The recommended sub-version is $recoVer but the input sub-version is $release\n";
    #        } else {
	#			print $fh "$datestring :: The input sub-version $release is as per recommended sub-version $recoVer.\n";
    #            print "\nSUCCESS :: The input sub-version $release is as per recommended sub-version $recoVer\n";
    #        }
    #    } else {
	#		print $fh "$datestring :: The input sub-version $release as per min sub-version $minVer\n";
    #        print "\nSUCCESS :: The input sub-version $release as per min sub-version $minVer\n";
    #    }
    #}
}

sub compareVersions {
    my ($version1, $version2) = @_;

    $version1 =~ s/^\s+|\s+$//g;
    $version2 =~ s/^\s+|\s+$//g;
    my @levels1 = split(/\./, $version1);
    my @levels2 = split(/\./, $version2);

    my $length = scalar(@levels1) > scalar(@levels2) ? scalar(@levels1) : scalar(@levels2);
    for (my $i = 0; $i < $length; $i++) {
        my $v1 = $i < scalar(@levels1) ? int($levels1[$i]) : 0;
        my $v2 = $i < scalar(@levels2) ? int($levels2[$i]) : 0;
        my $compare = $v1 <=> $v2;
        if ($compare != 0) {
            return $compare;
        }
    }
    return 0;
}

sub unzipToDirectory {
my ($zipFilename, $destinationDir) = @_;

unless ($zipFilename and $destinationDir) {
	$! = "zipFilename and destinationDir must be supplied";
	return;
}

my $zipFile = IO::Uncompress::Unzip->new($zipFilename);

unless ($zipFile) {
	$! = "Could not open $zipFilename as zipFile";
	return;
}


# Status should always be 1 for each entry in the zip...
while (my $status = $zipFile->nextStream()) {

	my $zipEntryHeader = $zipFile->getHeaderInfo();
	my $pathAndFilename = $zipEntryHeader->{Name};

	# Returns from splitPath are volume, path, and filename...
	# We don't care about path (and it shouldn't be there, anyway)
    	my (undef, $path, $filename) = splitpath($pathAndFilename);

	# Make sure we have a filename...skip directories...
	$filename or next;

	my $fullOutputDir = $destinationDir . "/" . $path;
	mkpath($fullOutputDir);

	my $unzippedFilename = $fullOutputDir . $filename;

	my $outputFh;
	unless (open($outputFh, ">", $unzippedFilename)) {
		$! = "Could not open $unzippedFilename for writing";
		return;
	}

	binmode($outputFh);

	my $buffer;
    	while (($status = $zipFile->read($buffer))) {
		print $outputFh $buffer;
    	}

    	close($outputFh);

	#!print STDERR "Output $unzippedFilename " . -s $unzippedFilename, " bytes\n\n";
}
	return 1;
}
$platform = $^O;
$platform=lc($platform);

my $oracleHome = @ARGV[0];
my $jdkLoc = @ARGV[1];
my $platformName = @ARGV[2];
my $jsonFileLoc = @ARGV[3];
my $analyzeFlag = @ARGV[4];

$datestring = localtime();
my $logTimeStamp = localtime();
$logTimeStamp =~ s/ /_/g;
$logTimeStamp =~ s/:/_/g;

my $jdkUpgradeLog = File::Spec->catfile($oracleHome,"cfgtoollogs","omspatcher","SPB_PATCH","jdk_update_$logTimeStamp.log");

my $javaExec = File::Spec->catfile($oracleHome,"oracle_common","jdk","bin","java");
my $javaLoc = File::Spec->catfile($oracleHome,"oracle_common");
my $fh;
open ($fh, '>', $jdkUpgradeLog);
print "\nJDK update log location :: $jdkUpgradeLog\n";

#Running below command to get Env Java vendor and version
my $out = `$javaExec -XshowSettings:properties -version 2>\&1`;
my $vendor;
my $envVer;

foreach my $data (split /[\r\n]+/, $out){
	if(index($data, "java.vm.vendor") != -1){
		my @fields = split('=',$data);
		$vendor = $fields[1];
		$vendor=~ s/^\s+|\s+$//g;
		print $fh "$datestring :: The installed java vendor is $vendor\n";
	}
	if (index($data, "java.version") != -1){
	        my @fields = split('=',$data);
        	$envVer = $fields[1];
	        $envVer=~ s/^\s+|\s+$//g;
        	print $fh "$datestring :: The Env version is $envVer\n";
    }
}
my $newJdk;
my $newVer;
my $newJavaLoc;
my $newJava;
my $tempDir;
my $copyCmd;
my $moveCmd;
if ($platform =~ /win/){
	#Getting zip file from JDK loc
	my @files = glob "$jdkLoc/*.zip";
	my $zipFile = $files[0];
	print $fh "$datestring :: The zip file is $zipFile\n";
	if ($zipFile eq ""){
                die "Could not get the zip file\n"
        }
    my $newJdk = "";
    my $destDir = "$oracleHome\\.omspatcher_storage\\temp";
   #Unzipping the content to jdkloc/temp folder for the validations	
    my $success = unzipToDirectory($zipFile, $destDir);
    if($success != 1){
		print $fh "$datestring :: Could not unzip the jdk\n";
        die "Could not unzip the jdk\n";
    }

	#Reading the unzipped dir to get the new jdk folder created after unzipping
	opendir ( DIR, $destDir ) || die "Error in opening dir $destDir\n";
    while( ($filename = readdir(DIR))) {
        if ($filename eq '.' or $filename eq '..') {
            next;
        }
        $newJdk = $filename;
    }
    closedir(DIR);
    $newJavaLoc = "$destDir\\$newJdk\\bin\\java.exe";
	$tempDir = "$destDir";
	my $winJdk = "$tempDir\\$newJdk";
	
	my $newJdkLoc = "$javaLoc\\$newJdk";
	my $newTempjdk = "$javaLoc\\jdk_temp";

	#Preparing move and copy commands if jdk upgrade is needed
	$copyCmd = "xcopy /isvy $winJdk $newJdkLoc";
	$moveCmd = "move $newJdkLoc $newTempjdk";
}
else{
	#Checking for either tar or gz file in the JDK loc
	my @files = glob "$jdkLoc/*.tar";
	my $tarFile = $files[0];	
	if ($tarFile eq ""){
   		my @files1 = glob "$jdkLoc/*.gz";
		$tarFile = $files1[0];
	}
	print $fh "$datestring :: The tar file is $tarFile\n";
	if ($tarFile eq ""){
		die "Could not get the tar/gz file\n"
	}

	#Getting the folder name which will be created after untarring
	$newJdk = `tar tf $tarFile | head -1 | cut -f1 -d"/"`;
	chomp($newJdk);
	print $fh "$datestring :: The new jdk file is $newJdk\n";
	my $storageLoc = "$oracleHome/.omspatcher_storage";
	if (!(-e $storageLoc)){
		my $mkStorageDir = `mkdir $oracleHome/.omspatcher_storage`;
	}
	my $mkTempDir = `mkdir $oracleHome/.omspatcher_storage/temp`;
	my $destDir = "$oracleHome/.omspatcher_storage/temp";
	my $tar_cmd = `cd $destDir;tar xvf $tarFile`;
    $newJavaLoc = "$destDir/$newJdk/bin/java";
	$tempDir = "$destDir/$newJdk";
	my $newJdkLoc = "$javaLoc/$newJdk";
	my $newTempjdk = "$javaLoc/jdk_temp";

	#Preparing move and copy commands if jdk upgrade is needed
	$copyCmd = "cp -r $tempDir $javaLoc";	
	$moveCmd = "mv $newJdkLoc $newTempjdk";
}

#Running below command to get new java version
$newJava = `$newJavaLoc -XshowSettings:properties -version 2>\&1`;
foreach my $data1 (split /[\r\n]+/, $newJava){
    if (index($data1, "java.version") != -1){
        my @fields1 = split('=',$data1);
        $newVer = $fields1[1];
        $newVer=~ s/^\s+|\s+$//g;
        print $fh "$datestring :: The new version is $newVer\n";
		last;
    }
}

#Formatting the JDK versions to a standard format so that we can compare
my ($inputVersion, $release, $retCode) = formatJdk($newVer, $fh, $datestring);
print $fh "$datestring :: The input version after formatting $inputVersion\n";
print $fh "$datestring :: The input release after formatting $release\n";
print $fh "$datestring :: return code is $retCode\n";

if ($retCode != 1) {
	die "Could not segregate main version and release\n";
}

my ($envVersion, $envRelease, $retCode1) = formatJdk($envVer, $fh, $datestring);
print $fh "$datestring :: The Env version after formatting $inputVersion\n";
print $fh "$datestring :: The Env release after formatting $release\n";
print $fh "$datestring :: return code is $retCode1\n";

if ($retCode1 != 1) {
	die "Could not segregate main version and release\n";
}

#Preparing formats like 1.7.8.251 to compare env and new jdk versions
my $finalInpVer = "$inputVersion.$release";
my $finalEnvVer = "$envVersion.$envRelease";
print $fh "$datestring :: Final Input version is $finalInpVer and Final Env version is $finalEnvVer\n";
print "\nSPB JDK version :: $finalInpVer,  Env JDK version :: $finalEnvVer\n";

my $compareVal = compareVersions($finalInpVer, $finalEnvVer);
print $fh "$datestring :: The compare val is $compareVal\n";

#If the passed jdk version is less than or equal to env version, then exit
if($compareVal <= 0){
	print $fh "$datestring :: The Env version is greater or equal to JDK included in patch.....JDK will not be updated\n";
	print "\nThe Env JDK version is as per the supported JDK version expected by the SPB patch :: JDK will not be updated\n";
}
else{
	print $fh "$datestring :: The Env version is lower as compared to JDK included in patch :: JDK will be updated\n";
	print "\nThe Env version is lower as compared to JDK included in patch :: JDK will be updated\n";
	#print $fh "$datestring :: The Json file location is $jsonFileLoc\n";
	#Checking if the passed JDK is compatible with the JDK Env has and satifies min and rec versions
	#my $retVal = validateJDKVersion($vendor, $platformName, $inputVersion, $release, $jsonFileLoc, $analyzeFlag, $fh, $datestring);
	#print $fh "$datestring :: return val is $retVal\n";
	#if ($retVal != 1) {
	#	die "Could not validate the jdk version with json\n";
	#}


	#All the validations are passed, so we are good to upgrade
	if ($analyzeFlag){
		print $fh "$datestring :: Ananlyze flag is set to true\n";
	}
	else{
		print "Updating the JDK now...\n";
		print $fh "$datestring :: Updating the JDK now...\n";
		print $fh "$datestring :: Copy command is $copyCmd\n";
		print $fh "$datestring :: Move command is $moveCmd\n";
		my $cpCmd = `$copyCmd`;
		if ($? != 0) {
			print $fh "$datestring :: Could not copy the contents";
			die "Could not copy the contents..$!\n";
		}
		my $mvCmd = `$moveCmd`;
		if ($? != 0) {
			print $fh "$datestring :: Could not move the contents";
			die "Could not copy the contents..$!\n";
		}
		my $emInstanceFile = File::Spec->catfile($oracleHome,"sysman","config","emInstanceMapping.properties");

		#The above file confirms that the oms home is configured home and we can stop oms
		if (-e $emInstanceFile) {
			print $fh "$datestring :: emInstanceMapping.properties Exists..Stopping the oms..\n";
			my $emctlLoc;
			my $stopCmd;
			print "Stopping the OMS...\n";
			if ($platform =~ /win/){
				$emctlLoc = File::Spec->catfile($oracleHome,"bin","emctl.bat");
				print $fh "$datestring :: $emctlLoc stop oms -all\n";
				$stopCmd = `$emctlLoc stop oms -all`;
				#$startFlag=1;
			}
			else{
				#Not including -all as we need admin server to be up when the actual apply of spb starts
				$emctlLoc = File::Spec->catfile($oracleHome,"bin","emctl");
				print $fh "$datestring :: $emctlLoc stop oms\n";
				$stopCmd = `$emctlLoc stop oms`;
			}
			
			if ($? != 0) {
				print $fh "$datestring :: Could not stop the oms..";
				die "Could not stop the oms..$!\n";
			}
			print "$stopCmd\n";
			print $fh "$datestring :: $stopCmd\n";		
		}

		#Moving and copying the unzipped/untarred folders to upgrade the JDK
		my $epoch = time();
		my $oldJavaLoc = File::Spec->catfile($oracleHome,"oracle_common", "jdk");
		my $epochJavaLoc = File::Spec->catfile($oracleHome,"oracle_common", "jdk_$epoch");	
		my $tempJavaLoc = File::Spec->catfile($oracleHome,"oracle_common", "jdk_temp");
		rename($oldJavaLoc, $epochJavaLoc) or die("Error in renaming");
		print $fh "$datestring :: Old java folder renamed to $epochJavaLoc\n";
		print $fh "$datestring :: Back_up Java folder :: $epochJavaLoc\n";
		print "\nBack_up Java folder :: $epochJavaLoc\n";
		rename($tempJavaLoc, $oldJavaLoc) or die("Error in renaming");
		#my $mvJdk = `mv $oldJavaLoc $epochJavaLoc`;
		#my $mvJdk = `mv $tempJavaLoc $oldJavaLoc`;
		print "\nNew Java version is...\n";
		my $newVersionCmd = `$javaExec -version`;
		print $fh "$datestring :: Updated Java Successfully\n";
		print "\nUpdated Java Successfully\n";
	}
	close ($fh);	
}
