#!/usr/bin/env bash
#
# shellcheck disable=SC2317
#
# Wrapper script for starting a JRE within OpenNMS.
# DJ Gregor
# Copyright (c) Daniel J. Gregor, Jr.
#    parts Copyright (c) The OpenNMS Group
# Tuesday, August 31, 2004
#
# Parts based on parseMib.sh, a Java MIB parser by David Hustace
# Copyright (c) The OpenNMS Group
# Friday, February 13, 2004
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# For more information contact:
#      OpenNMS Licensing       <license@opennms.org>
#      http://www.opennms.org/
#      http://www.opennms.com/
#
# put an '@' here so we don't break maven substition

INCOMING_ARGS=("$@")

# shellcheck disable=SC2016
opennms_home="${OPENNMS_HOME}"
if [ -d '${install.dir}' ]; then
    # shellcheck disable=SC2153
    opennms_home='${install.dir}'
fi
OPENNMS_HOME="${opennms_home}"

# The user to run as
[ -z "$RUNAS" ] && RUNAS=opennms

if [ -e "${opennms_home}/bin/_lib.sh" ]; then
    # shellcheck disable=SC1090,SC1091
    . "${opennms_home}/bin/_lib.sh"

    # Load opennms.conf, if it exists, to override $RUNAS
    if [ -f "${opennms_home}/etc/opennms.conf" ]; then
        # shellcheck disable=SC1090
        __onms_read_conf "${opennms_home}/etc/opennms.conf"
    fi

    RUNUSER="$(command -v runuser 2>/dev/null || which runuser 2>/dev/null || :)"
    myuser="$(id -u -n)"
    if [ "$myuser" != "$RUNAS" ]; then
        if [ "$myuser" = "root" ] && [ -x "$RUNUSER" ]; then
            _cmd=("$RUNUSER" "-u" "$RUNAS" -- "$0" "${INCOMING_ARGS[@]}");
            exec "${_cmd[@]}"
        fi
        echo "ERROR: you should run this script as ${RUNAS}, not '${myuser}'." >&2
        # shellcheck disable=SC2145
        echo "       To correct this, try 'sudo -u ${RUNAS} $0 $@'" >&2
        echo "       If you wish for OpenNMS to run as ${myuser} instead," >&2
        echo "       create or edit ${OPENNMS_HOME}/etc/opennms.conf and set 'RUNAS=${myuser}'." >&2
        exit 4 # According to LSB: 4 - user had insufficient privileges
    fi
fi

minimum_version="11.0"
maximum_version="17.9999"

searchdirs=("/usr/lib/jvm" "/usr/java" "/System/Library/Java/JavaVirtualMachines" "/Library/Java/JavaVirtualMachines" "/Library/Java/Home" "/usr" "/opt")

dryrun=0

java_conf="$opennms_home/etc/java.conf"
basename="$(basename "$0")"

die(){
    echo "${basename}: $*" >&2
    exit 1
}
warn(){
    if [ "$quiet" -ne 1 ]; then
        echo "${basename}: $*" >&2
    fi
}
tell(){
    if [ "$quiet" -ne 1 ]; then
        echo "${basename}: $*" >&3
    fi
}


#
# Function: printHelp()
# Give the user some basic help.
#
printHelp() {
    echo "usage: $basename [-f] [-n] [-q]"
    echo "       {-s | -S <JRE path> | -c | -r <java options> | -p | -h}"
    echo ""
    echo "Exactly one of the following options is required:"
    echo ""
    echo "  -r <java options>"
    echo "      Exec the configured Java Runtime Environment (JRE) with"
    echo "      with <java options>."
    echo ""
    echo "  -s  Search for a suitable JRE and configure it, if found."
    echo ""
    echo "  -p  Print the currently-configured Java version."
    echo ""
    echo "  -S <JRE path>"
    echo "      Store the JRE at <JRE path> as the configured JRE."
    echo ""
    echo "  -c  Check that the configured JRE is appropriate."
    echo ""
    echo "  -h  Print this help information."
    echo ""
    echo "Optional options:"
    echo ""
    echo "  -a  Add a directory to the search path."
    echo ""
    echo "  -j  java.conf location.  Sets the java.conf directory.  If not"
    echo "      configured, it will be written to \$OPENNMS_HOME/etc/."
    echo ""
    echo "  -f  Force mode.  Ignores the JRE version check.  This option"
    echo "      only makes sense with the -r and -S options."
    echo ""
    echo "  -n  Dry run mode.  Don't change the configured JRE (-s or -S"
    echo "      options) or execute a JRE (-r option)."
    echo ""
    echo "  -q  Quiet mode.  Be fairly quiet."
    echo ""
    echo "The \"-s\" option chooses a JRE based on these factors:"
    echo ""
    echo "  1) If the JAVA_HOME environment variable is set and points at"
    echo "     an appropriate JRE, it \$JAVA_HOME/bin/java will be used."
    echo ""
    echo "  2) If \"which java\" returns an appropriate JRE, it will be used."
    echo ""
    echo "  3) Lastly, a few directories are searched to find an"
    echo "     appropriate JRE:" "${searchdirs[@]}"
    echo "     If you have passed -a, that directory will be searched as well."
    echo ""
    echo "If none of the above options work or if they don't find the JRE"
    echo "you want OpenNMS to use, use \"-S <JRE>\" to specify the JRE you"
    echo "want OpenNMS to use."
}

#
# Function: parseVersionLine(version)
# This function parses and returns the version string from
# the "version" line of a `java -version` output.
parseVersionLine() {
    echo "$1" | sed -e 's/^.* version \"//;s/\".*$//' -e 's,^1\.,,' -e 's,_,.,g' | cut -d. -f1-3
}

#
# Function: printVersion()
# Requires: varible $JP (java path)
# This function prints the (parsed) version of the current
# JDK defined in $JP.
printVersion() {
    ver="$("$JP" -version 2>&1 | grep ' version \"')"
    parseVersionLine "$ver"
}

printInstallJava() {
    warn ""
    warn "You can install a JVM by downloading one from Oracle, or by running something like"
    warn "'apt-get install openjdk-17-jdk' or 'yum install java-17-openjdk-devel'."
}

#
# Function: javaCheck()
# Requires: varible $JP (java path)
# This function verifies the version of the java
# command found in the javaPath() and the
# javaFind() functions.
#
javaCheck() {
    if [ -z "$OPENJDK_MINIMUM_BUILD" ]; then
        OPENJDK_MINIMUM_BUILD=14
    fi

    if [ -x "${opennms_home}/bin/find-java.sh" ]; then
        found="$("${opennms_home}/bin/find-java.sh" "${minimum_version}" "${maximum_version}" "$JP")"
        if [ -n "$found" ] && [ -d "$found" ] && [ -x "$found/bin/java" ]; then
            return 0
        else
            if [ -n "$JP" ]; then
                if [ "$run" -eq 0 ] && [ "$force" -eq 0 ]; then
                    return 1
                else
                    return 0
                fi
            fi
        fi
    else
        warn "'find-java.sh' was not found in \$OPENNMS_HOME/bin.  Falling back to the old check algorithm."
    fi

    ret="$(printVersion)"
    if echo "$ret" | grep -E '^([8-9]|1[0-9])\b' > /dev/null; then
        # Check for acceptable JVM
        if "$JP" -version 2>&1 | grep -E '^(Diablo )?Java\(TM\)' > /dev/null; then
            return 0
        fi
        if "$JP" -version 2>&1 | grep -E 'OpenJDK .* VM' >/dev/null; then
                build="$("$JP" -version 2>&1 | grep -E '^OpenJDK .* VM' | sed -e 's,^.*build ,,' -e 's,\..*$,,')"
                if [ "$build" -ge "$OPENJDK_MINIMUM_BUILD" ]; then
                        return 0
                else
                        warn "OpenNMS is only known to work with OpenJDK build $OPENJDK_MINIMUM_BUILD and later."
                        return 1
                fi
        fi

        if [ "$run" -eq 0 ] && [ "$force" -eq 0 ]; then
            warn "$JP is not an Oracle (or compatible) JVM."
            warn "You can use the '-f' option to ignore this check, but you will be on your own for support."
            return 1
        else
            warn "$JP is not a supported JVM.  Using it anyways."
            return 0
        fi
    else
        warn "$JP is not Java ${minimum_version} or newer."
        return 1
    fi
}

#
# Function: javaPaths()
# This function attempts to find a path
# to a java executable.  It searchs for
# files (hopefully directories) that match
# the regex "^j2*" or "^java$" and appends "/bin/java".
# It then tests to see if this there is an
# executable file by this name.  This is not
# foolproof but should get us close.
#
javaPaths() {
        if [ -x "${opennms_home}/bin/find-java.sh" ]; then
                found="$("${opennms_home}/bin/find-java.sh" "${minimum_version}" "${maximum_version}")"
                if [ -d "${found}" ] && [ -x "${found}/bin/java" ]; then
                        JP="${found}/bin/java"
                        return 0
                fi
        fi

        warn "${opennms_home}/bin/find-java.sh failed to find a valid JDK. Falling back to the old algorithm."
        for searchdir in "${searchdirs[@]}"; do
                if [ ! -d "$searchdir" ]; then
                        continue;
                fi

                # We search "j2*" for the Sun-supplied Java packages ("j2sdk"),
                # "java" for the Java installation shipped with SuSE, and "Home"
                # to catch /Library/Java/Home on Mac OS X.
                jdirs="$(find -L "$searchdir" -maxdepth 3 \( -name "j2*" -o -name "jdk*" -o -name "java" -o -name "Home" \) -print 2>/dev/null)"
                for jdir in "$searchdir/java/default" "$searchdir/java/latest" $jdirs; do
                        if [ -x "$jdir/bin/java" ]; then
                                JP="$jdir/bin/java"
                                javaCheck && return 0
                        fi
                done
        done
        return 1
}

#
# Function: checkJavaHome()
# See if $JAVA_HOME/bin/java is a good JVM.
#
checkJavaHome() {
    tell "Checking for an appropriate JVM in JAVA_HOME..."
    if [ "$JAVA_HOME" = "" ]; then
        tell "Skipping... JAVA_HOME not set."
        return 1
    fi

    if [ ! -d "$JAVA_HOME" ]; then
        tell "Skipping... JAVA_HOME (\"$JAVA_HOME\") is not a directory."
        return 1
    fi

    if [ ! -x "$JAVA_HOME/bin/java" ]; then
        warn "Skipping... JAVA_HOME is set, but \"$JAVA_HOME/bin/java\" does not exist or is not executable."
        return 1
    fi

    JP=$JAVA_HOME/bin/java
    if javaCheck; then
        tell "Found: \"$JAVA_HOME/bin/java\" is an appropriate JVM."
        return 0
    else
        warn "\"$JAVA_HOME/bin/java\" is not an appropriate JVM."
        printInstallJava
        return 1
    fi
}


#
# Function: checkPath()
# Checks for a JRE in our path, using which(1).
#
checkPath() {
    JP="$(command -v java || :)"
    if [ "$?" -eq 1 ] || [ -z "$JP" ]; then
        warn "Did not find a JVM in the PATH."
        return 1
    fi

    readlink="$(command -v readlink 2>/dev/null)"
    if [ -n "$readlink" ] && [ -n "$JP" ]; then
        JP="$(readlink -f "$JP")"
    fi

    if [ -n "$JP" ]; then
        tell "Checking JVM in the PATH: \"$JP\"..."
        if javaCheck; then
            tell "Found an appropriate JVM in the PATH: \"$JP\""
            return 0
        else
            warn "Did not find an appropriate JVM in the PATH: \"$JP\""
        fi
    else
        warn "Did not find any appropriate JVM in the PATH."
    fi
    return 1
}

#
# Function: findJava()
# A wrapper around javaPaths to give the user a clue about what is going on.
#
findJava() {
    tell "Searching for a good JVM..."
    if javaPaths; then
        tell "Found a good JVM in \"$JP\"."
        return 0
    else
        warn "Did not find an appropriate JVM in common JVM locations."
        return 1
    fi
}


#
# Function: javaFind()
# This function first checks JAVA_HOME, then
# the current path for a java executable and then
# searches for one by calling javaPaths().
#
javaFind() {
    tell "Looking for an appropriate JVM..."

    checkJavaHome && return 0
    checkPath && return 0
    findJava && return 0

    warn "Gave up searching for an appropriate JVM."
    warn "You can set a particular JVM by using \"$basename -S <java-executable>\"."
    warn ""
    printInstallJava

    return 1
}

#
# Function: checkConfiguredJava()
# Verifies that the java configured in $java_conf looks good.
# Complains and returns 1 if it doesn't look good.
# Is quiet and returns 0 if things look good.
#
checkConfiguredJava() {
    _verbose="$1"
    mantra="Run \"$opennms_home/bin/runjava -s\" to set up the java.conf file."

    if [ ! -f "$java_conf" ]; then
        warn "Error: $java_conf file not found."
        warn "$mantra"
        printInstallJava

        return 1
    fi

    JP="$(cat "$java_conf")"

    if [ -z "$JP" ] || [ ! -x "$JP" ]; then
        warn "Error: configured JVM not found."
        warn "\"$JP\" does not exist or is not executable."
        warn "$mantra"
        printInstallJava

        return 1
    fi

    if ! javaCheck; then
        warn "Error: bad version or vendor for configured JVM."
        warn "\"$JP -version\" does not report that is version 1.8+ and a compatible JDK."
        warn "$mantra"
        printInstallJava

        return 1
    fi

    if [ "$_verbose" ]; then
        echo "$JP"
    fi

    return 0
}

#
# Function: runJava()
# Verifies that we have an appropriate Java configured in $java_conf and
# execs it with the command-line arguments passed to us.
#
runJava() {
    if ! checkConfiguredJava; then
        if [ "$force" -gt 0 ]; then
            warn "Ignoring JVM warning due to '-f' option being used."
        else
            return 1
        fi
    fi

    if [ "$dryrun" -eq 0 ]; then
        add_ld_path
        exec "$JP" "$@"
    else
        tell "Dry run mode is set.  Would have executed: $JP $*"
    fi
}

#
# Function: add_ld_path()
# Adds $opennms_home/lib to the linker path.
#
add_ld_path () {
    case "$(uname)" in
        Darwin)
            if echo "$DYLD_LIBRARY_PATH" | \
                grep -v "$opennms_home/lib" >/dev/null; then
                DYLD_LIBRARY_PATH="$DYLD_LIBRARY_PATH:$opennms_home/lib:$opennms_home/lib/mac"
                export DYLD_LIBRARY_PATH
            fi
            return
            ;;

        Linux)
            if echo "$LD_LIBRARY_PATH" | \
                grep -v "$opennms_home/lib" >/dev/null; then
                LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$opennms_home/lib"
                export LD_LIBRARY_PATH
            fi

            case "$(uname -p)" in
                x86_64)
                    if echo "$LD_LIBRARY_PATH" | \
                        grep -v "$opennms_home/lib/linux64" >/dev/null; then
                        LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$opennms_home/lib/linux64"
                        export LD_LIBRARY_PATH
                    fi
                    ;;

                    *)
                    if echo "$LD_LIBRARY_PATH" | \
                        grep -v "$opennms_home/lib/linux32" >/dev/null; then
                        LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$opennms_home/lib/linux32"
                        export LD_LIBRARY_PATH
                    fi
                    ;;
            esac

            return
            ;;

        *)
            if echo "$LD_LIBRARY_PATH" | \
                grep -v "$opennms_home/lib" >/dev/null; then
                LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$opennms_home/lib"
                export LD_LIBRARY_PATH
            fi
            return
            ;;
    esac

    return 1
}


#
# Function: setupJava()
# Calls javaFind and if successful, stores the location of the Java in
# $java_conf.
#
setupJava() {
    if javaFind; then
        saveJava
        return $?
    else
        return 1
    fi
}

#
# Function: setJava()
# Calls javaFind and if successful, stores the location of the Java in
# $java_conf.
#
setJava() {
    tell "Checking specified JVM: \"$JP\"..."
    if [ ! -x "$JP" ]; then
        die "Specified JVM \"$JP\" does not exist or is not executable."
    fi
    if ! javaCheck; then
        if [ "$force" -gt 0 ]; then
            warn "JVM is not an appropriate version and/or vendor."
            warn "Ignoring warning due to '-f' option being used."
        else
            die "Specified executable \"$JP\" is not an appropriate JVM."
        fi
    else
        tell "Specified JVM is good."
    fi

    saveJava
    return $?
}

saveJava() {
    if [ $dryrun -eq 0 ]; then
        if ! echo "$JP" > "$java_conf"; then
            warn "Error saving JVM to configuration file."
            warn "Configuration file: $java_conf"
            die "Exiting..."
        fi
        tell "Value of \"$JP\" stored in configuration file."
    else
        tell "Value of \"$JP\" would have been stored if not in dryrun mode."
    fi

    return 0
}

parseArgsAndDoStuff() {
    exclusive=0

    verbose=0
    check=0
    force=0
    print_version=0
    quiet=0
    run=0
    search=0
    location=""

    while getopts vcfhnpqrsS:j:a: c
    do
      case $c in
          a)
              searchdirs=("$OPTARG" "${searchdirs[@]}");
              ;;

          c)
              check=1
              exclusive="$((exclusive + 1))"
              ;;

          f)
              force=1
              ;;

          h)
              printHelp
              exit 0
              ;;

          j)
              java_conf="$OPTARG/java.conf"
              ;;

          n)
              dryrun=1
              ;;

          p)
              print_version=1
              exclusive="$((exclusive + 1))"
              ;;

          q)
              quiet=1
              ;;

          r)
              run=1
              exclusive="$((exclusive + 1))"
              ;;

          s)
              search=1
              exclusive="$((exclusive + 1))"
              ;;

          S)
              location="$OPTARG"
              exclusive="$((exclusive + 1))"
              ;;

          v)
              verbose=1
              ;;

          \?)
              die "Invalid option.  Use \"-h\" for help."
              ;;
      esac
    done
    shift "$((OPTIND - 1))"

    if [ $exclusive -eq 0 ]; then
        warn "You must choose one of the command-line options."
        die "Use \"-h\" for help."
    fi

    if [ $exclusive -gt 1 ]; then
        warn "You must choose only one of the command-line options."
        die "Use \"-h\" for help."
    fi

    if [ $quiet -gt 0 ]; then
        exec 3>/dev/null
    else
        exec 3>&1
    fi

    if [ $check -gt 0 ]; then
        checkConfiguredJava "$verbose"
        exit $?
    fi

    if [ x"$location" != x"" ]; then
        JP="$location"
        setJava
        exit $?
    fi

    if [ $run -gt 0 ]; then
        runJava "$@"
        exit $?
    fi

    if [ $search -gt 0 ]; then
        setupJava
        exit $?
    fi

    if [ $print_version -gt 0 ]; then
        checkConfiguredJava "$verbose" >/dev/null
        printVersion
        exit $?
    fi

    die "Internal error... got to end of script and shouldn't have."
}

parseArgsAndDoStuff "$@"
