Skip to content

Instantly share code, notes, and snippets.

@FredIde
Last active September 29, 2021 10:43
Show Gist options
  • Select an option

  • Save FredIde/825c868f60edbad3c886 to your computer and use it in GitHub Desktop.

Select an option

Save FredIde/825c868f60edbad3c886 to your computer and use it in GitHub Desktop.

Revisions

  1. FredIde revised this gist Jan 13, 2016. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions aem-control.sh
    Original file line number Diff line number Diff line change
    @@ -93,11 +93,11 @@
    # Syslog facility to use (default is 'local0')
    #
    # COPYRIGHT
    # Copyright (c) 2013-2015 Digital Venture Unternehmensberatung GmbH
    # Apache License V2
    # All rights reserved.
    #
    # AUTHORS
    # Maximilian Kolmhuber (mk@dvu.eu)
    # Fred Ide
    #
    # CHANGE HISTORY
    # 2013-04-26 mk@dvu.eu
  2. FredIde created this gist Jan 13, 2016.
    2,014 changes: 2,014 additions & 0 deletions aem-control.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2014 @@
    #!/bin/bash
    #set -x

    #==========================================================================
    # NAME
    # aem-control - start, stop, restart or check the status of
    # an AEM instance (>= 5.5.0)
    #
    # SYNOPSIS
    # aem-control [start|stop|restart|status|activate|sweep|help|version]
    #
    # DESCRIPTION
    # Starts, stops, restartes or checks the status of a AEM instance.
    #
    # Mandatory command line arguments are
    #
    # start Start the Adobe AEM service
    # stop Stop the Adobe AEM service
    # restart Restart the Adobe AEM service
    # status Check the status of the Adobe AEM service
    # activate Activate all resolved Apache Felix bundles
    # sweep Run memory garbage collection
    # help Display usage information
    # version Display version information
    #
    # FILES
    # A configuration file "../etc/aem-control.conf" is required to provide
    # the following configuration variables:
    #
    # USERNAME
    # Operating system user to run this script as
    #
    # JAVA_HOME
    # Home directory of Java JDK
    #
    # JAVA_LANG
    # Value of LANG environment variable to be passed to Java
    # (e.g. 'en_US.UTF-8')
    #
    # JAVA_TZ
    # Value of TZ environment variable to be passed to Java
    # (e.g. 'UTC')
    #
    # JAVA_EXTRA_OPT_ARRAY
    # JVM extra options to use (garbage collector etc.)
    #
    # AEM_HOME
    # AEM home directory (the directory in which the 'crx-quickstart'
    # directory resides)
    #
    # AEM_ADDRESS
    # AEM listen address (usually '0.0.0.0' for all interfaces, otherwise
    # IP address of specific interface)
    #
    # AEM_PORT
    # AEM port number (usually 4502 for author and 4503 for publish
    # instances)
    #
    # AEM_USERNAME
    # Username of the administrative AEM user (usually 'admin')
    #
    # AEM_PASSWORD
    # Password of the administrative AEM user
    #
    # AEM_RUN_MODES
    # Comma-delimited list of AEM run modes (must either contain 'author' or 'publish')
    #
    # LOG_HOME
    # Home directory for AEM log directories
    #
    # LOG_HISTORY
    # Maximum number of log directories to keep
    #
    # CONNECT_TIMEOUT
    # Maximum number of seconds to wait for network connection
    # to be established (default is 5 seconds)
    #
    # SOCKET_TIMEOUT
    # Maximum number of seconds to wait for a complete response
    # from a host after a request was sent (default is 120 seconds)
    #
    # START_TIMEOUT
    # Timeout for AEM service to start in seconds (default is 600 seconds)
    #
    # STOP_TIMEOUT
    # Timeout for AEM service to stop in seconds (default is 1800 seconds)
    #
    # CHECK_DELAY
    # Delay in between successive functional checks in seconds
    # (default is 10 seconds)
    #
    # SYSLOG_FACILITY
    # Syslog facility to use (default is 'local0')
    #
    # COPYRIGHT
    # Copyright (c) 2013-2015 Digital Venture Unternehmensberatung GmbH
    # All rights reserved.
    #
    # AUTHORS
    # Maximilian Kolmhuber (mk@dvu.eu)
    #
    # CHANGE HISTORY
    # 2013-04-26 mk@dvu.eu
    # Initial release
    # 2015-10-12 FAI
    # Bug fixing
    #==========================================================================

    #--------------------------------------------------------------------------
    # NAME
    # whence - search for a command and return its absolute file name
    #
    # SYNOPSIS
    # whence [command]
    #
    # DESCRIPTION
    # Searches for a command and returns its absolute file name
    #--------------------------------------------------------------------------

    declare -r PATH='/sbin:/bin:/usr/sbin:/usr/bin'

    whence() {
    local -r COMMAND_NAME="$1"
    local COMMAND_FILE

    # Search PATH for command
    COMMAND_FILE="$(type -p "$COMMAND_NAME" 2>/dev/null)"
    if [[ -x "$COMMAND_FILE" ]]; then
    # Found
    echo "$COMMAND_FILE"
    else
    # Not found
    echo -n "Error: \"$COMMAND_NAME\" - command not found" >/dev/tty
    kill -2 $$
    fi
    }

    # Declare absolute file names of all external commands used by this script
    declare -r CAT="$(whence cat)"
    declare -r CURL="$(whence curl)"
    declare -r DATE="$(whence date)"
    declare -r ENV="$(whence env)"
    declare -r FIND="$(whence find)"
    declare -r GREP="$(whence grep)"
    declare -r HOSTNAME="$(whence hostname)"
    declare -r ID="$(whence id)"
    declare -r IFCONFIG="$(whence ifconfig)"
    declare -r LN="$(whence ln)"
    declare -r LOGGER="$(whence logger)"
    declare -r MKDIR="$(whence mkdir)"
    declare -r MV="$(whence mv)"
    declare -r NOHUP="$(whence nohup)"
    declare -r PGREP="$(whence pgrep)"
    declare -r READLINK="$(whence readlink)"
    declare -r RM="$(whence rm)"
    declare -r RMDIR="$(whence rmdir)"
    declare -r SLEEP="$(whence sleep)"
    declare -r SORT="$(whence sort)"
    declare -r SU="$(whence su)"
    declare -r TAIL="$(whence tail)"

    #--------------------------------------------------------------------------
    # NAME
    # real_path - return the canonicalized absolute pathname of a given
    # pathname
    #
    # SYNOPSIS
    # real_path [pathname]
    #
    # DESCRIPTION
    # Expands all symbolic links and resolves references to /./, /../ and
    # extra '/' characters in the specified pathname to produce a
    # canonicalized absolute pathname. If the specified pathname is invalid,
    # the function will set the return code to 1.
    #--------------------------------------------------------------------------
    real_path() {
    local -r REL_PATH="$1"
    local ABS_PATH

    ABS_PATH="$("$READLINK" -f "$REL_PATH" 2>/dev/null)"
    if [[ -z "$ABS_PATH" ]]; then
    echo "$REL_PATH"
    return 1
    fi

    echo "$ABS_PATH"
    return 0
    }

    #--------------------------------------------------------------------------
    # Global Variables
    #--------------------------------------------------------------------------

    # General constants
    declare -r LF=$'\n'

    # Declare and initialize script-releated variables
    declare -r MY_VERSION='1.3.0'
    declare -r MY_SCRIPT_FILE="$(real_path "$0")"
    declare -r MY_SCRIPT_NAME="${MY_SCRIPT_FILE##*/}"
    declare -r MY_SCRIPT_DIR="${MY_SCRIPT_FILE%/*}"
    declare -r MY_SCRIPT_ARGS="$@"
    declare -r MY_FQDN="$("$HOSTNAME" -f 2>/dev/null)"
    declare -ri MY_PID=$$
    declare -r MY_CWD="$PWD"
    declare -r MY_IFS="$IFS"
    declare -r MY_TIMESTAMP="$("$DATE" -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)"
    declare MY_LOCK_FILE="/tmp/.${MY_SCRIPT_NAME}.lock"

    # Declare configuration file variables
    declare USERNAME
    declare JAVA_HOME
    declare JAVA_LANG='en_US.UTF-8'
    declare JAVA_TZ='UTC'
    declare JAVA_HEAP_SIZE
    declare JAVA_PERM_SIZE
    declare -a JAVA_EXTRA_OPT_ARRAY
    declare AEM_HOME
    declare AEM_ADDRESS='0.0.0.0' # default to all interfaces
    declare AEM_PORT=4502 # default to port 4502
    declare AEM_USERNAME='admin' # default to 'admin'
    declare AEM_PASSWORD='admin' # default to 'admin'
    declare AEM_RUN_MODES='author' # default to 'author'
    declare AEM_UMASK='0022'
    declare -a AEM_EXTRA_OPT_ARRAY
    declare LOG_HOME
    declare LOG_HISTORY=7 # default to 7 days
    declare CONNECT_TIMEOUT=5 # default to 5 seconds
    declare SOCKET_TIMEOUT=120 # default to 2 minutes
    declare START_TIMEOUT=1800 # default to 30 minutes
    declare -a SKIP_BUNDLE_ARRAY
    declare SKIP_BUNDLE_REGEX
    declare STOP_TIMEOUT=1800 # default to 30 minutes
    declare CHECK_DELAY=10 # Default to 10 seconds
    declare SYSLOG_FACILITY='local0' # default to 'local0'

    # Declare internal variables
    declare CONF_FILE
    declare JAVA
    declare AEM_BASE_DIR
    declare AEM_JAR_FILE
    declare AEM_INSTANCE_NAME
    declare AEM_AUTHORITY
    declare AEM_CREDENTIALS
    declare AEM_PID

    #--------------------------------------------------------------------------
    # NAME
    # version - print version information and exit
    #
    # SYNOPSIS
    # version
    #
    # DESCRIPTION
    # Writes version information to standard output and terminates the script
    # with error code 0.
    #--------------------------------------------------------------------------
    version() {
    echo ''
    echo "$MY_SCRIPT_NAME version $MY_VERSION"
    echo ''
    echo 'Copyright (c) 2013-2015 Digital Venture Unternehmensberatung GmbH'
    echo 'All rights reserved.'
    echo ''

    exit 0
    }

    #--------------------------------------------------------------------------
    # NAME
    # usage - print usage information and exit
    #
    # SYNOPSIS
    # usage [exit_code]
    #
    # DESCRIPTION
    # Writes usage information to standard output and terminates the script
    # with the specified error code or the default exit code 2.
    #--------------------------------------------------------------------------
    usage() {
    local -ri EXIT_CODE=${1:-2}

    echo ''
    echo "Usage: $MY_SCRIPT_NAME [start|stop|restart|status|help|version] (first)"
    echo ''
    echo 'Mandatory Arguments:'
    echo 'start Start the Adobe AEM service'
    echo 'stop Stop the Adobe AEM service'
    echo 'restart Restart the Adobe AEM service'
    echo 'status Check the status of the Adobe AEM service'
    echo 'activate Activate all installed/resolved Apache Felix bundles'
    echo 'sweep Run memory garbage collection'
    echo 'help Display this message'
    echo 'version Display version information'
    echo ''
    exit $EXIT_CODE
    }

    #--------------------------------------------------------------------------
    # NAME
    # log - print and log messages
    #
    # SYNOPSIS
    # log [severity] [message...]
    #
    # DESCRIPTION
    # Writes the specified message to the respecitve log.
    #--------------------------------------------------------------------------
    log() {
    local SYSLOG_SEVERITY="$1"
    local SEVERITY
    local LINE

    case "$SYSLOG_SEVERITY" in
    'emerg')
    # Emergency: system is unusable
    SEVERITY='Emergency'
    ;;
    'alert')
    # Alert: action must be taken immediately
    SEVERITY='Alert'
    ;;
    'crit')
    # Critical: critical condition
    SEVERITY='Critical'
    ;;
    'err')
    # Error: error condition
    SEVERITY='Error'
    ;;
    'warning')
    # Warning: warning condition
    SEVERITY='Warning'
    ;;
    'notice')
    # Notice: normal but significant condition
    SEVERITY='Notice'
    ;;
    'info')
    # Informational: informational message
    SEVERITY='Info'
    ;;
    'debug')
    # Debug: debug-level message
    SEVERITY='Debug'
    ;;
    *)
    # Invalid severity
    SYSLOG_SEVERITY='err'
    SEVERITY='Unknown severity'
    ;;
    esac
    shift

    echo "$SEVERITY: $1"
    "$LOGGER" \
    -p $SYSLOG_FACILITY.$SYSLOG_SEVERITY \
    -t "$MY_SCRIPT_NAME" \
    -i "$SEVERITY: $1" \
    &>/dev/null
    shift

    while (( $# > 0 )); do
    IFS="$LF"
    for LINE in $1; do
    if [[ -n "$LINE" ]]; then
    echo "Details: $LINE"
    "$LOGGER" \
    -p $SYSLOG_FACILITY.$SYSLOG_SEVERITY \
    -t "$MY_SCRIPT_NAME" \
    -i "Details: $LINE" \
    &>/dev/null
    fi
    done
    IFS="$MY_IFS"
    shift
    done
    }

    #--------------------------------------------------------------------------
    # NAME
    # crit - print critical error message and exit
    #
    # SYNOPSIS
    # crit [message...]
    #
    # DESCRIPTION
    # Writes the specified error message to standard error and
    # terminates the script with the specified error code.
    #--------------------------------------------------------------------------
    crit() {
    log 'crit' "$@" 1>&2
    exit 1
    }

    #--------------------------------------------------------------------------
    # NAME
    # err - print error message
    #
    # SYNOPSIS
    # err [message...]
    #
    # DESCRIPTION
    # Writes the specified error message to standard error.
    #--------------------------------------------------------------------------
    err() {
    log 'err' "$@" 1>&2
    }

    #--------------------------------------------------------------------------
    # NAME
    # warning - print warning message
    #
    # SYNOPSIS
    # warning [message...]
    #
    # DESCRIPTION
    # Writes the specified warning message to standard error.
    #--------------------------------------------------------------------------
    warning() {
    log 'warning' "$@" 1>&2
    }

    #--------------------------------------------------------------------------
    # NAME
    # notice - print notification message
    #
    # SYNOPSIS
    # notice [message...]
    #
    # DESCRIPTION
    # Writes the specified notification message to standard output.
    #--------------------------------------------------------------------------
    notice() {
    log 'notice' "$@"
    }

    #--------------------------------------------------------------------------
    # NAME
    # info - print informational message
    #
    # SYNOPSIS
    # info [message...]
    #
    # DESCRIPTION
    # Writes the specified informational message to standard output.
    #--------------------------------------------------------------------------
    info() {
    log 'info' "$@"
    }

    #--------------------------------------------------------------------------
    # NAME
    # lock - exit if there is an existing lock file or create a lock file
    #
    # SYNOPSIS
    # lock
    #
    # DESCRIPTION
    # Checks if there is an existing lock file and exits if the PID of the
    # locking process is still alive. Otherwise, a lock file is created.
    #--------------------------------------------------------------------------
    lock() {
    local PID

    # Check if there is an existing lock file
    if [[ -f "$MY_LOCK_FILE" ]]; then
    if [[ -r "$MY_LOCK_FILE" ]]; then
    # Check if process is still running
    PID=$("$CAT" "$MY_LOCK_FILE")
    if [[ "$PID" =~ ^[1-9][0-9]*$ ]]; then
    kill -0 "$PID" &>/dev/null
    if (( $? == 0 )); then
    # Process is still running
    crit "\"$MY_SCRIPT_NAME\" is locked by PID $PID"
    fi
    fi
    else
    # Process is bein run by someone else
    crit "\"$MY_SCRIPT_NAME\" is being run by another user"
    fi
    fi

    # Create new lock file
    echo -n "$MY_PID" 1>"$MY_LOCK_FILE"
    }

    #--------------------------------------------------------------------------
    # NAME
    # unlock - delete lock file
    #
    # SYNOPSIS
    # unlock
    #
    # DESCRIPTION
    # Deletes the lock file.
    #--------------------------------------------------------------------------
    unlock() {
    "$RM" -f "$MY_LOCK_FILE" &>/dev/null
    }

    #--------------------------------------------------------------------------
    # NAME
    # check_mode - check the permissions of a file or directory
    #
    # SYNOPSIS
    # check_mode [item] [f][d][r][w][x]
    #
    # DESCRIPTION
    # Checks if the specified file or directory possesses the specified
    # permissions
    #--------------------------------------------------------------------------
    check_mode() {
    local ITEM="$1"
    local FLAGS="$2"

    if [[ "$FLAGS" == *f* ]]; then
    if [[ ! -f "$ITEM" ]]; then
    crit "File \"$ITEM\" not found"
    fi
    fi
    if [[ "$FLAGS" == *d* ]]; then
    if [[ ! -d "$ITEM" ]]; then
    crit "Directory \"$ITEM\" not found"
    fi
    fi
    if [[ "$FLAGS" == *r* ]]; then
    if [[ ! -r "$ITEM" ]]; then
    crit "No read permission for \"$ITEM\""
    fi
    fi
    if [[ "$FLAGS" == *w* ]]; then
    if [[ ! -w "$ITEM" ]]; then
    crit "No write permission for \"$ITEM\""
    fi
    fi
    if [[ "$FLAGS" == *x* ]]; then
    if [[ ! -x "$ITEM" ]]; then
    crit "No execute permission for \"$ITEM\""
    fi
    fi
    }

    #--------------------------------------------------------------------------
    # NAME
    # check_if_addr - check interface address
    #
    # SYNOPSIS
    # check_if_addr [if_addr]
    #
    # DESCRIPTION
    # Checks whether or not the specified interface address belongs to this
    # host.
    #--------------------------------------------------------------------------
    check_if_addr() {
    local -r IF_ADDR="$1"
    local REGEX
    local -a LINE_ARRAY
    local LINE
    local ADDR

    # Check if the specified address references all interfaces
    if [[ "$IF_ADDR" == '0.0.0.0' ]]; then
    return
    fi

    # Check if the specified address references an existing interface
    IFS="$LF"
    LINE_ARRAY=( $("$IFCONFIG" -a 2>/dev/null) )
    IFS="$MY_IFS"

    REGEX='inet[[:blank:]]+(addr:)?([[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})'
    for LINE in "${LINE_ARRAY[@]}"; do
    if [[ "$LINE" =~ $REGEX ]]; then
    ADDR="${BASH_REMATCH[2]}"
    if [[ "$IF_ADDR" == "$ADDR" ]]; then
    return
    fi
    fi
    done

    crit "The interface address \"$IF_ADDR\" does not belong to this host"
    }

    #--------------------------------------------------------------------------
    # NAME
    # load_config - load configuration file
    #
    # SYNOPSIS
    # load_config
    #
    # DESCRIPTION
    # Sources configuration variables from a configuration file.
    #--------------------------------------------------------------------------
    load_config() {
    # Assemble absolute pathname of configuration directory
    local -r CONF_DIR="$(real_path "$MY_SCRIPT_DIR/../etc")"
    # Assemble name of configuration file
    local -r CONF_NAME="${MY_SCRIPT_NAME%.*}.conf"

    # Assemble absolute pathname of configuration file
    CONF_FILE="$(real_path "$CONF_DIR/$CONF_NAME")"

    # Make sure that configuration file exists and is readable
    check_mode "$CONF_FILE" fr

    # Change to configuration directory to allow configuration file to
    # source other configuration files by their relative pathname
    cd "$CONF_DIR"

    # Source configuration file
    source "$CONF_FILE"
    }

    #--------------------------------------------------------------------------
    # NAME
    # check_config - check configuration variables
    #
    # SYNOPSIS
    # check_config
    #
    # DESCRIPTION
    # Check if the configuration variables contain valid values.
    #--------------------------------------------------------------------------
    check_config() {
    local NOFILE
    local -i REQUIRED_EUID
    local REGEX
    local LINE
    local ADDRESS
    local -a ADDRESS_ARRAY
    local -i MAX_CHECK_DELAY
    local -a JAR_ARRAY
    local -i JAR_COUNT
    local -i SKIP_BUNDLE_COUNT

    # Check if syslog facility is defined
    if [[ -z "$SYSLOG_FACILITY" ]]; then
    crit "SYSLOG_FACILITY is not defined in configuration file \"$CONF_FILE\""
    fi

    # Check if username is defined
    if [[ -z "$USERNAME" ]]; then
    crit "USERNAME is not defined in configuration file \"$CONF_FILE\""
    fi

    # Check if username exists
    REQUIRED_EUID=$("$ID" -u $USERNAME 2>/dev/null)
    if (( $? != 0 )); then
    crit "Username \"$USERNAME\" does not exist"
    elif (( EUID != REQUIRED_EUID )); then
    # Check if effective user is root
    if (( EUID == 0 )); then
    # Switch to required user
    "$SU" - $USERNAME -c "cd \"$MY_CWD\"; \"$MY_SCRIPT_FILE\" $MY_SCRIPT_ARGS"
    # Note: su returns the exit code of the executed program
    exit $?
    else
    crit "Must be run as user \"$USERNAME\""
    fi
    fi

    # Check if Java home directory is defined
    if [[ -z "$JAVA_HOME" ]]; then
    crit "JAVA_HOME is not defined in configuration file \"$CONF_FILE\""
    fi

    # Check if Java home directory exists and is accessible
    JAVA_HOME="$(real_path "$JAVA_HOME")"
    check_mode "$JAVA_HOME" drx
    check_mode "$JAVA_HOME/bin" drx

    # Check if "java" program exists and is executable
    JAVA="$JAVA_HOME/bin/java"
    check_mode "$JAVA" fx

    # Check if Java heap size is defined and valid
    if [[ -z "$JAVA_HEAP_SIZE" ]]; then
    crit "JAVA_HEAP_SIZE is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$JAVA_HEAP_SIZE" =~ ^[0-9]{4,}$ ]]; then
    crit "JAVA_HEAP_SIZE must be an integer value greater or equal 1024"
    elif (( JAVA_HEAP_SIZE < 1024 )); then
    crit "JAVA_HEAP_SIZE must be an integer value greater or equal 1024"
    fi

    # Check if Java permanent generation size is defined and valid
    if [[ -z "$JAVA_PERM_SIZE" ]]; then
    crit "JAVA_PERM_SIZE is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$JAVA_PERM_SIZE" =~ ^[0-9]{3,}$ ]]; then
    crit "JAVA_PERM_SIZE must be an integer value greater or equal 256"
    elif (( JAVA_PERM_SIZE < 256 )); then
    crit "JAVA_PERM_SIZE must be an integer value greater or equal 256"
    fi

    # Check if AEM directory is defined
    if [[ -z "$AEM_HOME" ]]; then
    crit "AEM_HOME is not defined in configuration file \"$CONF_FILE\""
    fi

    # Check if AEM directory exists and is accessible
    AEM_HOME="$(real_path "$AEM_HOME")"
    if [[ "$AEM_HOME" =~ ^/*$ ]]; then
    crit "Cowardly refusing to accept \"$AEM_HOME\" as AEM instance directory"
    fi
    check_mode "$AEM_HOME/" drwx

    # Check if AEM crx-quickstart directoy exists and is accessible
    AEM_BASE_DIR="$AEM_HOME/crx-quickstart"
    check_mode "$AEM_BASE_DIR/" drx

    # Check if AEM crx-quickstart/app directroy exists and is accessible
    check_mode "$AEM_BASE_DIR/app/" drx

    # Check if AEM crx-quickstart/logs directroy exists and is accessible
    if [[ ! -L "$AEM_BASE_DIR/logs" && -d "$AEM_BASE_DIR/logs/" ]]; then
    check_mode "$AEM_BASE_DIR/logs/" drwx
    fi

    # Check if AEM address is defined and valid
    if [[ -z "$AEM_ADDRESS" ]]; then
    crit "AEM_ADDRESS is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$AEM_ADDRESS" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
    crit "AEM_ADDRESS does not contain a valid IP address"
    else
    check_if_addr "$AEM_ADDRESS"
    fi

    # Check if AEM port is defined and valid
    if [[ -z "$AEM_PORT" ]]; then
    crit "AEM_PORT is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$AEM_PORT" =~ ^[0-9]{4,5}$ ]]; then
    crit "AEM_PORT must be an integer value in the interval [1024; 65535]"
    elif (( AEM_PORT < 1024 || AEM_PORT > 65535 )); then
    crit "AEM_PORT must be an integer value in the interval [1024; 65535]"
    fi

    # Assemble authority part of AEM URL
    if [[ "$AEM_ADDRESS" == '0.0.0.0' ]]; then
    AEM_AUTHORITY="127.0.0.1:$AEM_PORT"
    else
    AEM_AUTHORITY="$AEM_ADDRESS:$AEM_PORT"
    fi

    # Check if AEM username is defined and valid
    if [[ -z "$AEM_USERNAME" ]]; then
    crit "AEM_USERNAME is not defined in configuration file \"$CONF_FILE\""
    elif [[ "$AEM_USERNAME" =~ [:] ]]; then
    crit "AEM_USERNAME must not contain a colon character (\":\")"
    fi

    # Check if AEM password is defined and valid
    if [[ -z "$AEM_PASSWORD" ]]; then
    crit "AEM_PASSWORD is not defined in configuration file \"$CONF_FILE\""
    elif [[ "$AEM_PASSWORD" =~ [:] ]]; then
    crit "AEM_PASSWORD must not contain a colon character (\":\")"
    fi

    # Assemble credentials for AEM
    AEM_CREDENTIALS="$AEM_USERNAME:$AEM_PASSWORD"

    # Check if AEM run mode is defined and valid
    if [[ -z "$AEM_RUN_MODES" ]]; then
    crit "AEM_RUN_MODES is not defined in configuration file \"$CONF_FILE\""
    fi
    REGEX='(^|,)(author|publish)(,|$)'
    if [[ ! "$AEM_RUN_MODES" =~ $REGEX ]]; then
    crit "AEM_RUN_MODES must either contain \"author\" or \"publish\""
    fi

    # Check if log directory home is defined, exists and is accessible
    if [[ -z "$LOG_HOME" ]]; then
    crit "LOG_HOME is not defined in configuration file \"$CONF_FILE\""
    fi

    # Check if log directory home exists and is accessible
    LOG_HOME="$(real_path "$LOG_HOME")"
    check_mode "$LOG_HOME" drwx

    # Check if log directory is valid
    REGEX="^$AEM_BASE_DIR/logs(/|\$)"
    if [[ "$LOG_HOME" =~ $REGEX ]]; then
    crit "LOG_HOME must not equal or be a subdirectory of \"$AEM_BASE_DIR/logs\""
    fi

    # Check if log history is defined and valid
    if [[ -z "$LOG_HISTORY" ]]; then
    crit "LOG_HISTORY is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$LOG_HISTORY" =~ ^[0-9]+$ ]]; then
    crit "LOG_HISTORY must be an integer value greater than 0"
    elif (( LOG_HISTORY == 0 )); then
    crit "LOG_HISTORY must be an integer value greater than 0"
    fi

    # Check if connection timeout is defined and valid
    if [[ -z "$CONNECT_TIMEOUT" ]]; then
    crit "CONNECT_TIMEOUT is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$CONNECT_TIMEOUT" =~ ^[0-9]+$ ]]; then
    crit "CONNECT_TIMEOUT must be an integer value greater or equal 5"
    elif (( CONNECT_TIMEOUT < 5 )); then
    crit "CONNECT_TIMEOUT must be an integer value greater or equal 5"
    fi

    # Check if socket timeout is defined and valid
    if [[ -z "$SOCKET_TIMEOUT" ]]; then
    crit "SOCKET_TIMEOUT is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$SOCKET_TIMEOUT" =~ ^[0-9]+$ ]]; then
    crit "SOCKET_TIMEOUT must be an integer value greater or equal 15"
    elif (( SOCKET_TIMEOUT < 15 )); then
    crit "SOCKET_TIMEOUT must be an integer value greater or equal 15"
    fi

    # Check if start timeout is valid
    if [[ ! "$START_TIMEOUT" =~ ^[0-9]{2,}$ ]]; then
    crit "START_TIMEOUT must be an integer value greater or equal 60"
    elif (( START_TIMEOUT < 60 )); then
    crit "START_TIMEOUT must be an integer value greater or equal 60"
    fi

    # Check if stop timeout is valid
    if [[ ! "$STOP_TIMEOUT" =~ ^[0-9]{2,}$ ]]; then
    crit "STOP_TIMEOUT must be an integer value greater or equal 60"
    elif (( STOP_TIMEOUT < 60 )); then
    crit "STOP_TIMEOUT must be an integer value greater or equal 60"
    fi

    # Compute maximum value for delay
    if (( START_TIMEOUT < STOP_TIMEOUT )); then
    (( MAX_CHECK_DELAY = START_TIMEOUT / 4 ))
    else
    (( MAX_CHECK_DELAY = STOP_TIMEOUT / 4 ))
    fi

    # Check if check delay is valid
    if [[ -z "$CHECK_DELAY" ]]; then
    crit "CHECK_DELAY is not defined in configuration file \"$CONF_FILE\""
    elif [[ ! "$CHECK_DELAY" =~ ^[0-9]+$ ]]; then
    crit "CHECK_DELAY must be an integer value in the interval [1; $MAX_CHECK_DELAY]"
    elif (( CHECK_DELAY < 1 || CHECK_DELAY > MAX_CHECK_DELAY )); then
    crit "CHECK_DELAY must be an integer value in the interval [1; $MAX_CHECK_DELAY]"
    fi

    # Assemble regular expression for bundle exclusion
    SKIP_BUNDLE_COUNT=${#SKIP_BUNDLE_ARRAY[*]}
    while (( SKIP_BUNDLE_COUNT > 1 )); do
    (( SKIP_BUNDLE_COUNT-- ))
    SKIP_BUNDLE_REGEX="|${SKIP_BUNDLE_ARRAY[SKIP_BUNDLE_COUNT]}$SKIP_BUNDLE_REGEX"
    done
    if (( SKIP_BUNDLE_COUNT > 0 )); then
    SKIP_BUNDLE_REGEX="\"name\":\"(${SKIP_BUNDLE_ARRAY[0]}$SKIP_BUNDLE_REGEX)\""
    else
    SKIP_BUNDLE_REGEX='^$'
    fi

    # Search for Adobe AEM jar file
    JAR_ARRAY=( $( \
    "$FIND" \
    "$AEM_BASE_DIR/app" \
    -maxdepth 1 \
    -name "*.jar" \
    ) )
    JAR_COUNT=${#JAR_ARRAY[*]}

    # Check if Adobe AEM jar file exists and is accessible
    if (( JAR_COUNT < 1 )); then
    crit "Cannot find any Adobe AEM jar file that matches \"$AEM_BASE_DIR/app/*.jar\""
    elif (( JAR_COUNT > 1)); then
    crit "Found multiple Adobe AEM jar files that match \"$AEM_BASE_DIR/app/*.jar\": ${JAR_ARRAY[*]}"
    fi

    AEM_JAR_FILE="${JAR_ARRAY[0]}"
    check_mode "$AEM_JAR_FILE" fr

    # Initialize instance name
    AEM_INSTANCE_NAME="${MY_FQDN}_${AEM_ADDRESS}_${AEM_PORT}"

    # Initialize lock file name
    MY_LOCK_FILE="/tmp/.aem_${AEM_INSTANCE_NAME}.lock"

    # Change back to original directory
    cd "$MY_CWD"
    }

    #--------------------------------------------------------------------------
    # NAME
    # configure_shell - configure shell limits
    #
    # SYNOPSIS
    # configure_shell
    #
    # DESCRIPTION
    # Checks the maximum number of open file descriptors and the maximum
    # amount of virtual memory available to the shell and sets these
    # built-in shell limits to their highest possible values.
    #--------------------------------------------------------------------------
    configure_shell() {
    local -r REGEX='[[:digit:]]+'
    local VALUE
    local -i EXIT_STATUS

    # Check maximum number of open file descriptors
    VALUE="$(ulimit -n)"
    if [[ "$VALUE" =~ $REGEX ]]; then
    if (( VALUE < 65535 )); then
    # Set maximum number of open file descriptors
    for VALUE in 65535 63999 32768 16384 8192; do
    ulimit -n $VALUE &>/dev/null
    EXIT_STATUS=$?
    if (( EXIT_STATUS == 0 )); then
    break
    fi
    done
    if (( EXIT_STATUS != 0 )); then
    crit "The maximum number of open file descriptors must be at least 8192"
    fi
    fi
    elif [[ "$VALUE" != 'unlimited' ]]; then
    crit "\"ulimit -n\" returned unknown value \"$VALUE\""
    fi

    # Check maximum amount of virtual memory available to the shell
    VALUE="$(ulimit -v)"
    if [[ "$VALUE" != 'unlimited' ]]; then
    # Set maximum amount of virtual memory available to the shell
    ulimit -v unlimited &>/dev/null
    EXIT_STATUS=$?
    if (( EXIT_STATUS != 0 )); then
    crit "The maximum amount of virtual memory available to the shell must be unlimited"
    fi
    fi

    # Set file mode mask
    umask "$AEM_UMASK"
    }

    #--------------------------------------------------------------------------
    # NAME
    # rotate_log_dirs - rotate log directories
    #
    # SYNOPSIS
    # rotate_log_dirs
    #
    # DESCRIPTION
    # Deletes old log directory structures, creates a new log directoy
    # structure and replaces all AEM log directories with symbolic links
    # to this new log directory structure.
    #--------------------------------------------------------------------------
    rotate_log_dirs() {
    local -r CURRENT_LOG_DIR='current'
    local -r LOG_BASE_DIR="$LOG_HOME/$AEM_INSTANCE_NAME"
    local REGEX
    local -a LOG_DIR_ARRAY
    local -i INDEX
    local LOG_DIR

    # Create log base directory
    "$MKDIR" -p "$LOG_BASE_DIR/" &>/dev/null

    # Get array of all existing log directories sorted alphabetically in reverse order
    REGEX="^.*/[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+Z\$"
    LOG_DIR_ARRAY=( $( \
    "$FIND" \
    "$LOG_BASE_DIR" \
    -maxdepth 1 \
    -regex "$REGEX" \
    2>/dev/null | \
    "$SORT" -r \
    2>/dev/null \
    ) )

    # Delete old log directories
    INDEX=${#LOG_DIR_ARRAY[*]}
    while (( INDEX >= LOG_HISTORY )); do
    (( INDEX-- ))
    LOG_DIR="${LOG_DIR_ARRAY[INDEX]}"
    "$RM" -rf "$LOG_DIR" &>/dev/null
    done

    # Check if "logs" is a real directory
    if [[ ! -L "$AEM_BASE_DIR/logs" && -d "$AEM_BASE_DIR/logs/" ]]; then
    # Move "logs" directory to new log directory
    "$MV" "$AEM_BASE_DIR/logs" "$LOG_BASE_DIR/$MY_TIMESTAMP" &>/dev/null
    else
    # Create new log directory
    "$MKDIR" -p "$LOG_BASE_DIR/$MY_TIMESTAMP" &>/dev/null
    fi

    # Make "current" symbolic link point to new log directoy
    cd "$LOG_BASE_DIR"
    "$RM" -f "$CURRENT_LOG_DIR" &>/dev/null
    "$LN" -s "$MY_TIMESTAMP" "$CURRENT_LOG_DIR" &>/dev/null

    # Make "logs" symbolic link point to "current" symbolic link
    "$RM" -f "$AEM_BASE_DIR/logs" &>/dev/null
    "$LN" -s "$LOG_BASE_DIR/$CURRENT_LOG_DIR" "$AEM_BASE_DIR/logs" &>/dev/null
    }

    #--------------------------------------------------------------------------
    # NAME
    # get_aem_pid - get Adobe AEM process ID
    #
    # SYNOPSIS
    # get_aem_pid
    #
    # DESCRIPTION
    # Sets the global AEM_PID variable to the process ID of the
    # Adobe AEM process.
    #--------------------------------------------------------------------------
    get_aem_pid() {
    #set -x
    local -r REGEX="-Daem\\.instance\\.name=${AEM_INSTANCE_NAME//./\\.}"

    # Get process ID of Adobe AEM process
    AEM_PID=$("$PGREP" -u $EUID -f -- "$REGEX" 2>/dev/null)
    # Check if process ID is empty
    if [[ -z "$AEM_PID" ]]; then
    # info "Java-process is NOT running for $REGEX "
    # Check if there is a PID file (maybe AEM was started
    # with the vanilla start script)
    if [[ -r "$AEM_BASE_DIR/conf/cq.pid" ]]; then
    # Get process ID from PID file
    AEM_PID="$("$CAT" "$AEM_BASE_DIR/conf/cq.pid" 2>/dev/null)"
    if [[ -n "$AEM_PID" ]]; then
    # Check if AEM process is still running
    kill -0 "$AEM_PID" &>/dev/null
    if (( $? != 0 )); then
    # AEM process is no longer running
    AEM_PID=''
    info "Java-process is NO LONGER running for $REGEX "
    fi
    fi
    fi
    fi
    # set +x
    }

    #--------------------------------------------------------------------------
    # NAME
    # activate - activate all installed/resolved Apache Felix bundles
    #
    # SYNOPSIS
    # activate
    #
    # DESCRIPTION
    # Activates all resolved Apache Felix bundles.
    #--------------------------------------------------------------------------
    activate() {
    local URL
    local OUTPUT
    local -i EXIT_STATUS
    local REGEX
    local -a BUNDLE_ARRAY
    local -i BUNDLE_COUNT
    local BUNDLE
    local -i BUNDLE_ID

    info "Retrieving all installed or resolved Apache Felix bundles"

    # Get bundle information from Apache Felix
    URL="http://$AEM_AUTHORITY/system/console/bundles.json"
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS != 0 )); then
    crit "\"$CURL\" returned error code $EXIT_STATUS" "$OUTPUT"
    fi

    # Extract all installed or resolved bundles from JSON output
    REGEX='\{[^\}]*"state"[[:space:]]*:[[:space:]]*"(Installed|Resolved)"[^\}]+\}'
    IFS="$LF"
    BUNDLE_ARRAY=( $( \
    echo "$OUTPUT" | \
    "$GREP" -Eio "$REGEX" 2>/dev/null | \
    "$GREP" -Eiv "$SKIP_BUNDLE_REGEX" 2>/dev/null \
    ) )
    IFS="$MY_IFS"

    BUNDLE_COUNT=${#BUNDLE_ARRAY[@]}

    info "Apache Felix reports that there are $BUNDLE_COUNT installed or resolved bundles"

    if (( BUNDLE_COUNT == 0 )); then
    return
    fi

    # Start all installed or resolved bundles
    REGEX='"id"[[:space:]]*:[[:space:]]*([[:digit:]]+)'
    for BUNDLE in "${BUNDLE_ARRAY[@]}"; do
    if [[ "$BUNDLE" =~ $REGEX ]]; then
    BUNDLE_ID=${BASH_REMATCH[1]}

    info "Starting bundle with ID $BUNDLE_ID"

    URL="http://$AEM_AUTHORITY/system/console/bundles/$BUNDLE_ID"
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request POST \
    --url "$URL" \
    --data 'action=start' \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS != 0 )); then
    crit "\"$CURL\" returned error code $EXIT_STATUS" "$OUTPUT"
    fi
    fi
    done
    }

    #--------------------------------------------------------------------------
    # NAME
    # start - start AEM service
    #
    # SYNOPSIS
    # start
    #
    # DESCRIPTION
    # Starts the AEM service.
    #--------------------------------------------------------------------------
    start() {
    local -a JAVA_OPT_ARRAY
    local -a AEM_OPT_ARRAY
    local URL
    local OUTPUT
    local REGEX
    local REGEX2
    local -i EXIT_STATUS
    local -i START_TIME
    local -i DURATION
    local -i REMAINING_TIME
    local -a BUNDLE_ARRAY
    local -i BUNDLE_COUNT

    # Make sure there is no stale PID file
    "$RM" -f "$AEM_BASE_DIR/conf/cq.pid" &>/dev/null

    # Start Adobe AEM
    info "Starting Adobe AEM process"

    # Assemble Java options
    JAVA_OPT_ARRAY=( \
    '-server' \
    "-Xms${JAVA_HEAP_SIZE}m" \
    "-Xmx${JAVA_HEAP_SIZE}m" \
    "-XX:PermSize=${JAVA_PERM_SIZE}m" \
    "-XX:MaxPermSize=${JAVA_PERM_SIZE}m" \
    '-Djava.awt.headless=true' \
    '-Dfile.encoding=UTF8' \
    "-Daem.instance.name=$AEM_INSTANCE_NAME" \
    "-Dsling.run.modes=$AEM_RUN_MODES" \
    )

    # Assemble Adobe AEM options
    AEM_OPT_ARRAY=( \
    'start' \
    '-a' "$AEM_ADDRESS" \
    '-p' "$AEM_PORT" \
    '-c' "$AEM_BASE_DIR" \
    '-i' "$AEM_BASE_DIR/launchpad" \
    )

    # Start Adobe AEM process
    configure_shell
    "$ENV" \
    -i \
    "LANG=$JAVA_LANG" \
    "TZ=$JAVA_TZ" \
    "JAVA_HOME=$JAVA_HOME" \
    "$NOHUP" \
    "$JAVA" \
    "${JAVA_OPT_ARRAY[@]}" \
    "${JAVA_EXTRA_OPT_ARRAY[@]}" \
    -jar "$AEM_JAR_FILE" \
    "${AEM_OPT_ARRAY[@]}" \
    "${AEM_EXTRA_OPT_ARRAY[@]}" \
    0</dev/null \
    1>"$AEM_BASE_DIR/logs/startup.log" \
    2>&1 \
    &

    # Check for error code
    EXIT_STATUS=$?
    if (( EXIT_STATUS != 0 )); then
    crit \
    "Adobe AEM returned error $EXIT_STATUS" \
    "$("$TAIL" "$AEM_BASE_DIR/logs/startup.log" 2>/dev/null)"
    fi

    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < START_TIMEOUT )); do
    # Check if Adobe AEM process is running
    get_aem_pid
    if [[ -n "$AEM_PID" ]]; then
    break
    else
    info "Adobe AEM process NOT running yet. PLEASE CHECK startup.log"
    fi

    # Compute remaining time
    (( REMAINING_TIME = START_TIMEOUT - DURATION ))

    info "$REMAINING_TIME seconds remaining"

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    # Check for timeout
    if (( DURATION >= START_TIMEOUT )); then
    # Check if Adobe AEM process is running
    get_aem_pid
    if [[ -n "$AEM_PID" ]]; then
    crit \
    "Adobe AEM process failed to start within $START_TIMEOUT seconds"
    "$("$TAIL" "$AEM_BASE_DIR/logs/startup.log" 2>/dev/null)"
    else
    crit \
    "Adobe AEM process NOT started within $START_TIMEOUT seconds. PLEASE CHECK startup.log"
    "$("$TAIL" "$AEM_BASE_DIR/logs/startup.log" 2>/dev/null)"

    fi
    else
    # Adjust start timeout
    (( START_TIMEOUT -= DURATION ))
    fi

    # Adobe AEM process is running
    echo "$AEM_PID" > "$AEM_BASE_DIR/conf/cq.pid" 2>/dev/null
    info "Adobe AEM process is running with PID $AEM_PID"

    # Wait for Apache Sling to respond
    info "Waiting for Apache Sling to become available"

    REGEX='[[:blank:]]OK$'
    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < START_TIMEOUT )); do
    OUTPUT="$(\
    "$JAVA" \
    -jar "$AEM_JAR_FILE" \
    status \
    -c "$AEM_BASE_DIR" \
    0</dev/null \
    2>&1 \
    )"

    EXIT_STATUS=$?
    if (( EXIT_STATUS == 0 )); then
    # Check for regular expression in output
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    # Apache Sling responded properly
    break
    fi
    fi

    # Compute remaining time
    (( REMAINING_TIME = START_TIMEOUT - DURATION ))

    if [[ -z "$OUTPUT" ]]; then
    info "$REMAINING_TIME seconds remaining - Apache Sling does not respond"
    else
    get_aem_pid
    if [[ -z "$AEM_PID" ]]; then
    crit \
    "Adobe AEM process is no longer running. PLEASE CHECK startup.log or error.log"
    "$("$TAIL" "$AEM_BASE_DIR/logs/startup.log" 2>/dev/null)"
    else
    info "$REMAINING_TIME seconds remaining - Apache Sling responds, but not as expected"
    fi
    fi

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    # Check for timeout
    if (( DURATION >= START_TIMEOUT )); then
    if (( EXIT_STATUS != 0 )); then
    crit "Apache Sling failed to respond within $START_TIMEOUT seconds" "$OUTPUT"
    else
    crit "Apache Sling failed to start within $START_TIMEOUT seconds" "$OUTPUT"
    fi
    else
    # Adjust start timeout
    (( START_TIMEOUT -= DURATION ))
    fi

    info "Apache Sling is available"

    # Wait for Apache Felix to start
    info "Waiting for Apache Felix to become available"

    URL="http://$AEM_AUTHORITY/system/console/vmstat"
    REGEX='System is up and running!'
    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < START_TIMEOUT )); do
    # Check if Apache Felix is up and running
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    # Extract status line from output
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    break
    fi

    # Compute remaining time
    (( REMAINING_TIME = START_TIMEOUT - DURATION ))

    if [[ -z "$OUTPUT" ]]; then
    info "$REMAINING_TIME seconds remaining - Apache Felix does not respond"
    else
    info "$REMAINING_TIME seconds remaining - Apache Felix responds, but not as expected"
    fi

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    # Check for timeout
    if (( DURATION >= START_TIMEOUT )); then
    # Check for connection timeout
    if (( EXIT_STATUS != 0 )); then
    crit "Apache Felix failed to respond within $START_TIMEOUT seconds" "$OUTPUT"
    else
    crit "Apache Felix failed to start within $START_TIMEOUT seconds" "$OUTPUT"
    fi
    else
    # Adjust start timeout
    (( START_TIMEOUT -= DURATION ))
    fi

    URL="http://$AEM_AUTHORITY/system/console/bundles.json"

    # Filter "Installed", "Resolved" or "Starting" bundles
    REGEX='\{[^\}]*"state"[[:space:]]*:[[:space:]]*"(Installed|Resolved|Starting)"[^\}]+\}'
    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < START_TIMEOUT )); do
    # Get bundle information from Apache Felix
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS == 0 )); then
    # Extract all installed or resolved bundles from JSON output
    IFS="$LF"
    BUNDLE_ARRAY=( $( \
    echo "$OUTPUT" | \
    "$GREP" -Eio "$REGEX" 2>/dev/null | \
    "$GREP" -Eiv "$SKIP_BUNDLE_REGEX" 2>/dev/null \
    ) )
    IFS="$MY_IFS"

    # Get number of installed or resolved bundles
    BUNDLE_COUNT=${#BUNDLE_ARRAY[@]}
    if (( BUNDLE_COUNT == 0 )); then
    # No more installed or resolved bundles
    break
    fi
    fi

    # Compute remaining time
    (( REMAINING_TIME = START_TIMEOUT - DURATION ))

    if (( EXIT_STATUS != 0 || BUNDLE_COUNT < 0 )); then
    info "$REMAINING_TIME seconds remaining - Apache Felix does not respond"
    else
    info "$REMAINING_TIME seconds remaining - Apache Felix reports that $BUNDLE_COUNT bundle(s) are not started yet"
    fi

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    # Check for timeout
    if (( DURATION >= START_TIMEOUT )); then
    # Check for connection timeout
    if (( EXIT_STATUS != 0 )); then
    crit "Apache Felix failed to respond within $START_TIMEOUT seconds" "$OUTPUT"
    else
    crit "Apache Felix failed to start $BUNDLE_COUNT bundle(s) within $START_TIMEOUT seconds"
    fi
    else
    # Adjust start timeout
    (( START_TIMEOUT -= DURATION ))
    fi

    info "Apache Felix is available"

    # Wait for Adobe CRX to start
    info "Waiting for Adobe CRX to become available"

    URL="http://$AEM_AUTHORITY/crx/packmgr/service.jsp?cmd=ls"
    REGEX='<status[[:space:]]+code="200"'
    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < START_TIMEOUT )); do
    # Check if Adobe CRX is responsive
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS == 0 )); then
    # Check for regular expression in output
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    # Adobe CRX responded properly
    break
    fi
    fi

    # Compute remaining time
    (( REMAINING_TIME = START_TIMEOUT - DURATION ))
    info "$REMAINING_TIME seconds remaining"

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    # Check for timeout
    if (( DURATION >= START_TIMEOUT )); then
    if (( EXIT_STATUS != 0 )); then
    crit "Adobe CRX failed to respond within $START_TIMEOUT seconds" "$OUTPUT"
    else
    crit "Adobe CRX failed to start within $START_TIMEOUT seconds" "$OUTPUT"
    fi
    else
    # Adjust start timeout
    (( START_TIMEOUT -= DURATION ))
    fi

    info "Adobe CRX is available"

    # Wait for Adobe WCM to start
    info "Waiting for Adobe WCM to become available"

    URL="http://$AEM_AUTHORITY/libs/cq/core/content/welcome.html"
    REGEX='Welcome,[[:space:]]+Administrator\.'
    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < START_TIMEOUT )); do
    # Check if Adobe WCM is responsive
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS == 0 )); then
    # Check for regular expression in output
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    # Adobe WCM responded properly
    break;
    fi
    fi

    # Compute remaining time
    (( REMAINING_TIME = START_TIMEOUT - DURATION ))
    info "$REMAINING_TIME seconds remaining"

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    # Check for timeout
    if (( DURATION >= START_TIMEOUT )); then
    if (( EXIT_STATUS != 0 )); then
    crit "Adobe WCM failed to respond within $START_TIMEOUT seconds" "$OUTPUT"
    else
    crit "Adobe WCM failed to start within $START_TIMEOUT seconds" "$OUTPUT"
    fi
    fi

    info "Adobe WCM is available"
    }

    #--------------------------------------------------------------------------
    # NAME
    # stop - stop service

    # SYNOPSIS
    # stop
    #
    # DESCRIPTION
    # Stops the AEM service
    #--------------------------------------------------------------------------
    stop() {
    local -i START_TIME
    local -i DURATION
    local -i REMAINING_TIME

    # Stop Adobe AEM
    info "Stopping Adobe AEM process"

    cd "$AEM_HOME"
    (
    "$ENV" \
    -i \
    "LANG=$JAVA_LANG" \
    "TZ=$JAVA_TZ" \
    "JAVA_HOME=$JAVA_HOME" \
    "$JAVA" \
    -Djava.awt.headless=true \
    -Dfile.encoding=UTF8 \
    -jar "$AEM_JAR_FILE" \
    stop \
    -c "$AEM_BASE_DIR" \
    0</dev/null \
    1>"$AEM_BASE_DIR/logs/shutdown.log" \
    2>&1
    ) &

    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < STOP_TIMEOUT )); do
    # Check if Adobe AEM process stopped
    kill -0 $AEM_PID &>/dev/null
    if (( $? != 0 )); then
    # Adobe AEM stopped
    break
    fi

    # Compute remaining time
    (( REMAINING_TIME = STOP_TIMEOUT - DURATION ))
    info "$REMAINING_TIME seconds remaining"

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    if (( DURATION >= STOP_TIMEOUT )); then
    err "Adobe AEM process failed to stop"

    # Stop Adobe AEM process with SIGTERM
    info "Sending TERM signal to Adobe AEM process running with PID $AEM_PID"
    kill -15 $AEM_PID &>/dev/null

    # Wait for Adobe AEM to stop
    info "Waiting for Adobe AEM process to stop"

    START_TIME=$("$DATE" -u '+%s' 2>/dev/null)
    DURATION=0
    while (( DURATION < STOP_TIMEOUT )); do
    # Check if Adobe AEM process stopped
    kill -0 $AEM_PID &>/dev/null
    if (( $? != 0 )); then
    # Adobe AEM stopped
    break
    fi

    # Compute remaining time
    (( REMAINING_TIME = STOP_TIMEOUT - DURATION ))
    info "$REMAINING_TIME seconds remaining"

    # Wait ...
    "$SLEEP" $CHECK_DELAY &>/dev/null

    # Update duration
    (( DURATION = $("$DATE" -u '+%s' 2>/dev/null) - START_TIME ))
    done

    if (( DURATION >= STOP_TIMEOUT )); then
    err "Adobe AEM process failed to stop"

    # Stop Adobe AEM process with SIGKILL
    info "Sending KILL signal to Adobe AEM process running with PID $AEM_PID"
    kill -9 $AEM_PID &>/dev/null
    fi
    fi

    # Adobe AEM process stopped
    "$RM" -f "$AEM_BASE_DIR/conf/cq.pid" &>/dev/null
    }

    #--------------------------------------------------------------------------
    # NAME
    # status - get service status
    #
    # SYNOPSIS
    # service
    #
    # DESCRIPTION
    # Performs elementary functional test.
    #--------------------------------------------------------------------------
    status() {
    local URL
    local REGEX
    local OUTPUT
    local -a BUNDLE_ARRAY
    local -i BUNDLE_COUNT
    local -i RESULT=0

    # Get status information from Apache Sling
    cd "$AEM_HOME"
    OUTPUT="$( \
    "$JAVA" \
    -jar "$AEM_JAR_FILE" \
    status \
    -c "$AEM_BASE_DIR" \
    2>&1 \
    )"
    if (( $? == 0 )); then
    # Check for regular expression in output
    REGEX='[[:blank:]]OK$'
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    info "Apache Sling is available"
    else
    err "Apache Sling did not respond as expected" "$OUTPUT"
    RESULT=1
    fi
    else
    err "Apache Sling failed to respond" "$OUTPUT"
    RESULT=1
    fi

    # Check if Apache Felix is up and running
    URL="http://$AEM_AUTHORITY/system/console/vmstat"
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    # Extract status line from output
    REGEX='System is up and running!'
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    # Get bundle information from Apache Felix
    URL="http://$AEM_AUTHORITY/system/console/bundles.json"
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS == 0 )); then
    # Extract all installed or resolved bundles from JSON output
    REGEX='\{[^\}]*"state"[[:space:]]*:[[:space:]]*"(Installed|Resolved|Starting)"[^\}]+\}'
    IFS="$LF"
    BUNDLE_ARRAY=( $( \
    echo "$OUTPUT" | \
    "$GREP" -Eio "$REGEX" 2>/dev/null | \
    "$GREP" -Eiv "$SKIP_BUNDLE_REGEX" 2>/dev/null \
    ) )
    IFS="$MY_IFS"

    # Get number of installed or resolved bundles
    BUNDLE_COUNT=${#BUNDLE_ARRAY[@]}
    if (( BUNDLE_COUNT == 0 )); then
    info "Apache Felix is available"
    else
    err "Apache Felix failed to start $BUNDLE_COUNT bundles"
    RESULT=1
    fi
    else
    err "Apache Felix failed to respond" "$OUTPUT"
    RESULT=1
    fi
    else
    err "Apache Felix failed to start" "$OUTPUT"
    RESULT=1
    fi

    # Check if Adobe CRX is responsive
    URL="http://$AEM_AUTHORITY/crx/packmgr/service.jsp?cmd=ls"
    REGEX='<status[[:space:]]+code="200"'
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS == 0 )); then
    # Check for regular expression in output
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    info "Adobe CRX is available"
    else
    err "Adobe CRX did not respond as expected" "$OUTPUT"
    RESULT=1
    fi
    else
    err "Adobe CRX failed to respond" "$OUTPUT"
    RESULT=1
    fi

    # Check if Adobe WCM is responsive
    URL="http://$AEM_AUTHORITY/libs/cq/core/content/welcome.html"
    REGEX='Welcome,[[:space:]]+Administrator\.'
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request GET \
    --url "$URL" \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS == 0 )); then
    # Check for regular expression in output
    if [[ "$OUTPUT" =~ $REGEX ]]; then
    info "Adobe WCM is available"
    else
    err "Adobe WCM did not respond as expected" "$OUTPUT"
    RESULT=1
    fi
    else
    err "Adobe WCM failed to respond" "$OUTPUT"
    RESULT=1
    fi

    return $RESULT
    }

    #--------------------------------------------------------------------------
    # NAME
    # sweep - Run memory garbage collection
    #
    # SYNOPSIS
    # sweep
    #
    # DESCRIPTION
    # Runs a memory garbage collection.
    #--------------------------------------------------------------------------
    sweep() {
    local URL
    local OUTPUT
    local -i EXIT_STATUS

    # Run memory garbage collection
    URL="http://$AEM_AUTHORITY/system/console/memoryusage"
    OUTPUT="$( \
    "$CURL" \
    --silent \
    --show-error \
    --connect-timeout $CONNECT_TIMEOUT \
    --max-time $SOCKET_TIMEOUT \
    --location \
    --user "$AEM_CREDENTIALS" \
    --request POST \
    --url "$URL" \
    --data 'command=gc' \
    2>&1 \
    )"
    EXIT_STATUS=$?

    if (( EXIT_STATUS != 0 )); then
    crit "\"$CURL\" returned error code $EXIT_STATUS" "$OUTPUT"
    fi
    }

    #--------------------------------------------------------------------------
    # Main program
    #--------------------------------------------------------------------------

    # Source configuration
    load_config

    # Check configuration
    check_config

    if [ "$2" = "first" ];then
    (( START_TIMEOUT = START_TIMEOUT * 10 ))
    info "Set START_TIMEOUT to $START_TIMEOUT due to first start"
    fi

    # Analyze command-line argument
    case "$1" in
    'start')
    # Lock this script
    lock

    info "Starting Adobe AEM service"

    # Check if Adobe AEM process is already running
    get_aem_pid
    if [[ -z "$AEM_PID" ]]; then
    # Rotate log directories
    rotate_log_dirs

    # Start Adobe AEM service
    start
    fi

    info "Adobe AEM service started successfully"

    # Unlock this script
    unlock
    ;;

    'stop')
    # Lock this script
    lock

    info "Stopping Adobe AEM service"

    # Check if Adobe AEM process is running
    get_aem_pid
    if [[ -n "$AEM_PID" ]]; then
    # Stop Adobe AEM service
    stop
    fi

    info "Adobe AEM service stopped successfully"

    # Unlock this script
    unlock
    ;;

    'restart')
    # Lock this script
    lock

    info "Restarting Adobe AEM service"

    # Check if Adobe AEM process is already running
    get_aem_pid
    if [[ -n "$AEM_PID" ]]; then
    # Stop Adobe AEM service
    stop
    fi

    # Rotate log directories
    rotate_log_dirs

    # Start Adobe AEM service
    start

    info "Adobe AEM service restarted successfully"

    # Unlock this script
    unlock
    ;;

    'status')
    info "Checking status of Adobe AEM service"

    # Check if Adobe AEM process is running
    get_aem_pid
    if [[ -z "$AEM_PID" ]]; then
    err "Adobe AEM service is not available"
    exit 3
    else
    info "Adobe AEM is running with PID $AEM_PID"

    # Check if Adobe AEM service is available
    status
    if (( $? == 0 )); then
    info "Adobe AEM service is available"
    else
    err "Adobe AEM service is not available"
    exit 3
    fi
    fi
    ;;

    'activate')
    # Lock this script
    lock

    # Check if Adobe AEM process is running
    get_aem_pid
    if [[ -z "$AEM_PID" ]]; then
    crit "Adobe AEM service is not running"
    fi

    info "Activating all installed or resolved Apache Felix bundles"

    activate

    # Unlock this script
    unlock
    ;;

    'sweep')
    # Lock this script
    lock

    # Check if Adobe AEM process is running
    get_aem_pid
    if [[ -z "$AEM_PID" ]]; then
    crit "Adobe AEM service is not running"
    fi

    info "Running memory garbage collection"

    # Run memory garbage collection
    sweep

    info "Memory garbage collection complete"

    # Unlock this script
    unlock
    ;;

    'help')
    usage 0
    ;;

    'version')
    version
    ;;

    *)
    usage
    ;;
    esac

    exit 0