#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

function usage() {
    cat <<EOF
USAGE: modify_war <args>
WHERE <args>:

    -e <jar>
                    Assumes the input file is an .ear file and will add the
                    given jar as a shared, application library. The file will
                    be added in a /lib directory (by default) and any embedded
                    .war files will have a corresponding Class-Path entry added
                    to their MANIFEST.MF file. The option can be given multiple times.

    -h
                    Displays this help message.

    -j <jar>
                    Additional library to add to the input file. Can be given
                    multiple times.

    -J <jvm opt>
                    JVM argument to pass to sub-commands. Typically this might
                    be to define proxy values. For example -J"-Dhttp.proxyHost=my-proxy"

    -l <lib>
                    Library directory where new jars will be placed inside war.
                    Defaults to WEB-INF/lib.

    -m <lib>
                    Library directory where new jars will be placed inside ear.
                    Defaults to /lib.

    -o <file>
                    The output file.

    -p <param=value>
                    Specific parameter for inclusion into the session filter
                    definition as a regular init-param. Can be given multiple times.

    -r
                    Test run which only outputs an updated web.xml file.

    -t <cache-type>
                    Type of cache. Must be one of 'peer-to-peer' or
                    'client-server'. Default is peer-to-peer.

    -v              
                    Verbose output
 
    -w <war/ear file>
                    The input file - either a WAR or EAR. The following actions
                    will be performed depending on the type of file:
                    WARs will have a <filter> element added to the web.xml and
                    will have the appropriate jars copied to WEB-INF/lib.
                    If the file is an EAR, then the appropriate jars will be
                    copied to lib, within the ear and each embedded war files'
                    manifest will have a Class-Path entry added (if one does
                    not already exist).
                    An appropriate slf4j binding jar must be included for ears
                    or wars using -e or -j respectively. The following jars are
                    provided:
                        slf4j-jdk14
                        slf4j-log4j12
                        geode-modules-slf4j-weblogic

    -x
                    Do not create a self-contained war/ear file by copying all
                    necessary jars into the file. When this option is used,
                    additional jars will need to be made available to the
                    container:
                        geode-modules.jar
                        geode-modules-session-internal.jar
                        geode-core.jar
                        geode-common.jar
                        geode-management.jar
                        antlr.jar
                        log4j-core.jar
                        log4j-api.jar
                        log4j-jul.jar
                        fastutil.jar
                        javax.transactions-api.jar
                        jgroups.jar
                        micrometer-core.jar
                        slf4j-api.jar
                        slf4j-jdk14.jar (not for WebLogic)
                        geode-modules-slf4j-weblogic.jar (WebLogic only)
                    This option still modifies any web.xml files.
                        
EOF
}


# Two logging levels - 'I'nfo and 'D'ebug
function log() {
    local MSG=$1
    local LVL=${2:-I}

    if [ "$LVL" == "D" ]; then
        if [ -n "$DEBUG" ]; then
            echo "$(date '+%Y-%m-%d %H:%M:%S')  $LVL  $MSG" >&2
        fi
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S')  $LVL  $MSG" >&2
    fi
}


function exiting() {
    local MSG=$1
    local CODE=${2:-0}

    if [ -n "$MSG" ]; then
        echo "ERROR: $MSG"
    fi

    rm -rf $TMP_DIR
    exit $CODE
}


function add_war_jars() {
    local WAR_LIB_DIR=$1/$ARG_WAR_LIB_DIR/
    mkdir -p $WAR_LIB_DIR

    log "Copying jar(s) into war's '$ARG_WAR_LIB_DIR' directory" D
    for J in ${ARG_WAR_LIBS[*]}; do
        log "    $J" D
        cp $J $WAR_LIB_DIR || exiting "Unable to copy $J to temp location $WAR_LIB_DIR" 1
    done
}


function process_manifest() {
    local MANIFEST=$1
    local TMP_MANIFEST

    log "Processing manifest $MANIFEST" D

    CP_LIBS=""
    for J in ${OTHER_JARS[*]} ${ARG_EAR_LIBS[*]}; do
        CP_LIBS="$CP_LIBS $ARG_EAR_LIB_DIR/$(basename $J)"
    done

    TMP_MANIFEST="$TMP_DIR/manifest.mf.$$"
    cp $MANIFEST $TMP_MANIFEST

    awk -v CP_LIBS="$CP_LIBS" '
        BEGIN {
            cp = 0
            split(CP_LIBS, cp_array)
        }
        /^Class-Path/ {print $0 CP_LIBS; cp = 1; getline}
        /^ *\r?$/ {
            if (cp == 0) {
                print "Class-Path:" CP_LIBS
                cp = 1
            }
        }
        {print $0}
        END { if (cp == 0) print "Class-Path:" CP_LIBS }
    ' $TMP_MANIFEST > $MANIFEST

    rm $TMP_MANIFEST
}


function process_web_xml() {
    local WORK_DIR=$1
    local ARG_P=""
    local JVM_OPTS=""

    for i in ${ARG_GEMFIRE_PARAMETERS[*]}; do
        ARG_P="$ARG_P -p $i"
    done

    for j in ${ARG_JVM_OPTS[*]}; do
        JVM_OPTS="$JVM_OPTS $j"
    done
    
    WEB_XML=$(find $WORK_DIR -name web.xml)
    TMP_WEB_XML="${WORK_DIR}/web.xml.$$"
    CMD="${JAVACMD} $JVM_OPTS -jar $SESSION_JAR $ARG_P -t $ARG_CACHE_TYPE -w $WEB_XML"
    log "Executing java cmd: ${CMD}" D

    if [ $ARG_TEST_RUN -eq 0 ]; then
        eval ${CMD} > $TMP_WEB_XML || exiting "Error updating web.xml" 1
        cp $TMP_WEB_XML $WEB_XML
        rm -f $TMP_WEB_XML
    else
        eval ${CMD} || exiting "Error updating web.xml" 1
    fi
}


function process_input_file() {
    local WORK_DIR=$1

    if [[ "$ARG_INPUT_FILE" =~ \.war$ ]]; then
        process_web_xml $WORK_DIR
        add_war_jars $WORK_DIR
        return
    fi
 
    WAR_LIST=$( find $WORK_DIR -name '*.war' )
    for WAR in $WAR_LIST; do
        log "Processing embedded war file $WAR" D
        TMP_WAR_DIR="${WAR}.$$"

        log "Unzipping war to $TMP_WAR_DIR" D
        unzip -q -o -d $TMP_WAR_DIR $WAR

        process_web_xml $TMP_WAR_DIR
        if [ $ARG_TEST_RUN -eq 0 ]; then
            add_war_jars $TMP_WAR_DIR

            MANIFEST=$( find $TMP_WAR_DIR -name MANIFEST.MF )

            if [ $ARG_PROCESS_LIBS -eq 1 -a -n "$MANIFEST" ]; then
                process_manifest $MANIFEST
            fi

            log "Creating new war $WAR" D
            if [ -n "$MANIFEST" ]; then
                $JARCMD cmf $MANIFEST $WAR -C $TMP_WAR_DIR .
            else
                $JARCMD cf $WAR -C $TMP_WAR_DIR .
            fi
        fi

        rm -rf $TMP_WAR_DIR
    done
}

########  Mainline  #########

X=`dirname $0`
EXE_DIR=`cd $X; pwd`
LIB_DIR=`cd ${X}/../lib; pwd`
VERSION="1.14.4"

if [ -z "$GEODE" ]; then
    exiting "Please set the GEODE environment variable to the root of the Geode install location" 1
fi

SESSION_JAR="${LIB_DIR}/geode-modules-session-${VERSION}.jar"

declare -a OTHER_JARS
OTHER_JARS=(${GEODE}/lib/geode-core-${VERSION}.jar \
    ${GEODE}/lib/geode-logging-${VERSION}.jar \
    ${GEODE}/lib/geode-membership-${VERSION}.jar \
    ${GEODE}/lib/geode-serialization-${VERSION}.jar \
    ${GEODE}/lib/geode-tcp-server-${VERSION}.jar \
    ${GEODE}/lib/geode-common-${VERSION}.jar \
    ${GEODE}/lib/geode-log4j-${VERSION}.jar \
    ${GEODE}/lib/geode-management-${VERSION}.jar \
    ${GEODE}/lib/antlr-2.7.7.jar \
    ${GEODE}/lib/log4j-core-2.17.1.jar \
    ${GEODE}/lib/log4j-api-2.17.1.jar \
    ${GEODE}/lib/log4j-jul-2.17.1.jar \
    ${GEODE}/lib/fastutil-8.5.2.jar \
    ${GEODE}/lib/javax.transaction-api-1.3.jar \
    ${GEODE}/lib/jetty-http-9.4.39.v20210325.jar \
    ${GEODE}/lib/jetty-io-9.4.39.v20210325.jar \
    ${GEODE}/lib/jetty-server-9.4.39.v20210325.jar \
    ${GEODE}/lib/jetty-util-9.4.39.v20210325.jar \
    ${GEODE}/lib/jgroups-3.6.14.Final.jar \
    ${GEODE}/lib/commons-io-2.8.0.jar \
    ${GEODE}/lib/commons-lang3-3.11.jar \
    ${GEODE}/lib/shiro-core-1.8.0.jar \
    ${GEODE}/lib/commons-validator-1.7.jar \
    ${GEODE}/lib/micrometer-core-1.6.3.jar \
    ${LIB_DIR}/geode-modules-${VERSION}.jar \
    ${LIB_DIR}/geode-modules-session-internal-${VERSION}.jar \
    ${LIB_DIR}/slf4j-api-1.7.30.jar \
    ${LIB_DIR}/slf4j-jdk14-1.7.30.jar)

TMP_DIR="/tmp/modify_war.$$"

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    JAVACMD="$JAVA_HOME/bin/java"
    JARCMD="$JAVA_HOME/bin/jar"
    if [[ ! -x "$JAVACMD" || ! -x "$JARCMD" ]] ; then
        echo "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation."
        exit 1;
    fi
else
    hash java >/dev/null 2>&1 || { echo "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation."; exit 1;}
    hash jar >/dev/null 2>&1 || { echo "ERROR: JAVA_HOME is not set and no 'jar' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation."; exit 1;}
    JAVACMD="java"
    JARCMD="jar"
fi

ARG_INPUT_FILE=""
ARG_WAR_LIB_DIR="WEB-INF/lib"
ARG_EAR_LIB_DIR="lib"
ARG_OUTPUT_FILE=""
ARG_TEST_RUN=0
ARG_CACHE_TYPE=""
ARG_PROCESS_LIBS=1
declare -a ARG_GEMFIRE_PARAMETERS=""
declare -a ARG_WAR_LIBS="$SESSION_JAR"
declare -a ARG_EAR_LIBS=""
declare -a ARG_JVM_OPTS=""

trap exiting INT QUIT TERM

mkdir $TMP_DIR

CMD_ARGS="-j $SESSION_JAR"

while getopts "e:hj:J:l:m:o:p:rt:vw:x" OPT; do
    case $OPT in
        e)
            if [ ! -f $OPTARG ]; then
                exiting "Cannot read file '$OPTARG' given with option -e" 1
            fi
            ARG_EAR_LIBS[${#ARG_EAR_LIBS[@]}]=$OPTARG
            ;;
        h)
            usage
            exiting "" 1
            ;;
        j)
            if [ ! -f $OPTARG ]; then
                exiting "Cannot read file '$OPTARG' given with option -j" 1
            fi
            ARG_WAR_LIBS[${#ARG_WAR_LIBS[@]}]=$OPTARG
            ;;
        J)
            ARG_JVM_OPTS[${#ARG_JVM_OPTS[@]}]=$OPTARG
            ;;
        l)
            ARG_WAR_LIB_DIR=$OPTARG
            ;;
        m)
            ARG_EAR_LIB_DIR=$OPTARG
            ;;
        o)
            ARG_OUTPUT_FILE=$OPTARG
            ;;
        p)
            ARG_GEMFIRE_PARAMETERS[${#ARG_GEMFIRE_PARAMETERS[@]}]=$OPTARG
            ;;
        r)
            ARG_TEST_RUN=1
            ;;
        t)
            case $OPTARG in
                peer-to-peer|client-server)
                    ARG_CACHE_TYPE=$OPTARG
                    ;;
                *)
                    exiting "Invalid cache type '$OPTARG' given with option -t. Options are 'peer-to-peer' or 'client-server'." 1
                    ;;
            esac
            ;;
        v)
            DEBUG=1
            ;;
        w)
            if [ ! -f $OPTARG ]; then
                exiting "Cannot read file '$OPTARG' given with option -w" 1
            fi
            ARG_INPUT_FILE=$OPTARG
            ;;
        x)
            ARG_PROCESS_LIBS=0
            ;;
        [?])
            echo "Unknown option '$OPTARG'"
            echo
            usage
            exit 1
            ;;
        :)
            echo "Option '$OPTARG' requires an argument"
            echo
            usage
            exit 1
            ;;
    esac
done

# Some validation
if [ -z "$ARG_INPUT_FILE" ]; then
    exiting "Please supply an input file with the -w option" 1
fi

if [ -z "$ARG_OUTPUT_FILE" ]; then
    ARG_OUTPUT_FILE="sessions-$(basename $ARG_INPUT_FILE)"
fi

if [[ "$ARG_INPUT_FILE" =~ \.war$ && -n "${ARG_EAR_LIBS[*]}" ]]; then
    log "Input file appears to be a war but -e also specified. EAR processing will be skipped." W
fi

if [[ "$ARG_INPUT_FILE" =~ \.war$ && $ARG_PROCESS_LIBS -eq 1 ]]; then
    for J in ${OTHER_JARS[*]}; do
        ARG_WAR_LIBS[${#ARG_WAR_LIBS[@]}]=$J
    done
fi

unzip -q -o -d $TMP_DIR $ARG_INPUT_FILE
process_input_file $TMP_DIR

if [[ $ARG_TEST_RUN -eq 0 && $ARG_PROCESS_LIBS -eq 1 && "$ARG_INPUT_FILE" =~ \.ear$ ]]; then
    log "Copying additional jars into ear's '$ARG_EAR_LIB_DIR' directory" D
    mkdir -p $TMP_DIR/$ARG_EAR_LIB_DIR
    for i in ${OTHER_JARS[*]} ${ARG_EAR_LIBS[*]}; do
        log "    $i" D
        cp $i $TMP_DIR/$ARG_EAR_LIB_DIR/ || exiting "Unable to copy $i to $TMP_DIR/$ARG_EAR_LIB_DIR" 1
    done
fi

if [ $ARG_TEST_RUN -eq 0 ]; then
    mkdir -p $TMP_DIR/META-INF
    touch $TMP_DIR/META-INF/MANIFEST.MF
    $JARCMD cmf $TMP_DIR/META-INF/MANIFEST.MF $ARG_OUTPUT_FILE -C $TMP_DIR .
    log "Created file: $ARG_OUTPUT_FILE" I
fi

exiting
